From f503b25c7fd8d984d29536e78577741e5e7c5e9f Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Thu, 2 Feb 2023 21:27:16 +0100 Subject: [PATCH v5] 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 | 68 +++++++++++++++++++++ src/include/catalog/pg_proc.dat | 3 + src/test/regress/expected/xml.out | 93 +++++++++++++++++++++++++++++ src/test/regress/expected/xml_1.out | 45 ++++++++++++++ src/test/regress/sql/xml.sql | 27 +++++++++ 6 files changed, 270 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; ]]> + + + <literal>xmlpretty</literal> + + + xmlpretty + + + +xmlpretty ( xml ) xml + + + + Converts the given XML value to pretty-printed, indented text. + + + + Example: + 42'); + xmlpretty +-------------------------- + + + 42 + + + +(1 row) + +]]> + + + diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 079bcb1208..9c7f5c85cb 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -473,6 +473,74 @@ xmlBuffer_to_xmltype(xmlBufferPtr buf) } #endif +Datum +xmlpretty(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + + xmlDocPtr doc; + xmlChar *xmlbuf = NULL; + text *arg = PG_GETARG_TEXT_PP(0); + StringInfoData buf; + PgXmlErrorContext *xmlerrcxt; + + doc = xml_parse(arg, XMLOPTION_DOCUMENT, false, GetDatabaseEncoding(), NULL); + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + + int nbytes; + + /** + * 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); + + if(!nbytes || xmlerrcxt->err_occurred) { + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not indent the given XML document"); + } + + initStringInfo(&buf); + appendStringInfoString(&buf, (const char *)xmlbuf); + + } + PG_CATCH(); + { + + if(doc!=NULL) + xmlFreeDoc(doc); + if(xmlbuf!=NULL) + xmlFree(xmlbuf); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + + } + PG_END_TRY(); + + xmlFreeDoc(doc); + xmlFree(xmlbuf); + + pg_xml_done(xmlerrcxt, false); + + 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..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..afaa83941b 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1599,3 +1599,96 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH | <foo/> (1 row) +-- XML pretty print: single line XML string +SELECT xmlpretty('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650')::xml; + xmlpretty +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $5.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML pretty print: XML string with space, tabs and newline between nodes +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + xmlpretty +-------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML pretty print: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + xmlpretty +------------------------------------------------------------------------------------------------------------ + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup+ + 650 + + + + + + +(1 row) + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + xmlpretty +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + 650 + + + + + + +(1 row) + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> ')::xml; + xmlpretty +------------------------------------------------------------------------------------------------------------- + + + + + Belgian Waffles + + $15.95 + + Two of our famous Belgian Waffles with plenty of real maple syrup + + + + &"<>!foo]]> + + + + + + + + +(1 row) + +-- XML pretty print: NULL parameter +SELECT xmlpretty(NULL)::xml; + xmlpretty +----------- + +(1 row) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 378b412db0..aecec39e05 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -1268,3 +1268,48 @@ 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 pretty print: single line XML string +SELECT xmlpretty('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650')::xml; +ERROR: unsupported XML feature +LINE 1: SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; +ERROR: unsupported XML feature +LINE 1: SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; +ERROR: unsupported XML feature +LINE 1: SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; +ERROR: unsupported XML feature +LINE 1: SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> ')::xml; +ERROR: unsupported XML feature +LINE 1: SELECT xmlpretty('"', b xml PATH '""'); + + +-- XML pretty print: single line XML string +SELECT xmlpretty('Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650')::xml; + +-- XML pretty print: XML string with space, tabs and newline between nodes +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using a namespace +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and a comment +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup +650 ')::xml; + +-- XML pretty print: XML string with space, tabs and newline between nodes, using multiple namespaces and CDATA +SELECT xmlpretty(' Belgian Waffles $15.95 + Two of our famous Belgian Waffles with plenty of real maple syrup + &"<>!foo]]> ')::xml; + +-- XML pretty print: NULL parameter +SELECT xmlpretty(NULL)::xml; \ No newline at end of file -- 2.25.1