diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 5fa5f0b..2dbab27 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -110,6 +110,8 @@ static int parse_xml_decl(const xmlChar * str, size_t *lenp, xmlChar ** version, xmlChar ** encoding, int *standalone); static bool print_xml_decl(StringInfo buf, const xmlChar * version, pg_enc encoding, int standalone); +static xmlDocPtr xml_parse_with_nodes(text *data, XmlOptionType xmloption_arg, + bool preserve_whitespace, xmlChar * encoding, xmlNodePtr *chunks); static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, xmlChar * encoding); static text *xml_xmlnodetoxmltype(xmlNodePtr cur); @@ -1125,8 +1127,8 @@ print_xml_decl(StringInfo buf, const xmlChar * version, * yet do not use SAX - see xmlreader.c) */ static xmlDocPtr -xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, - xmlChar * encoding) +xml_parse_with_nodes(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, + xmlChar * encoding, xmlNodePtr *chunks) { int32 len; xmlChar *string; @@ -1185,7 +1187,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, res_code); res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, - utf8string + count, NULL); + utf8string + count, chunks); if (res_code != 0) xml_ereport(ERROR, ERRCODE_INVALID_XML_CONTENT, "invalid XML content"); @@ -1200,6 +1202,15 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, return doc; } +static xmlDocPtr +xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, + xmlChar * encoding) +{ + return xml_parse_with_nodes( + data, xmloption_arg, preserve_whitespace, encoding, NULL + ); +} + /* * xmlChar<->text conversions @@ -3174,6 +3185,30 @@ xml_xmlnodetoxmltype(xmlNodePtr cur) } #endif +/* + * Provide access to the document's content fragment. + */ +static void +xpathfun_fragment(xmlXPathParserContextPtr ctxt, int nargs) +{ + xmlNodeSetPtr nset = NULL; + xmlNodePtr cur; + + if (nargs != 0) + { + xmlXPathSetArityError(ctxt); + return; + } + + cur = ctxt->context->node; + while (cur != NULL) + { + nset = xmlXPathNodeSetMerge(nset, xmlXPathNodeSetCreate(cur)); + cur = cur->next; + } + + valuePush(ctxt, xmlXPathWrapNodeSet(nset)); +} /* * Evaluate XPath expression and return array of XML values. @@ -3193,15 +3228,12 @@ xpath(PG_FUNCTION_ARGS) xmltype *data = PG_GETARG_XML_P(1); ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); ArrayBuildState *astate = NULL; - xmlParserCtxtPtr ctxt; xmlDocPtr doc; + xmlNodePtr chunks; xmlXPathContextPtr xpathctx; xmlXPathCompExprPtr xpathcomp; xmlXPathObjectPtr xpathobj; - char *datastr; - int32 len; int32 xpath_len; - xmlChar *string; xmlChar *xpath_expr; int i; int res_nitems; @@ -3248,51 +3280,12 @@ xpath(PG_FUNCTION_ARGS) ns_count = 0; } - datastr = VARDATA(data); - len = VARSIZE(data) - VARHDRSZ; - xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ; - if (xpath_len == 0) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("empty XPath expression"))); - xml_init(); - /* - * To handle both documents and fragments, regardless of the fact whether - * the XML datum has a single root (XML well-formedness), we wrap the XML - * datum in a dummy element (...) and extend the XPath expression - * accordingly. To do it, throw away the XML prolog, if any. - */ - if (len >= 5 && - xmlStrncmp((xmlChar *) datastr, (xmlChar *) "')) - i++; - - if (i == len) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not parse XML data"); - - ++i; - - datastr += i; - len -= i; - } - - string = (xmlChar *) palloc((len + 8) * sizeof(xmlChar)); - memcpy(string, "", 3); - memcpy(string + 3, datastr, len); - memcpy(string + 3 + len, "", 5); - len += 7; - - xpath_expr = (xmlChar *) palloc((xpath_len + 3) * sizeof(xmlChar)); - memcpy(xpath_expr, "/x", 2); - memcpy(xpath_expr + 2, VARDATA(xpath_expr_text), xpath_len); - xpath_expr[xpath_len + 2] = '\0'; - xpath_len += 2; + xpath_len = VARSIZE(xpath_expr_text) - VARHDRSZ; + xpath_expr = (xmlChar *) palloc((xpath_len + 1) * sizeof(xmlChar)); + memcpy(xpath_expr, VARDATA(xpath_expr_text), xpath_len); + xpath_expr[xpath_len] = '\0'; xmlInitParser(); @@ -3300,19 +3293,19 @@ xpath(PG_FUNCTION_ARGS) * redundant XML parsing (two parsings for the same value during one * command execution are possible) */ - ctxt = xmlNewParserCtxt(); - if (ctxt == NULL) - xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, - "could not allocate parser context"); - doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0); + doc = xml_parse_with_nodes(data, xmloption, true, NULL, &chunks); if (doc == NULL) xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, "could not parse XML data"); + xpathctx = xmlXPathNewContext(doc); if (xpathctx == NULL) xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, "could not allocate XPath context"); - xpathctx->node = xmlDocGetRootElement(doc); + + xmlXPathRegisterFunc(xpathctx, (xmlChar *) "fragment", (xmlXPathFunction) xpathfun_fragment); + + xpathctx->node = chunks; if (xpathctx->node == NULL) xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, "could not find root XML element"); @@ -3376,7 +3369,6 @@ xpath(PG_FUNCTION_ARGS) xmlXPathFreeObject(xpathobj); xmlXPathFreeContext(xpathctx); xmlFreeDoc(doc); - xmlFreeParserCtxt(ctxt); if (res_nitems == 0) PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID));