Move new version of contrib/ xml into xml2, keep old version in /xml.
authorBruce Momjian <bruce@momjian.us>
Fri, 5 Mar 2004 03:57:58 +0000 (03:57 +0000)
committerBruce Momjian <bruce@momjian.us>
Fri, 5 Mar 2004 03:57:58 +0000 (03:57 +0000)
contrib/README
contrib/xml/TODO [new file with mode: 0644]
contrib/xml/pgxml.c [new file with mode: 0644]
contrib/xml/pgxml.h [new file with mode: 0644]
contrib/xml/pgxml_dom.c [new file with mode: 0644]
contrib/xml/pgxml_dom.sql.in [new file with mode: 0644]
contrib/xml2/Makefile [moved from contrib/xml/Makefile with 100% similarity]
contrib/xml2/README.pgxml [moved from contrib/xml/README.pgxml with 100% similarity]
contrib/xml2/pgxml.sql.in [moved from contrib/xml/pgxml.sql.in with 100% similarity]
contrib/xml2/xpath.c [moved from contrib/xml/xpath.c with 100% similarity]
contrib/xml2/xslt_proc.c [moved from contrib/xml/xslt_proc.c with 100% similarity]

index a8a2c6c968b97e15f2314e050bf22446f2cabf9e..0071f43b8576ba2c0708f03efa79538246280d2d 100644 (file)
@@ -217,5 +217,9 @@ vacuumlo -
    by Peter T Mount <peter@retep.org.uk>
 
 xml -
+   Storing XML in PostgreSQL (obsolete version)
+   by John Gray <jgray@azuli.co.uk>
+
+xml2 -
    Storing XML in PostgreSQL
    by John Gray <jgray@azuli.co.uk>
diff --git a/contrib/xml/TODO b/contrib/xml/TODO
new file mode 100644 (file)
index 0000000..5ddd62a
--- /dev/null
@@ -0,0 +1,78 @@
+PGXML TODO List
+===============
+
+Some of these items still require much more thought! Since the first
+release, the XPath support has improved (because I'm no longer using a
+homemade algorithm!).
+
+1. Performance considerations
+
+At present each document is parsed to produce the DOM tree on every query.
+
+Pros: 
+   Easy
+   No persistent memory or storage allocation for parsed trees
+       (libxml docs suggest representation of a document might
+        be 4 times the size of the text)
+
+Cons:
+   Slow/ CPU intensive to parse.
+   Makes it difficult for PLs to apply libxml manipulations to create
+       new documents or amend existing ones.
+
+
+2. XQuery 
+
+I'm not sure if the addition of XQuery would be best as a function or
+as a new front-end parser. This is one to think about, but with a
+decent implementation of XPath, one of the prerequisites is covered.
+
+3. DOM Interfaces
+
+Expose more aspects of the DOM to user functions/ PLs. This would
+allow a procedure in a PL to run some queries and then use exposed
+interfaces to libxml to create an XML document out of the query
+results. I accept the argument that this might be more properly
+performed on the client side.
+
+4. Returning sets of documents from XPath queries.
+
+Although the current implementation allows you to amalgamate the
+returned results into a single document, it's quite possible that
+you'd like to use the returned set of nodes as a source for FROM.
+Is there a good way to optimise/index the results of certain XPath
+operations to make them faster?:
+
+select docid, pgxml_xpath(document,'//site/location/text()','','') as location 
+where pgxml_xpath(document,'//site/name/text()','','') = 'Church Farm';
+
+and with multiple element occurences in a document?
+
+select d.docid, pgxml_xpath(d.document,'//site/location/text()','','') 
+from docstore d, 
+pgxml_xpaths('docstore','document','//feature/type/text()','docid') ft 
+where ft.key = d.docid and ft.value ='Limekiln';
+
+pgxml_xpaths params are relname, attrname, xpath, returnkey. It would
+return a set of two-element tuples (key,value) consisting of the value of
+returnkey, and the cdata value of the xpath. The XML document would be
+defined by relname and attrname.
+
+The pgxml_xpaths function could be the basis of a functional index,
+which could speed up the above query very substantially, working
+through the normal query planner mechanism.
+
+5. Return type support.
+
+Better support for returning e.g. numeric or boolean values. I need to
+get to grips with the returned data from libxml first.
+
+John Gray <jgray@azuli.co.uk> 16 August 2001
+
+
+
+
+
+
diff --git a/contrib/xml/pgxml.c b/contrib/xml/pgxml.c
new file mode 100644 (file)
index 0000000..4d8c3b9
--- /dev/null
@@ -0,0 +1,352 @@
+/********************************************************
+ * Interface code to parse an XML document using expat
+ ********************************************************/
+
+#include "postgres.h"
+#include "fmgr.h"
+
+#include "expat.h"
+#include "pgxml.h"
+
+/* Memory management - we make expat use standard pg MM */
+
+XML_Memory_Handling_Suite mhs;
+
+/* passthrough functions (palloc is a macro) */
+
+static void *
+pgxml_palloc(size_t size)
+{
+   return palloc(size);
+}
+
+static void *
+pgxml_repalloc(void *ptr, size_t size)
+{
+   return repalloc(ptr, size);
+}
+
+static void
+pgxml_pfree(void *ptr)
+{
+   return pfree(ptr);
+}
+
+static void
+pgxml_mhs_init()
+{
+   mhs.malloc_fcn = pgxml_palloc;
+   mhs.realloc_fcn = pgxml_repalloc;
+   mhs.free_fcn = pgxml_pfree;
+}
+
+static void
+pgxml_handler_init()
+{
+   /*
+    * This code should set up the relevant handlers from  user-supplied
+    * settings. Quite how these settings are made is another matter :)
+    */
+}
+
+/* Returns true if document is well-formed */
+
+PG_FUNCTION_INFO_V1(pgxml_parse);
+
+Datum
+pgxml_parse(PG_FUNCTION_ARGS)
+{
+   /* called as pgxml_parse(document) */
+   XML_Parser  p;
+   text       *t = PG_GETARG_TEXT_P(0);        /* document buffer */
+   int32       docsize = VARSIZE(t) - VARHDRSZ;
+
+   pgxml_mhs_init();
+
+   pgxml_handler_init();
+
+   p = XML_ParserCreate_MM(NULL, &mhs, NULL);
+   if (!p)
+   {
+       ereport(ERROR,
+               (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+                errmsg("could not create expat parser")));
+       PG_RETURN_NULL();       /* seems appropriate if we couldn't parse */
+   }
+
+   if (!XML_Parse(p, (char *) VARDATA(t), docsize, 1))
+   {
+       /*
+        * elog(WARNING, "Parse error at line %d:%s",
+        * XML_GetCurrentLineNumber(p),
+        * XML_ErrorString(XML_GetErrorCode(p)));
+        */
+       XML_ParserFree(p);
+       PG_RETURN_BOOL(false);
+   }
+
+   XML_ParserFree(p);
+   PG_RETURN_BOOL(true);
+}
+
+/* XPath handling functions */
+
+/* XPath support here is for a very skeletal kind of XPath!
+   It was easy to program though... */
+
+/* This first is the core function that builds a result set. The
+   actual functions called by the user manipulate that result set
+   in various ways.
+*/
+
+static XPath_Results *
+build_xpath_results(text *doc, text *pathstr)
+{
+   XPath_Results *xpr;
+   char       *res;
+   pgxml_udata *udata;
+   XML_Parser  p;
+   int32       docsize;
+
+   xpr = (XPath_Results *) palloc((sizeof(XPath_Results)));
+   memset((void *) xpr, 0, sizeof(XPath_Results));
+   xpr->rescount = 0;
+
+   docsize = VARSIZE(doc) - VARHDRSZ;
+
+   /* res isn't going to be the real return type, it is just a buffer */
+
+   res = (char *) palloc(docsize);
+   memset((void *) res, 0, docsize);
+
+   xpr->resbuf = res;
+
+   udata = (pgxml_udata *) palloc((sizeof(pgxml_udata)));
+   memset((void *) udata, 0, sizeof(pgxml_udata));
+
+   udata->currentpath[0] = '\0';
+   udata->textgrab = 0;
+
+   udata->path = (char *) palloc(VARSIZE(pathstr));
+   memcpy(udata->path, VARDATA(pathstr), VARSIZE(pathstr) - VARHDRSZ);
+
+   udata->path[VARSIZE(pathstr) - VARHDRSZ] = '\0';
+
+   udata->resptr = res;
+   udata->reslen = 0;
+
+   udata->xpres = xpr;
+
+   /* Now fire up the parser */
+   pgxml_mhs_init();
+
+   p = XML_ParserCreate_MM(NULL, &mhs, NULL);
+   if (!p)
+   {
+       ereport(ERROR,
+               (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+                errmsg("could not create expat parser")));
+       pfree(xpr);
+       pfree(udata->path);
+       pfree(udata);
+       pfree(res);
+       return NULL;
+   }
+   XML_SetUserData(p, (void *) udata);
+
+   /* Set the handlers */
+
+   XML_SetElementHandler(p, pgxml_starthandler, pgxml_endhandler);
+   XML_SetCharacterDataHandler(p, pgxml_charhandler);
+
+   if (!XML_Parse(p, (char *) VARDATA(doc), docsize, 1))
+   {
+       /*
+        * elog(WARNING, "Parse error at line %d:%s",
+        * XML_GetCurrentLineNumber(p),
+        * XML_ErrorString(XML_GetErrorCode(p)));
+        */
+       XML_ParserFree(p);
+       pfree(xpr);
+       pfree(udata->path);
+       pfree(udata);
+
+       return NULL;
+   }
+
+   pfree(udata->path);
+   pfree(udata);
+   XML_ParserFree(p);
+   return xpr;
+}
+
+
+PG_FUNCTION_INFO_V1(pgxml_xpath);
+
+Datum
+pgxml_xpath(PG_FUNCTION_ARGS)
+{
+   /* called as pgxml_xpath(document,pathstr, index) for the moment */
+
+   XPath_Results *xpresults;
+   text       *restext;
+
+   text       *t = PG_GETARG_TEXT_P(0);        /* document buffer */
+   text       *t2 = PG_GETARG_TEXT_P(1);
+   int32       ind = PG_GETARG_INT32(2) - 1;
+
+   xpresults = build_xpath_results(t, t2);
+
+   /*
+    * This needs to be changed depending on the mechanism for returning
+    * our set of results.
+    */
+
+   if (xpresults == NULL)      /* parse error (not WF or parser failure) */
+       PG_RETURN_NULL();
+
+   if (ind >= (xpresults->rescount))
+       PG_RETURN_NULL();
+
+   restext = (text *) palloc(xpresults->reslens[ind] + VARHDRSZ);
+   memcpy(VARDATA(restext), xpresults->results[ind], xpresults->reslens[ind]);
+
+   VARATT_SIZEP(restext) = xpresults->reslens[ind] + VARHDRSZ;
+
+   pfree(xpresults->resbuf);
+   pfree(xpresults);
+
+   PG_RETURN_TEXT_P(restext);
+}
+
+
+static void
+pgxml_pathcompare(void *userData)
+{
+   char       *matchpos;
+
+   matchpos = strstr(UD->currentpath, UD->path);
+
+   if (matchpos == NULL)
+   {                           /* Should we have more logic here ? */
+       if (UD->textgrab)
+       {
+           UD->textgrab = 0;
+           pgxml_finalisegrabbedtext(userData);
+       }
+       return;
+   }
+
+   /*
+    * OK, we have a match of some sort. Now we need to check that our
+    * match is anchored to the *end* of the string AND that it is
+    * immediately preceded by a '/'
+    */
+
+   /*
+    * This test wouldn't work if strlen (UD->path) overran the length of
+    * the currentpath, but that's not possible because we got a match!
+    */
+
+   if ((matchpos + strlen(UD->path))[0] == '\0')
+   {
+       if ((UD->path)[0] == '/')
+       {
+           if (matchpos == UD->currentpath)
+               UD->textgrab = 1;
+       }
+       else
+       {
+           if ((matchpos - 1)[0] == '/')
+               UD->textgrab = 1;
+       }
+   }
+}
+
+static void
+pgxml_starthandler(void *userData, const XML_Char * name,
+                  const XML_Char ** atts)
+{
+
+   char        sepstr[] = "/";
+
+   if ((strlen(name) + strlen(UD->currentpath)) > MAXPATHLENGTH - 2)
+       elog(WARNING, "path too long");
+   else
+   {
+       strncat(UD->currentpath, sepstr, 1);
+       strcat(UD->currentpath, name);
+   }
+   if (UD->textgrab)
+   {
+       /*
+        * Depending on user preference, should we "reconstitute" the
+        * element into the result text?
+        */
+   }
+   else
+       pgxml_pathcompare(userData);
+}
+
+static void
+pgxml_endhandler(void *userData, const XML_Char * name)
+{
+   /*
+    * Start by removing the current element off the end of the
+    * currentpath
+    */
+
+   char       *sepptr;
+
+   sepptr = strrchr(UD->currentpath, '/');
+   if (sepptr == NULL)
+   {
+       /* internal error */
+       elog(ERROR, "did not find '/'");
+       sepptr = UD->currentpath;
+   }
+   if (strcmp(name, sepptr + 1) != 0)
+   {
+       elog(WARNING, "wanted [%s], got [%s]", sepptr, name);
+       /* unmatched entry, so do nothing */
+   }
+   else
+   {
+       sepptr[0] = '\0';       /* Chop that element off the end */
+   }
+
+   if (UD->textgrab)
+       pgxml_pathcompare(userData);
+
+}
+
+static void
+pgxml_charhandler(void *userData, const XML_Char * s, int len)
+{
+   if (UD->textgrab)
+   {
+       if (len > 0)
+       {
+           memcpy(UD->resptr, s, len);
+           UD->resptr += len;
+           UD->reslen += len;
+       }
+   }
+}
+
+/* Should I be using PG list types here? */
+
+static void
+pgxml_finalisegrabbedtext(void *userData)
+{
+   /* In res/reslen, we have a single result. */
+   UD->xpres->results[UD->xpres->rescount] = UD->resptr - UD->reslen;
+   UD->xpres->reslens[UD->xpres->rescount] = UD->reslen;
+   UD->reslen = 0;
+   UD->xpres->rescount++;
+
+   /*
+    * This effectively concatenates all the results together but we do
+    * know where one ends and the next begins
+    */
+}
diff --git a/contrib/xml/pgxml.h b/contrib/xml/pgxml.h
new file mode 100644 (file)
index 0000000..2b80124
--- /dev/null
@@ -0,0 +1,42 @@
+/* Header for pg xml parser interface */
+
+static void *pgxml_palloc(size_t size);
+static void *pgxml_repalloc(void *ptr, size_t size);
+static void pgxml_pfree(void *ptr);
+static void pgxml_mhs_init();
+static void pgxml_handler_init();
+Datum      pgxml_parse(PG_FUNCTION_ARGS);
+Datum      pgxml_xpath(PG_FUNCTION_ARGS);
+static void pgxml_starthandler(void *userData, const XML_Char * name,
+                  const XML_Char ** atts);
+static void pgxml_endhandler(void *userData, const XML_Char * name);
+static void pgxml_charhandler(void *userData, const XML_Char * s, int len);
+static void pgxml_pathcompare(void *userData);
+static void pgxml_finalisegrabbedtext(void *userData);
+
+#define MAXPATHLENGTH 512
+#define MAXRESULTS 100
+
+
+typedef struct
+{
+   int         rescount;
+   char       *results[MAXRESULTS];
+   int32       reslens[MAXRESULTS];
+   char       *resbuf;         /* pointer to the result buffer for pfree */
+}  XPath_Results;
+
+
+
+typedef struct
+{
+   char        currentpath[MAXPATHLENGTH];
+   char       *path;
+   int         textgrab;
+   char       *resptr;
+   int32       reslen;
+   XPath_Results *xpres;
+}  pgxml_udata;
+
+
+#define UD ((pgxml_udata *) userData)
diff --git a/contrib/xml/pgxml_dom.c b/contrib/xml/pgxml_dom.c
new file mode 100644 (file)
index 0000000..2b11b1d
--- /dev/null
@@ -0,0 +1,265 @@
+/* Parser interface for DOM-based parser (libxml) rather than
+   stream-based SAX-type parser */
+
+#include "postgres.h"
+#include "fmgr.h"
+
+/* libxml includes */
+
+#include <libxml/xpath.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+
+/* declarations */
+
+static void *pgxml_palloc(size_t size);
+static void *pgxml_repalloc(void *ptr, size_t size);
+static void pgxml_pfree(void *ptr);
+static char *pgxml_pstrdup(const char *string);
+
+static void pgxml_parser_init();
+
+static xmlChar *pgxmlNodeSetToText(xmlNodeSetPtr nodeset, xmlDocPtr doc,
+                  xmlChar * toptagname, xmlChar * septagname,
+                  int format);
+
+static xmlChar *pgxml_texttoxmlchar(text *textstring);
+
+
+Datum      pgxml_parse(PG_FUNCTION_ARGS);
+Datum      pgxml_xpath(PG_FUNCTION_ARGS);
+
+/* memory handling passthrough functions (e.g. palloc, pstrdup are
+   currently macros, and the others might become so...) */
+
+static void *
+pgxml_palloc(size_t size)
+{
+   return palloc(size);
+}
+
+static void *
+pgxml_repalloc(void *ptr, size_t size)
+{
+   return repalloc(ptr, size);
+}
+
+static void
+pgxml_pfree(void *ptr)
+{
+   return pfree(ptr);
+}
+
+static char *
+pgxml_pstrdup(const char *string)
+{
+   return pstrdup(string);
+}
+
+static void
+pgxml_parser_init()
+{
+   /*
+    * This code should also set parser settings from  user-supplied info.
+    * Quite how these settings are made is another matter :)
+    */
+
+   xmlMemSetup(pgxml_pfree, pgxml_palloc, pgxml_repalloc, pgxml_pstrdup);
+   xmlInitParser();
+
+}
+
+
+/* Returns true if document is well-formed */
+
+PG_FUNCTION_INFO_V1(pgxml_parse);
+
+Datum
+pgxml_parse(PG_FUNCTION_ARGS)
+{
+   /* called as pgxml_parse(document) */
+   xmlDocPtr   doctree;
+   text       *t = PG_GETARG_TEXT_P(0);        /* document buffer */
+   int32       docsize = VARSIZE(t) - VARHDRSZ;
+
+   pgxml_parser_init();
+
+   doctree = xmlParseMemory((char *) VARDATA(t), docsize);
+   if (doctree == NULL)
+   {
+       xmlCleanupParser();
+       PG_RETURN_BOOL(false);  /* i.e. not well-formed */
+   }
+   xmlCleanupParser();
+   xmlFreeDoc(doctree);
+   PG_RETURN_BOOL(true);
+}
+
+static xmlChar
+*
+pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
+                  xmlDocPtr doc,
+                  xmlChar * toptagname,
+                  xmlChar * septagname,
+                  int format)
+{
+   /* Function translates a nodeset into a text representation */
+
+   /*
+    * iterates over each node in the set and calls xmlNodeDump to write
+    * it to an xmlBuffer -from which an xmlChar * string is returned.
+    */
+   /* each representation is surrounded by <tagname> ... </tagname> */
+   /* if format==0, add a newline between nodes?? */
+
+   xmlBufferPtr buf;
+   xmlChar    *result;
+   int         i;
+
+   buf = xmlBufferCreate();
+
+   if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
+   {
+       xmlBufferWriteChar(buf, "<");
+       xmlBufferWriteCHAR(buf, toptagname);
+       xmlBufferWriteChar(buf, ">");
+   }
+   if (nodeset != NULL)
+   {
+       for (i = 0; i < nodeset->nodeNr; i++)
+       {
+           if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
+           {
+               xmlBufferWriteChar(buf, "<");
+               xmlBufferWriteCHAR(buf, septagname);
+               xmlBufferWriteChar(buf, ">");
+           }
+           xmlNodeDump(buf, doc, nodeset->nodeTab[i], 1, (format == 2));
+
+           if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
+           {
+               xmlBufferWriteChar(buf, "</");
+               xmlBufferWriteCHAR(buf, septagname);
+               xmlBufferWriteChar(buf, ">");
+           }
+           if (format)
+               xmlBufferWriteChar(buf, "\n");
+       }
+   }
+
+   if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
+   {
+       xmlBufferWriteChar(buf, "</");
+       xmlBufferWriteCHAR(buf, toptagname);
+       xmlBufferWriteChar(buf, ">");
+   }
+   result = xmlStrdup(buf->content);
+   xmlBufferFree(buf);
+   return result;
+}
+
+static xmlChar *
+pgxml_texttoxmlchar(text *textstring)
+{
+   xmlChar    *res;
+   int32       txsize;
+
+   txsize = VARSIZE(textstring) - VARHDRSZ;
+   res = (xmlChar *) palloc(txsize + 1);
+   memcpy((char *) res, VARDATA(textstring), txsize);
+   res[txsize] = '\0';
+   return res;
+}
+
+
+PG_FUNCTION_INFO_V1(pgxml_xpath);
+
+Datum
+pgxml_xpath(PG_FUNCTION_ARGS)
+{
+   xmlDocPtr   doctree;
+   xmlXPathContextPtr ctxt;
+   xmlXPathObjectPtr res;
+   xmlChar    *xpath,
+              *xpresstr,
+              *toptag,
+              *septag;
+   xmlXPathCompExprPtr comppath;
+
+   int32       docsize,
+               ressize;
+   text       *t,
+              *xpres;
+
+   t = PG_GETARG_TEXT_P(0);    /* document buffer */
+   xpath = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(1));   /* XPath expression */
+   toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(2));
+   septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(3));
+
+   docsize = VARSIZE(t) - VARHDRSZ;
+
+   pgxml_parser_init();
+
+   doctree = xmlParseMemory((char *) VARDATA(t), docsize);
+   if (doctree == NULL)
+   {                           /* not well-formed */
+       xmlCleanupParser();
+       PG_RETURN_NULL();
+   }
+
+   ctxt = xmlXPathNewContext(doctree);
+   ctxt->node = xmlDocGetRootElement(doctree);
+
+   /* compile the path */
+   comppath = xmlXPathCompile(xpath);
+   if (comppath == NULL)
+   {
+       elog(WARNING, "XPath syntax error");
+       xmlFreeDoc(doctree);
+       pfree((void *) xpath);
+       xmlCleanupParser();
+       PG_RETURN_NULL();
+   }
+
+   /* Now evaluate the path expression. */
+   res = xmlXPathCompiledEval(comppath, ctxt);
+   xmlXPathFreeCompExpr(comppath);
+
+   if (res == NULL)
+   {
+       xmlFreeDoc(doctree);
+       pfree((void *) xpath);
+       xmlCleanupParser();
+       PG_RETURN_NULL();       /* seems appropriate */
+   }
+   /* now we dump this node, ?surrounding by tags? */
+   /* To do this, we look first at the type */
+   switch (res->type)
+   {
+       case XPATH_NODESET:
+           xpresstr = pgxmlNodeSetToText(res->nodesetval,
+                                         doctree,
+                                         toptag, septag, 0);
+           break;
+       case XPATH_STRING:
+           xpresstr = xmlStrdup(res->stringval);
+           break;
+       default:
+           elog(WARNING, "Unsupported XQuery result: %d", res->type);
+           xpresstr = xmlStrdup("<unsupported/>");
+   }
+
+
+   /* Now convert this result back to text */
+   ressize = strlen(xpresstr);
+   xpres = (text *) palloc(ressize + VARHDRSZ);
+   memcpy(VARDATA(xpres), xpresstr, ressize);
+   VARATT_SIZEP(xpres) = ressize + VARHDRSZ;
+
+   /* Free various storage */
+   xmlFreeDoc(doctree);
+   pfree((void *) xpath);
+   xmlFree(xpresstr);
+   xmlCleanupParser();
+   PG_RETURN_TEXT_P(xpres);
+}
diff --git a/contrib/xml/pgxml_dom.sql.in b/contrib/xml/pgxml_dom.sql.in
new file mode 100644 (file)
index 0000000..514643b
--- /dev/null
@@ -0,0 +1,10 @@
+-- SQL for XML parser
+
+-- Adjust this setting to control where the objects get created.
+SET search_path TO public;
+
+CREATE OR REPLACE FUNCTION pgxml_parse(text) RETURNS boolean
+    AS 'MODULE_PATHNAME' LANGUAGE c STRICT;
+
+CREATE OR REPLACE FUNCTION pgxml_xpath(text, text, text, text) RETURNS text
+    AS 'MODULE_PATHNAME' LANGUAGE c STRICT;
similarity index 100%
rename from contrib/xml/Makefile
rename to contrib/xml2/Makefile
similarity index 100%
rename from contrib/xml/xpath.c
rename to contrib/xml2/xpath.c