From ced9fccddc033de98709a6e93dc6530ce68149db Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Thu, 2 Feb 2023 21:27:16 +0100 Subject: [PATCH v3 1/3] 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; ]]> + + + <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..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/> (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: invalid XML string (not well balanced) +SELECT xmlpretty('')::xml; +ERROR: invalid XML content +LINE 1: SELECT xmlpretty('')::xml; + ^ +DETAIL: line 1: chunk is not well balanced + + ^ +-- 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 'pre"', 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: invalid XML string (not well balanced) +SELECT xmlpretty('')::xml; + +-- XML pretty print: invalid parameter +SELECT xmlpretty(42)::xml; + +-- XML pretty print: NULL parameter +SELECT xmlpretty(NULL)::xml; + -- 2.25.1 From ceb24fcbc55e94a69968432f7a0d93e9e240cd2d Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Fri, 3 Feb 2023 07:48:42 +0100 Subject: [PATCH v3 2/3] Remove unecessary regression tests The removed removed tests (corner cases) were unnecessray and were causing the cfbot to fail, as the system is delivering different error messages in linux (chunk is not well balanced) and windows / macos (Premature end of data in tag foo line 1). --- src/test/regress/expected/xml.out | 14 -------------- src/test/regress/sql/xml.sql | 9 +-------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 98a338ad8d..afaa83941b 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1685,20 +1685,6 @@ SELECT xmlpretty('')::xml; -ERROR: invalid XML content -LINE 1: SELECT xmlpretty('')::xml; - ^ -DETAIL: line 1: chunk is not well balanced - - ^ --- 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 diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 2b40c90966..6e9a7b2295 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -649,12 +649,5 @@ SELECT xmlpretty('Two of our famous Belgian Waffles with plenty of real maple syrup &"<>!foo]]> ')::xml; --- XML pretty print: invalid XML string (not well balanced) -SELECT xmlpretty('')::xml; - --- XML pretty print: invalid parameter -SELECT xmlpretty(42)::xml; - -- XML pretty print: NULL parameter -SELECT xmlpretty(NULL)::xml; - +SELECT xmlpretty(NULL)::xml; \ No newline at end of file -- 2.25.1 From f2b5a722c7ff3d7aa41ff20ae146af2477e590da Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Mon, 6 Feb 2023 16:51:13 +0100 Subject: [PATCH v3 3/3] Add missing xmlFree call for xml buffer Indented xml string now stored in a StringInfoData and xmlChar* buffer is properly freed. --- src/backend/utils/adt/xml.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 6409133137..4b6a9fde01 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -479,8 +479,9 @@ xmlpretty(PG_FUNCTION_ARGS) #ifdef USE_LIBXML xmlDocPtr doc; - xmlChar *buf = NULL; + xmlChar *xmlbuf = NULL; text *arg = PG_GETARG_TEXT_PP(0); + StringInfoData buf; doc = xml_parse(arg, XMLOPTION_DOCUMENT, false, GetDatabaseEncoding(), NULL); @@ -492,10 +493,15 @@ xmlpretty(PG_FUNCTION_ARGS) * but as we do not need it, we can leave it NULL. * int format) # 1 = node indenting. */ - xmlDocDumpFormatMemory(doc, &buf, NULL, 1); + xmlDocDumpFormatMemory(doc, &xmlbuf, NULL, 1); - xmlFreeDoc(doc); - PG_RETURN_XML_P(cstring_to_xmltype((char*)buf)); + initStringInfo(&buf); + appendStringInfoString(&buf, (char*)xmlbuf); + + xmlFree(xmlbuf); + xmlFreeDoc(doc); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&buf)); #else NO_XML_SUPPORT(); -- 2.25.1