From d830b88ed345a631242708139854692de0e727b8 Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jones@uni-muenster.de>
Date: Mon, 9 Nov 2026 09:59:02 +0100
Subject: [PATCH v1] Omit the XML declaration of xml values embedded as element
 content

xmlelement(), xmlforest() and the query_to_xml() family copied the XML
declaration of an xml argument into the element whenever xml_out()
would print it, i.e. for a standalone property or a version other than
1.0.  A declaration is only allowed at the start of a document, so the
result could not be read back by xml_in().

Add a flag to xml_out_internal() to omit the declaration, and use it in
map_sql_value_to_xml_value() for xml values used as element content.
---
 src/backend/utils/adt/xml.c       | 20 +++++++++++++++-----
 src/test/regress/expected/xml.out | 19 +++++++++++++++++++
 src/test/regress/sql/xml.sql      |  4 ++++
 3 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 44a3a21d135..91f0bba9766 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -305,9 +305,11 @@ xml_in(PG_FUNCTION_ARGS)
  *
  * This is only acceptable so long as xmltype and text use the same
  * representation.
+ *
+ * If omit_xmldecl is true, no XML declaration is printed.
  */
 static char *
-xml_out_internal(xmltype *x, pg_enc target_encoding)
+xml_out_internal(xmltype *x, pg_enc target_encoding, bool omit_xmldecl)
 {
 	char	   *str = text_to_cstring((text *) x);
 
@@ -324,7 +326,8 @@ xml_out_internal(xmltype *x, pg_enc target_encoding)
 
 		initStringInfo(&buf);
 
-		if (!print_xml_decl(&buf, version, target_encoding, standalone))
+		if (omit_xmldecl ||
+			!print_xml_decl(&buf, version, target_encoding, standalone))
 		{
 			/*
 			 * If we are not going to produce an XML declaration, eat a single
@@ -361,7 +364,7 @@ xml_out(PG_FUNCTION_ARGS)
 	 * different client encoding, so we'd do more harm than good by including
 	 * it.
 	 */
-	PG_RETURN_CSTRING(xml_out_internal(x, 0));
+	PG_RETURN_CSTRING(xml_out_internal(x, 0, false));
 }
 
 
@@ -444,7 +447,7 @@ xml_send(PG_FUNCTION_ARGS)
 	 * xml_out_internal doesn't convert the encoding, it just prints the right
 	 * declaration. pq_sendtext will do the conversion.
 	 */
-	outval = xml_out_internal(x, pg_get_client_encoding());
+	outval = xml_out_internal(x, pg_get_client_encoding(), false);
 
 	pq_begintypsend(&buf);
 	pq_sendtext(&buf, outval, strlen(outval));
@@ -2725,6 +2728,13 @@ map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
 
 		}
 
+		/*
+		 * An XML declaration is only allowed at the start of a document, so
+		 * omit it from xml values used as element content.
+		 */
+		if (type == XMLOID && xml_escape_strings)
+			return xml_out_internal(DatumGetXmlP(value), 0, true);
+
 		/*
 		 * otherwise, just use the type's native text representation
 		 */
@@ -4836,7 +4846,7 @@ XmlTableSetDocument(TableFuncScanState *state, Datum value)
 	 * Use out function for casting to string (remove encoding property). See
 	 * comment in xml_out.
 	 */
-	str = xml_out_internal(xmlval, 0);
+	str = xml_out_internal(xmlval, 0, false);
 
 	length = strlen(str);
 	xstr = pg_xmlCharStrndup(str, length);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 6998745ee03..66c0ac763c7 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -190,6 +190,25 @@ SELECT xmlelement(name foo, xml 'b<a/>r');
  <foo>b<a/>r</foo>
 (1 row)
 
+-- an XML declaration is not kept when an xml value is embedded as content
+SELECT xmlelement(name foo, '<?xml version="1.0" standalone="yes"?><a/>'::xml);
+   xmlelement    
+-----------------
+ <foo><a/></foo>
+(1 row)
+
+SELECT xmlforest('<?xml version="1.1"?><a/>'::xml AS foo);
+    xmlforest    
+-----------------
+ <foo><a/></foo>
+(1 row)
+
+SELECT xmlelement(name foo, array['<?xml version="1.0" standalone="no"?><a/>'::xml]);
+             xmlelement             
+------------------------------------
+ <foo><element><a/></element></foo>
+(1 row)
+
 SELECT xmlelement(name foo, array[1, 2, 3]);
                                xmlelement                                
 -------------------------------------------------------------------------
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0581d21b03f..ce38a19bafb 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -62,6 +62,10 @@ SELECT xmlelement(name foo, text 'bar');
 SELECT xmlelement(name foo, xml 'bar');
 SELECT xmlelement(name foo, text 'b<a/>r');
 SELECT xmlelement(name foo, xml 'b<a/>r');
+-- an XML declaration is not kept when an xml value is embedded as content
+SELECT xmlelement(name foo, '<?xml version="1.0" standalone="yes"?><a/>'::xml);
+SELECT xmlforest('<?xml version="1.1"?><a/>'::xml AS foo);
+SELECT xmlelement(name foo, array['<?xml version="1.0" standalone="no"?><a/>'::xml]);
 SELECT xmlelement(name foo, array[1, 2, 3]);
 SET xmlbinary TO base64;
 SELECT xmlelement(name foo, bytea 'bar');
-- 
2.56.0

