On 10.02.23 02:10, Peter Smith wrote:
On Thu, Feb 9, 2023 at 7:17 PM Jim Jones <jim.jo...@uni-muenster.de> wrote:
1.
FYI, there are some whitespace warnings applying the v5 patch

Trailing whitespaces removed. The patch applies now without warnings.
======
src/test/regress/sql/xml.sql

2.
The v5 patch was already testing NULL, but it might be good to add
more tests to verify the function is behaving how you want for edge
cases. For example,

+-- XML pretty print: NULL, empty string, spaces only...
SELECT xmlpretty(NULL);
SELECT xmlpretty('');
SELECT xmlpretty('     ');

Test cases added.

3.
The function is returning XML anyway, so is the '::xml' casting in
these tests necessary?

e.g.
SELECT xmlpretty(NULL)::xml; --> SELECT xmlpretty(NULL);
It is indeed not necessary. Most likely I used it for testing and forgot to remove it afterwards. Now removed.
======
src/include/catalog/pg_proc.dat

4.

+  { oid => '4642', descr => 'Indented text from xml',
+  proname => 'xmlpretty', prorettype => 'xml',
+  proargtypes => 'xml', prosrc => 'xmlpretty' },

Spurious leading space for this new entry.
Removed.

======
doc/src/sgml/func.sgml

5.
+ <foo id="x">
+   <bar id="y">
+     <var id="z">42</var>
+   </bar>
+ </foo>
+
+(1 row)
+
+]]></screen>

A spurious blank line in the example after the "(1 row)"
Removed.
~~~

6.
Does this function docs belong in section 9.15.1 "Producing XML
Content"? Or (since it is not really producing content) should it be
moved to the 9.15.3 "Processing XML" section?
Moved to the section 9.15.3

Following the suggestion of Peter Eisentraut I renamed the function to xmlformat().

v6 attached.

Thanks a lot for the review.

Best, Jim
From 5ca93fe69e8b62895b13cdd51d568d061362c912 Mon Sep 17 00:00:00 2001
From: Jim Jones <jim.jo...@uni-muenster.de>
Date: Thu, 2 Feb 2023 21:27:16 +0100
Subject: [PATCH v6] Add pretty-printed XML output option

This small patch introduces a XML pretty print function.
It basically takes advantage of the indentation feature
of xmlDocDumpFormatMemory from libxml2 to format XML strings.
---
 doc/src/sgml/func.sgml              |  34 +++++++++
 src/backend/utils/adt/xml.c         |  45 ++++++++++++
 src/include/catalog/pg_proc.dat     |   3 +
 src/test/regress/expected/xml.out   | 108 ++++++++++++++++++++++++++++
 src/test/regress/expected/xml_1.out |  57 +++++++++++++++
 src/test/regress/sql/xml.sql        |  33 +++++++++
 6 files changed, 280 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e09e289a43..a621192425 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14861,6 +14861,40 @@ SELECT xmltable.*
 ]]></screen>
     </para>
    </sect3>
+
+     <sect3 id="functions-xml-xmlformat">
+    <title><literal>xmlformat</literal></title>
+
+     <indexterm>
+     <primary>xmlformat</primary>
+     </indexterm>
+
+<synopsis>
+<function>xmlformat</function> ( <type>xml</type> ) <returnvalue>xml</returnvalue>
+</synopsis>
+
+     <para>
+     Converts the given XML value to pretty-printed, indented text.
+     </para>
+
+     <para>
+     Example:
+     <screen><![CDATA[
+SELECT xmlformat('<foo id="x"><bar id="y"><var id="z">42</var></bar></foo>');
+        xmlformat
+--------------------------
+ <foo id="x">
+   <bar id="y">
+     <var id="z">42</var>
+   </bar>
+ </foo>
+
+(1 row)
+
+]]></screen>
+     </para>
+   </sect3>
+
   </sect2>
 
   <sect2 id="functions-xml-mapping">
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 079bcb1208..ec12707b5c 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -473,6 +473,51 @@ xmlBuffer_to_xmltype(xmlBufferPtr buf)
 }
 #endif
 
+Datum
+xmlformat(PG_FUNCTION_ARGS)
+{
+#ifdef USE_LIBXML
+
+	xmlDocPtr  doc;
+	xmlChar    *xmlbuf = NULL;
+	text       *arg = PG_GETARG_TEXT_PP(0);
+	StringInfoData buf;
+	int nbytes;
+
+	doc = xml_parse(arg, XMLOPTION_DOCUMENT, false, GetDatabaseEncoding(), NULL);
+
+	if(!doc)
+		elog(ERROR, "could not parse the given XML document");
+
+	/**
+	 * xmlDocDumpFormatMemory (
+	 *   xmlDocPtr doc,     # the XML document
+	 *   xmlChar **xmlbuf,  # the memory pointer
+	 *   int      *nbytes,  # the memory length
+	 *   int       format   # 1 = node indenting
+	 *)
+	 */
+
+	xmlDocDumpFormatMemory(doc, &xmlbuf, &nbytes, 1);
+
+	xmlFreeDoc(doc);
+
+	if(!nbytes)
+		elog(ERROR, "could not indent the given XML document");
+
+	initStringInfo(&buf);
+	appendStringInfoString(&buf, (const char *)xmlbuf);
+
+	xmlFree(xmlbuf);
+
+	PG_RETURN_XML_P(stringinfo_to_xmltype(&buf));
+
+#else
+	NO_XML_SUPPORT();
+return 0;
+#endif
+}
+
 
 Datum
 xmlcomment(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c0f2a8a77c..54e8a6262a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8842,6 +8842,9 @@
 { oid => '3053', descr => 'determine if a string is well formed XML content',
   proname => 'xml_is_well_formed_content', prorettype => 'bool',
   proargtypes => 'text', prosrc => 'xml_is_well_formed_content' },
+{ oid => '4642', descr => 'Indented text from xml',
+  proname => 'xmlformat', prorettype => 'xml',
+  proargtypes => 'xml', prosrc => 'xmlformat' },
 
 # json
 { oid => '321', descr => 'I/O',
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 3c357a9c7e..8e53c87a41 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1599,3 +1599,111 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH
  <foo/> | &lt;foo/&gt;
 (1 row)
 
+-- XML format: single line XML string
+SELECT xmlformat('<breakfast_menu id="42"><food type="discounter"><name>Belgian Waffles</name><price>$5.95</price><description>Two of our famous Belgian Waffles with plenty of real maple syrup</description><calories>650</calories></food></breakfast_menu>');
+                                            xmlformat                                             
+--------------------------------------------------------------------------------------------------
+ <breakfast_menu id="42">                                                                        +
+   <food type="discounter">                                                                      +
+     <name>Belgian Waffles</name>                                                                +
+     <price>$5.95</price>                                                                        +
+     <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>+
+     <calories>650</calories>                                                                    +
+   </food>                                                                                       +
+ </breakfast_menu>                                                                               +
+ 
+(1 row)
+
+-- XML format: XML string with space, tabs and newline between nodes
+SELECT xmlformat('<breakfast_menu id="73"> <food type="organic" class="fancy">    <name>Belgian Waffles</name> <price>$15.95</price>
+                                 <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
+<calories>650</calories>       </food>           </breakfast_menu>  ');
+                                            xmlformat                                             
+--------------------------------------------------------------------------------------------------
+ <breakfast_menu id="73">                                                                        +
+   <food type="organic" class="fancy">                                                           +
+     <name>Belgian Waffles</name>                                                                +
+     <price>$15.95</price>                                                                       +
+     <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>+
+     <calories>650</calories>                                                                    +
+   </food>                                                                                       +
+ </breakfast_menu>                                                                               +
+ 
+(1 row)
+
+-- XML format: XML string with space, tabs and newline between nodes, using a namespace
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; id="73"> <meal:food type="organic" class="fancy">   <meal:name>Belgian Waffles</meal:name>      <meal:price>$15.95</meal:price>
+                                 <meal:description>Two of our famous Belgian Waffles with plenty of real maple syrup</meal:description>
+<meal:calories>650</meal:calories>     </meal:food></meal:breakfast_menu>');
+                                                 xmlformat                                                  
+------------------------------------------------------------------------------------------------------------
+ <meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; id="73">                                      +
+   <meal:food type="organic" class="fancy">                                                                +
+     <meal:name>Belgian Waffles</meal:name>                                                                +
+     <meal:price>$15.95</meal:price>                                                                       +
+     <meal:description>Two of our famous Belgian Waffles with plenty of real maple syrup</meal:description>+
+     <meal:calories>650</meal:calories>                                                                    +
+   </meal:food>                                                                                            +
+ </meal:breakfast_menu>                                                                                    +
+ 
+(1 row)
+
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <!-- eat this --> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories>650</meal:calories> </meal:food></meal:breakfast_menu>');
+                                                  xmlformat                                                  
+-------------------------------------------------------------------------------------------------------------
+ <meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73">+
+   <meal:food type="organic" class="fancy">                                                                 +
+     <meal:name>Belgian Waffles</meal:name>                                                                 +
+     <!-- eat this -->                                                                                      +
+     <meal:price>$15.95</meal:price>                                                                        +
+     <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description> +
+     <meal:calories>650</meal:calories>                                                                     +
+   </meal:food>                                                                                             +
+ </meal:breakfast_menu>                                                                                     +
+ 
+(1 row)
+
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories><c><![CDATA[<unknown> &"<>!<a>foo</a>]]></c></meal:calories> </meal:food></meal:breakfast_menu>');
+                                                  xmlformat                                                  
+-------------------------------------------------------------------------------------------------------------
+ <meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73">+
+   <meal:food type="organic" class="fancy">                                                                 +
+     <meal:name>Belgian Waffles</meal:name>                                                                 +
+     <meal:price>$15.95</meal:price>                                                                        +
+     <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description> +
+     <meal:calories>                                                                                        +
+       <c><![CDATA[<unknown> &"<>!<a>foo</a>]]></c>                                                         +
+     </meal:calories>                                                                                       +
+   </meal:food>                                                                                             +
+ </meal:breakfast_menu>                                                                                     +
+ 
+(1 row)
+
+-- XML format: NULL parameter
+SELECT xmlformat(NULL);
+ xmlformat 
+-----------
+ 
+(1 row)
+
+-- XML format: empty string
+SELECT xmlformat('');
+ERROR:  invalid XML document
+DETAIL:  line 1: switching encoding : no input
+
+^
+line 1: Document is empty
+
+^
+-- XML format: invalid string (whitespaces)
+SELECT xmlformat('   ');
+ERROR:  invalid XML document
+DETAIL:  line 1: Start tag expected, '<' not found
+   
+   ^
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 378b412db0..0657f839a7 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1268,3 +1268,60 @@ DETAIL:  This functionality requires the server to be built with libxml support.
 SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: single line XML string
+SELECT xmlformat('<breakfast_menu id="42"><food type="discounter"><name>Belgian Waffles</name><price>$5.95</price><description>Two of our famous Belgian Waffles with plenty of real maple syrup</description><calories>650</calories></food></breakfast_menu>');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('<breakfast_menu id="42"><food type="discou...
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: XML string with space, tabs and newline between nodes
+SELECT xmlformat('<breakfast_menu id="73"> <food type="organic" class="fancy">    <name>Belgian Waffles</name> <price>$15.95</price>
+                                 <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
+<calories>650</calories>       </food>           </breakfast_menu>  ');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('<breakfast_menu id="73"> <food type="organ...
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: XML string with space, tabs and newline between nodes, using a namespace
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; id="73"> <meal:food type="organic" class="fancy">   <meal:name>Belgian Waffles</meal:name>      <meal:price>$15.95</meal:price>
+                                 <meal:description>Two of our famous Belgian Waffles with plenty of real maple syrup</meal:description>
+<meal:calories>650</meal:calories>     </meal:food></meal:breakfast_menu>');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fa...
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <!-- eat this --> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories>650</meal:calories> </meal:food></meal:breakfast_menu>');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fa...
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories><c><![CDATA[<unknown> &"<>!<a>foo</a>]]></c></meal:calories> </meal:food></meal:breakfast_menu>');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fa...
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: NULL parameter
+SELECT xmlformat(NULL);
+ xmlformat 
+-----------
+ 
+(1 row)
+
+-- XML format: empty string
+SELECT xmlformat('');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('');
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- XML format: invalid string (whitespaces)
+SELECT xmlformat('   ');
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlformat('   ');
+                         ^
+DETAIL:  This functionality requires the server to be built with libxml support.
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index ddff459297..2517e84419 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -624,3 +624,36 @@ SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n
 \x
 
 SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+
+
+-- XML format: single line XML string
+SELECT xmlformat('<breakfast_menu id="42"><food type="discounter"><name>Belgian Waffles</name><price>$5.95</price><description>Two of our famous Belgian Waffles with plenty of real maple syrup</description><calories>650</calories></food></breakfast_menu>');
+
+-- XML format: XML string with space, tabs and newline between nodes
+SELECT xmlformat('<breakfast_menu id="73"> <food type="organic" class="fancy">    <name>Belgian Waffles</name> <price>$15.95</price>
+                                 <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
+<calories>650</calories>       </food>           </breakfast_menu>  ');
+
+-- XML format: XML string with space, tabs and newline between nodes, using a namespace
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; id="73"> <meal:food type="organic" class="fancy">   <meal:name>Belgian Waffles</meal:name>      <meal:price>$15.95</meal:price>
+                                 <meal:description>Two of our famous Belgian Waffles with plenty of real maple syrup</meal:description>
+<meal:calories>650</meal:calories>     </meal:food></meal:breakfast_menu>');
+
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <!-- eat this --> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories>650</meal:calories> </meal:food></meal:breakfast_menu>');
+
+-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA
+SELECT xmlformat('<meal:breakfast_menu xmlns:meal="http://fancycafe.im/meal/"; xmlns:desc="http://fancycafe.mn/meal/"; id="73"> <meal:food type="organic" class="fancy"> <meal:name>Belgian Waffles</meal:name> <meal:price>$15.95</meal:price>
+                                 <desc:description>Two of our famous Belgian Waffles with plenty of real maple syrup</desc:description>
+<meal:calories><c><![CDATA[<unknown> &"<>!<a>foo</a>]]></c></meal:calories> </meal:food></meal:breakfast_menu>');
+
+-- XML format: NULL parameter
+SELECT xmlformat(NULL);
+
+-- XML format: empty string
+SELECT xmlformat('');
+
+-- XML format: invalid string (whitespaces)
+SELECT xmlformat('   ');
\ No newline at end of file
-- 
2.25.1

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to