From 835c9ec18255adce9f9ae1e1e5d9e4287bac5452 Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Thu, 2 Feb 2023 21:27:16 +0100 Subject: [PATCH v10] 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/expected/xml_2.out | 105 +++++++++++++++++++++++++++ src/test/regress/sql/xml.sql | 32 +++++++++ 7 files changed, 384 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.* ]]> + + + <literal>xmlformat</literal> + + + xmlformat + + + +xmlformat ( xml ) xml + + + + Converts the given XML value to pretty-printed, indented text. + + + + Example: + 42'); + xmlformat +-------------------------- + + + 42 + + + +(1 row) + +]]> + + + 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..8bc8919092 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/> (1 row) +-- XML format: single line XML string +SELECT xmlformat('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650'); + xmlformat +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $5.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +------------------------------------------------------------------------------------------------------------ + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> '); + xmlformat +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + + + &"<>!foo]]> + + + + + + + + +(1 row) + +-- XML format: NULL parameter +SELECT xmlformat(NULL); + xmlformat +----------- + +(1 row) + +-- XML format: invalid string (whitespaces) +SELECT xmlformat(' '); +ERROR: invalid XML document +DETAIL: line 1: Start tag expected, '<' not found + + ^ +-- XML format: empty string +SELECT xmlformat(''); +ERROR: invalid XML document +DETAIL: line 1: switching encoding : no input + +^ +line 1: Document is empty + +^ diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 378b412db0..79c4721f4b 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 '""', b xml PATH '""'); ERROR: unsupported XML feature DETAIL: This functionality requires the server to be built with libxml support. +-- XML format: single line XML string +SELECT xmlformat('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650'); +ERROR: unsupported XML feature +LINE 1: SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); +ERROR: unsupported XML feature +LINE 1: SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); +ERROR: unsupported XML feature +LINE 1: SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); +ERROR: unsupported XML feature +LINE 1: SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> '); +ERROR: unsupported XML feature +LINE 1: SELECT xmlformat('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650'); + xmlformat +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $5.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +------------------------------------------------------------------------------------------------------------ + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + xmlformat +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + 650 + + + + + + +(1 row) + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> '); + xmlformat +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + + + &"<>!foo]]> + + + + + + + + +(1 row) + +-- XML format: NULL parameter +SELECT xmlformat(NULL); + xmlformat +----------- + +(1 row) + +-- XML format: invalid string (whitespaces) +SELECT xmlformat(' '); +ERROR: invalid XML document +DETAIL: line 1: Start tag expected, '<' not found + + ^ +-- XML format: empty string +SELECT xmlformat(''); +ERROR: invalid XML document +DETAIL: line 1: Document is empty + +^ \ No newline at end of file diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index ddff459297..19c5b9d7a4 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -624,3 +624,35 @@ SELECT * FROM XMLTABLE('*' PASSING 'pre"', b xml PATH '""'); + +-- XML format: single line XML string +SELECT xmlformat('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650'); + +-- XML format: XML string with space, tabs and newline between nodes +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + +-- XML format: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 '); + +-- XML format: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlformat(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> '); + +-- XML format: NULL parameter +SELECT xmlformat(NULL); + +-- XML format: invalid string (whitespaces) +SELECT xmlformat(' '); + +-- XML format: empty string +SELECT xmlformat(''); \ No newline at end of file -- 2.25.1