Hi,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.
postgres=# SELECT xmlpretty('<foo id="x"><bar id="y"><var id="z">42</var></bar></foo>');
xmlpretty -------------------------- <foo id="x"> + <bar id="y"> + <var id="z">42</var>+ </bar> + </foo> + (1 row) The patch also contains regression tests and documentation. Feedback is very welcome! Jim
From ced9fccddc033de98709a6e93dc6530ce68149db 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 v1] 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 | 30 +++++++++ src/include/catalog/pg_proc.dat | 3 + src/test/regress/expected/xml.out | 107 ++++++++++++++++++++++++++++++ src/test/regress/sql/xml.sql | 34 ++++++++++ 5 files changed, 208 insertions(+) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e09e289a43..e8b5e581f0 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -14293,6 +14293,40 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab; ]]></screen> </para> </sect3> + + <sect3 id="functions-xml-xmlpretty"> + <title><literal>xmlpretty</literal></title> + + <indexterm> + <primary>xmlpretty</primary> + </indexterm> + +<synopsis> +<function>xmlpretty</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 xmlpretty('<foo id="x"><bar id="y"><var id="z">42</var></bar></foo>'); + xmlpretty +-------------------------- + <foo id="x"> + <bar id="y"> + <var id="z">42</var> + </bar> + </foo> + +(1 row) + +]]></screen> + </para> + </sect3> + </sect2> <sect2 id="functions-xml-predicates"> diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 079bcb1208..6409133137 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -473,6 +473,36 @@ xmlBuffer_to_xmltype(xmlBufferPtr buf) } #endif +Datum +xmlpretty(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + + xmlDocPtr doc; + xmlChar *buf = NULL; + text *arg = PG_GETARG_TEXT_PP(0); + + doc = xml_parse(arg, XMLOPTION_DOCUMENT, false, GetDatabaseEncoding(), NULL); + + /** + * xmlDocDumpFormatMemory ( + * xmlDocPtr doc, # the XML document. + * xmlChar ** buf, # buffer where the formatted XML document will be stored. + * int *size, # this could store the size of the created buffer + * but as we do not need it, we can leave it NULL. + * int format) # 1 = node indenting. + */ + xmlDocDumpFormatMemory(doc, &buf, NULL, 1); + + xmlFreeDoc(doc); + PG_RETURN_XML_P(cstring_to_xmltype((char*)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..3224dc3e76 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 => 'xmlpretty', prorettype => 'xml', + proargtypes => 'xml', prosrc => 'xmlpretty' }, # json { oid => '321', descr => 'I/O', diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 3c357a9c7e..98a338ad8d 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1599,3 +1599,110 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH <foo/> | <foo/> (1 row) +-- XML pretty print: single line XML string +SELECT xmlpretty('<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; + xmlpretty +-------------------------------------------------------------------------------------------------- + <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 pretty print: XML string with space, tabs and newline between nodes +SELECT xmlpretty('<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; + xmlpretty +-------------------------------------------------------------------------------------------------- + <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 pretty print: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlpretty('<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; + xmlpretty +------------------------------------------------------------------------------------------------------------ + <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 pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlpretty('<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; + xmlpretty +------------------------------------------------------------------------------------------------------------- + <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 pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlpretty('<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; + xmlpretty +------------------------------------------------------------------------------------------------------------- + <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 pretty print: invalid XML string (not well balanced) +SELECT xmlpretty('<foo>')::xml; +ERROR: invalid XML content +LINE 1: SELECT xmlpretty('<foo>')::xml; + ^ +DETAIL: line 1: chunk is not well balanced +<foo> + ^ +-- XML pretty print: invalid parameter +SELECT xmlpretty(42)::xml; +ERROR: function xmlpretty(integer) does not exist +LINE 1: SELECT xmlpretty(42)::xml; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- XML pretty print: NULL parameter +SELECT xmlpretty(NULL)::xml; + xmlpretty +----------- + +(1 row) + diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index ddff459297..2b40c90966 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -624,3 +624,37 @@ 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 pretty print: single line XML string +SELECT xmlpretty('<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; + +-- XML pretty print: XML string with space, tabs and newline between nodes +SELECT xmlpretty('<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; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlpretty('<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; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlpretty('<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; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlpretty('<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; + +-- XML pretty print: invalid XML string (not well balanced) +SELECT xmlpretty('<foo>')::xml; + +-- XML pretty print: invalid parameter +SELECT xmlpretty(42)::xml; + +-- XML pretty print: NULL parameter +SELECT xmlpretty(NULL)::xml; + -- 2.25.1
smime.p7s
Description: S/MIME Cryptographic Signature