OSDN Git Service

Update CVS HEAD for 2007 copyright. Back branches are typically not
[pg-rex/syncrep.git] / src / backend / utils / adt / xml.c
1 /*-------------------------------------------------------------------------
2  *
3  * xml.c
4  *        XML data type support.
5  *
6  *
7  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.10 2007/01/05 22:19:42 momjian Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14
15 /*
16  * Generally, XML type support is only available when libxml use was
17  * configured during the build.  But even if that is not done, the
18  * type and all the functions are available, but most of them will
19  * fail.  For one thing, this avoids having to manage variant catalog
20  * installations.  But it also has nice effects such as that you can
21  * dump a database containing XML type data even if the server is not
22  * linked with libxml.  Thus, make sure xml_out() works even if nothing
23  * else does.
24  */
25
26 #include "postgres.h"
27
28 #ifdef USE_LIBXML
29 #include <libxml/chvalid.h>
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32 #include <libxml/uri.h>
33 #include <libxml/xmlerror.h>
34 #endif /* USE_LIBXML */
35
36 #include "fmgr.h"
37 #include "libpq/pqformat.h"
38 #include "mb/pg_wchar.h"
39 #include "nodes/execnodes.h"
40 #include "utils/builtins.h"
41 #include "utils/memutils.h"
42 #include "utils/xml.h"
43
44
45 #ifdef USE_LIBXML
46
47 #define PG_XML_DEFAULT_URI "dummy.xml"
48
49 static StringInfo xml_err_buf = NULL;
50
51 static void     xml_init(void);
52 static void    *xml_palloc(size_t size);
53 static void    *xml_repalloc(void *ptr, size_t size);
54 static void     xml_pfree(void *ptr);
55 static char    *xml_pstrdup(const char *string);
56 static void     xml_ereport(int level, int sqlcode,
57                                                         const char *msg, void *ctxt);
58 static void     xml_errorHandler(void *ctxt, const char *msg, ...);
59 static void     xml_ereport_by_code(int level, int sqlcode,
60                                                                         const char *msg, int errcode);
61 static xmlChar *xml_text2xmlChar(text *in);
62 static xmlDocPtr xml_parse(text *data, bool is_document, bool preserve_whitespace);
63
64 #endif /* USE_LIBXML */
65
66 #define NO_XML_SUPPORT() \
67         ereport(ERROR, \
68                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
69                          errmsg("no XML support in this installation")))
70
71
72 Datum
73 xml_in(PG_FUNCTION_ARGS)
74 {
75 #ifdef USE_LIBXML
76         char            *s = PG_GETARG_CSTRING(0);
77         size_t          len;
78         xmltype         *vardata;
79
80         len = strlen(s);
81         vardata = palloc(len + VARHDRSZ);
82         VARATT_SIZEP(vardata) = len + VARHDRSZ;
83         memcpy(VARDATA(vardata), s, len);
84
85         /*
86          * Parse the data to check if it is well-formed XML data.  Assume
87          * that ERROR occurred if parsing failed.
88          */
89         xml_parse(vardata, false, true);
90
91         PG_RETURN_XML_P(vardata);
92 #else
93         NO_XML_SUPPORT();
94         return 0;
95 #endif
96 }
97
98
99 Datum
100 xml_out(PG_FUNCTION_ARGS)
101 {
102         xmltype         *s = PG_GETARG_XML_P(0);
103         char            *result;
104         int32           len;
105
106         len = VARSIZE(s) - VARHDRSZ;
107         result = palloc(len + 1);
108         memcpy(result, VARDATA(s), len);
109         result[len] = '\0';
110
111         PG_RETURN_CSTRING(result);
112 }
113
114
115 Datum
116 xml_recv(PG_FUNCTION_ARGS)
117 {
118 #ifdef USE_LIBXML
119         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
120         xmltype    *result;
121         char       *str;
122         int                     nbytes;
123
124         str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
125
126         result = (xmltype *) palloc(nbytes + VARHDRSZ);
127         VARATT_SIZEP(result) = nbytes + VARHDRSZ;
128         memcpy(VARDATA(result), str, nbytes);
129         pfree(str);
130
131         /*
132          * Parse the data to check if it is well-formed XML data.  Assume
133          * that ERROR occurred if parsing failed.
134          */
135         xml_parse(result, false, true);
136
137         PG_RETURN_XML_P(result);
138 #else
139         NO_XML_SUPPORT();
140         return 0;
141 #endif
142 }
143
144
145 Datum
146 xml_send(PG_FUNCTION_ARGS)
147 {
148         xmltype    *x = PG_GETARG_XML_P(0);
149         StringInfoData buf;
150
151         pq_begintypsend(&buf);
152         pq_sendbytes(&buf, VARDATA(x), VARSIZE(x) - VARHDRSZ);
153         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
154 }
155
156
157 #ifdef USE_LIBXML
158 static void
159 appendStringInfoText(StringInfo str, const text *t)
160 {
161         appendBinaryStringInfo(str, VARDATA(t), VARSIZE(t) - VARHDRSZ);
162 }
163
164
165 static xmltype *
166 stringinfo_to_xmltype(StringInfo buf)
167 {
168         int32 len;
169         xmltype *result;
170
171         len = buf->len + VARHDRSZ;
172         result = palloc(len);
173         VARATT_SIZEP(result) = len;
174         memcpy(VARDATA(result), buf->data, buf->len);
175
176         return result;
177 }
178 #endif
179
180
181 Datum
182 xmlcomment(PG_FUNCTION_ARGS)
183 {
184 #ifdef USE_LIBXML
185         text *arg = PG_GETARG_TEXT_P(0);
186         int len =  VARATT_SIZEP(arg) - VARHDRSZ;
187         StringInfoData buf;
188         int i;
189
190         /* check for "--" in string or "-" at the end */
191         for (i = 1; i < len; i++)
192                 if ((VARDATA(arg)[i] == '-' && VARDATA(arg)[i - 1] == '-')
193                         || (VARDATA(arg)[i] == '-' && i == len - 1))
194                                         ereport(ERROR,
195                                                         (errcode(ERRCODE_INVALID_XML_COMMENT),
196                                                          errmsg("invalid XML comment")));
197
198         initStringInfo(&buf);
199         appendStringInfo(&buf, "<!--");
200         appendStringInfoText(&buf, arg);
201         appendStringInfo(&buf, "-->");
202
203         PG_RETURN_XML_P(stringinfo_to_xmltype(&buf));
204 #else
205         NO_XML_SUPPORT();
206         return 0;
207 #endif
208 }
209
210
211 Datum
212 texttoxml(PG_FUNCTION_ARGS)
213 {
214         text       *data = PG_GETARG_TEXT_P(0);
215
216         PG_RETURN_XML_P(xmlparse(data, false, true));
217 }
218
219
220 xmltype *
221 xmlparse(text *data, bool is_document, bool preserve_whitespace)
222 {
223 #ifdef USE_LIBXML
224         xml_parse(data, is_document, preserve_whitespace);
225
226         return (xmltype *) data;
227 #else
228         NO_XML_SUPPORT();
229         return NULL;
230 #endif
231 }
232
233
234 xmltype *
235 xmlpi(char *target, text *arg)
236 {
237 #ifdef USE_LIBXML
238         xmltype *result;
239         StringInfoData buf;
240
241         if (pg_strncasecmp(target, "xml", 3) == 0)
242                 ereport(ERROR,
243                                 (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
244                                  errmsg("invalid XML processing instruction"),
245                                  errdetail("XML processing instruction target name cannot start with \"xml\".")));
246
247         initStringInfo(&buf);
248
249         appendStringInfo(&buf, "<?%s", target);
250
251         if (arg != NULL)
252         {
253                 char *string;
254
255                 string = DatumGetCString(DirectFunctionCall1(textout,
256                                                                                                          PointerGetDatum(arg)));
257                 if (strstr(string, "?>") != NULL)
258                 ereport(ERROR,
259                                 (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
260                                  errmsg("invalid XML processing instruction"),
261                                  errdetail("XML processing instruction cannot contain \"?>\".")));
262
263                 appendStringInfoChar(&buf, ' ');
264                 appendStringInfoString(&buf, string);
265                 pfree(string);
266         }
267         appendStringInfoString(&buf, "?>");
268
269         result = stringinfo_to_xmltype(&buf);
270         pfree(buf.data);
271         return result;
272 #else
273         NO_XML_SUPPORT();
274         return NULL;
275 #endif
276 }
277
278
279 xmltype *
280 xmlroot(xmltype *data, text *version, int standalone)
281 {
282 #ifdef USE_LIBXML
283         xmltype *result;
284         StringInfoData buf;
285
286         initStringInfo(&buf);
287
288         /*
289          * FIXME: This is probably supposed to be cleverer if there
290          * already is an XML preamble.
291          */
292         appendStringInfo(&buf,"<?xml");
293         if (version)
294         {
295                 appendStringInfo(&buf, " version=\"");
296                 appendStringInfoText(&buf, version);
297                 appendStringInfo(&buf, "\"");
298         }
299         if (standalone)
300                 appendStringInfo(&buf, " standalone=\"%s\"",
301                                                  (standalone == 1 ? "yes" : "no"));
302         appendStringInfo(&buf, "?>");
303         appendStringInfoText(&buf, (text *) data);
304
305         result = stringinfo_to_xmltype(&buf);
306         pfree(buf.data);
307         return result;
308 #else
309         NO_XML_SUPPORT();
310         return NULL;
311 #endif
312 }
313
314
315 /*
316  * Validate document (given as string) against DTD (given as external link)
317  * TODO !!! use text instead of cstring for second arg
318  * TODO allow passing DTD as a string value (not only as an URI)
319  * TODO redesign (see comment with '!!!' below)
320  */
321 Datum
322 xmlvalidate(PG_FUNCTION_ARGS)
323 {
324 #ifdef USE_LIBXML
325         text                            *data = PG_GETARG_TEXT_P(0);
326         text                            *dtdOrUri = PG_GETARG_TEXT_P(1);
327         bool                            result = false;
328         xmlParserCtxtPtr        ctxt = NULL;
329         xmlDocPtr                       doc = NULL;
330         xmlDtdPtr                       dtd = NULL;
331
332         xml_init();
333
334         /* We use a PG_TRY block to ensure libxml is cleaned up on error */
335         PG_TRY();
336         {
337                 ctxt = xmlNewParserCtxt();
338                 if (ctxt == NULL)
339                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
340                                                 "could not allocate parser context", ctxt);
341
342                 doc = xmlCtxtReadMemory(ctxt, (char *) VARDATA(data),
343                                                                 VARSIZE(data) - VARHDRSZ,
344                                                                 PG_XML_DEFAULT_URI, NULL, 0);
345                 if (doc == NULL)
346                         xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
347                                                 "could not parse XML data", ctxt);
348
349 #if 0
350                 uri = xmlCreateURI();
351                 elog(NOTICE, "dtd - %s", dtdOrUri);
352                 dtd = palloc(sizeof(xmlDtdPtr));
353                 uri = xmlParseURI(dtdOrUri);
354                 if (uri == NULL)
355                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
356                                                 "not implemented yet... (TODO)", ctxt);
357                 else
358 #endif
359                         dtd = xmlParseDTD(NULL, xml_text2xmlChar(dtdOrUri));
360
361                 if (dtd == NULL)
362                         xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
363                                                 "could not load DTD", ctxt);
364
365                 if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) == 1)
366                         result = true;
367
368                 if (!result)
369                         xml_ereport(NOTICE, ERRCODE_INVALID_XML_DOCUMENT,
370                                                 "validation against DTD failed", ctxt);
371
372 #if 0
373                 if (uri)
374                         xmlFreeURI(uri);
375 #endif
376                 if (dtd)
377                         xmlFreeDtd(dtd);
378                 if (doc)
379                         xmlFreeDoc(doc);
380                 if (ctxt)
381                         xmlFreeParserCtxt(ctxt);
382                 xmlCleanupParser();
383         }
384         PG_CATCH();
385         {
386 #if 0
387                 if (uri)
388                         xmlFreeURI(uri);
389 #endif
390                 if (dtd)
391                         xmlFreeDtd(dtd);
392                 if (doc)
393                         xmlFreeDoc(doc);
394                 if (ctxt)
395                         xmlFreeParserCtxt(ctxt);
396                 xmlCleanupParser();
397
398                 PG_RE_THROW();
399         }
400         PG_END_TRY();
401
402         PG_RETURN_BOOL(result);
403 #else /* not USE_LIBXML */
404         NO_XML_SUPPORT();
405         return 0;
406 #endif /* not USE_LIBXML */
407 }
408
409
410 #ifdef USE_LIBXML
411
412 /*
413  * Container for some init stuff (not good design!)
414  * TODO xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and check)
415  */
416 static void
417 xml_init(void)
418 {
419         /*
420          * Currently, we have no pure UTF-8 support for internals -- check
421          * if we can work.
422          */
423         if (sizeof (char) != sizeof (xmlChar))
424                 ereport(ERROR,
425                                 (errmsg("could not initialize XML library"),
426                                  errdetail("libxml2 has incompatible char type: sizeof(char)=%u, sizeof(xmlChar)=%u.",
427                                                    (int) sizeof(char), (int) sizeof(xmlChar))));
428
429         if (xml_err_buf == NULL)
430         {
431                 /* First time through: create error buffer in permanent context */
432                 MemoryContext oldcontext;
433
434                 oldcontext = MemoryContextSwitchTo(TopMemoryContext);
435                 xml_err_buf = makeStringInfo();
436                 MemoryContextSwitchTo(oldcontext);
437         }
438         else
439         {
440                 /* Reset pre-existing buffer to empty */
441                 xml_err_buf->data[0] = '\0';
442                 xml_err_buf->len = 0;
443         }
444         /* Now that xml_err_buf exists, safe to call xml_errorHandler */
445         xmlSetGenericErrorFunc(NULL, xml_errorHandler);
446
447         xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup);
448         xmlInitParser();
449         LIBXML_TEST_VERSION;
450 }
451
452
453 /*
454  * Convert a C string to XML internal representation
455  *
456  * TODO maybe, libxml2's xmlreader is better? (do not construct DOM, yet do not use SAX - see xml_reader.c)
457  * TODO what about internal URI for docs? (see PG_XML_DEFAULT_URI below)
458  */
459 static xmlDocPtr
460 xml_parse(text *data, bool is_document, bool preserve_whitespace)
461 {
462         int                                     res_code;
463         int32                           len;
464         xmlChar                         *string;
465         xmlParserCtxtPtr        ctxt = NULL;
466         xmlDocPtr                       doc = NULL;
467
468         len = VARSIZE(data) - VARHDRSZ; /* will be useful later */
469         string = xml_text2xmlChar(data);
470
471         xml_init();
472
473         /* We use a PG_TRY block to ensure libxml is cleaned up on error */
474         PG_TRY();
475         {
476                 ctxt = xmlNewParserCtxt();
477                 if (ctxt == NULL)
478                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
479                                                 "could not allocate parser context", ctxt);
480
481                 if (is_document)
482                 {
483                         /*
484                          * Note, that here we try to apply DTD defaults
485                          * (XML_PARSE_DTDATTR) according to SQL/XML:10.16.7.d:
486                          * 'Default valies defined by internal DTD are applied'.
487                          * As for external DTDs, we try to support them too, (see
488                          * SQL/XML:10.16.7.e)
489                          */
490                         doc = xmlCtxtReadMemory(ctxt, (char *) string, len,
491                                                                         PG_XML_DEFAULT_URI, NULL,
492                                                                         XML_PARSE_NOENT | XML_PARSE_DTDATTR
493                                                                         | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS));
494                         if (doc == NULL)
495                                 xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
496                                                         "invalid XML document", ctxt);
497                 }
498                 else
499                 {
500                         doc = xmlNewDoc(NULL);
501
502                         /*
503                          * FIXME: An XMLDecl is supposed to be accepted before the
504                          * content, but libxml doesn't allow this.  Parse that
505                          * ourselves?
506                          */
507
508                         /* TODO resolve: xmlParseBalancedChunkMemory assumes that string is UTF8 encoded! */
509                         res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, string, NULL);
510                         if (res_code != 0)
511                                 xml_ereport_by_code(ERROR, ERRCODE_INVALID_XML_CONTENT,
512                                                                         "invalid XML content", res_code);
513                 }
514
515                 /* TODO encoding issues
516                  * (thoughts:
517                  *              CASE:
518                  *              - XML data has explicit encoding attribute in its prolog
519                  *              - if not, assume that enc. of XML data is the same as client's one
520                  *
521                  *              The common rule is to accept the XML data only if its encoding
522                  *              is the same as encoding of the storage (server's). The other possible
523                  *              option is to accept all the docs, but DO TRANSFORMATION and, if needed,
524                  *              change the prolog.
525                  *
526                  *              I think I'd stick the first way (for the 1st version),
527                  *              it's much simplier (less errors...)
528                  * ) */
529                 /* ... */
530
531                 if (doc)
532                         xmlFreeDoc(doc);
533                 if (ctxt)
534                         xmlFreeParserCtxt(ctxt);
535                 xmlCleanupParser();
536         }
537         PG_CATCH();
538         {
539                 if (doc)
540                         xmlFreeDoc(doc);
541                 if (ctxt)
542                         xmlFreeParserCtxt(ctxt);
543                 xmlCleanupParser();
544
545                 PG_RE_THROW();
546         }
547         PG_END_TRY();
548
549         return doc;
550 }
551
552
553 /*
554  * xmlChar<->text convertions
555  */
556 static xmlChar *
557 xml_text2xmlChar(text *in)
558 {
559         int32           len = VARSIZE(in) - VARHDRSZ;
560         xmlChar         *res;
561
562         res = palloc(len + 1);
563         memcpy(res, VARDATA(in), len);
564         res[len] = '\0';
565
566         return(res);
567 }
568
569
570 /*
571  * Wrappers for memory management functions
572  */
573 static void *
574 xml_palloc(size_t size)
575 {
576         return palloc(size);
577 }
578
579
580 static void *
581 xml_repalloc(void *ptr, size_t size)
582 {
583         return repalloc(ptr, size);
584 }
585
586
587 static void
588 xml_pfree(void *ptr)
589 {
590         pfree(ptr);
591 }
592
593
594 static char *
595 xml_pstrdup(const char *string)
596 {
597         return pstrdup(string);
598 }
599
600
601 /*
602  * Wrapper for "ereport" function.
603  * Adds detail - libxml's native error message, if any.
604  */
605 static void
606 xml_ereport(int level, int sqlcode,
607                         const char *msg, void *ctxt)
608 {
609         xmlErrorPtr libxmlErr = NULL;
610
611         if (xml_err_buf->len > 0)
612         {
613                 ereport(DEBUG1,
614                                 (errmsg("%s", xml_err_buf->data)));
615                 xml_err_buf->data[0] = '\0';
616                 xml_err_buf->len = 0;
617         }
618
619         if (ctxt != NULL)
620                 libxmlErr = xmlCtxtGetLastError(ctxt);
621
622         if (libxmlErr == NULL)
623         {
624                 ereport(level,
625                                 (errcode(sqlcode),
626                                  errmsg("%s", msg)));
627         }
628         else
629         {
630                 /* as usual, libxml error message contains '\n'; get rid of it */
631                 char *xmlErrDetail;
632                 int xmlErrLen, i;
633
634                 xmlErrDetail = pstrdup(libxmlErr->message);
635                 xmlErrLen = strlen(xmlErrDetail);
636                 for (i = 0; i < xmlErrLen; i++)
637                 {
638                         if (xmlErrDetail[i] == '\n')
639                                 xmlErrDetail[i] = '.';
640                 }
641                 ereport(level,
642                                 (errcode(sqlcode),
643                                  errmsg("%s", msg),
644                                  errdetail("%s", xmlErrDetail)));
645         }
646 }
647
648
649 /*
650  * Error handler for libxml error messages
651  */
652 static void
653 xml_errorHandler(void *ctxt, const char *msg,...)
654 {
655         /* Append the formatted text to xml_err_buf */
656         for (;;)
657         {
658                 va_list         args;
659                 bool            success;
660
661                 /* Try to format the data. */
662                 va_start(args, msg);
663                 success = appendStringInfoVA(xml_err_buf, msg, args);
664                 va_end(args);
665
666                 if (success)
667                         break;
668
669                 /* Double the buffer size and try again. */
670                 enlargeStringInfo(xml_err_buf, xml_err_buf->maxlen);
671         }
672 }
673
674
675 /*
676  * Return error message by libxml error code
677  * TODO make them closer to recommendations from Postgres manual
678  */
679 static void
680 xml_ereport_by_code(int level, int sqlcode,
681                                         const char *msg, int code)
682 {
683     const char *det;
684
685         if (xml_err_buf->len > 0)
686         {
687                 ereport(DEBUG1,
688                                 (errmsg("%s", xml_err_buf->data)));
689                 xml_err_buf->data[0] = '\0';
690                 xml_err_buf->len = 0;
691         }
692
693     switch (code)
694         {
695         case XML_ERR_INTERNAL_ERROR:
696             det = "libxml internal error";
697             break;
698         case XML_ERR_ENTITY_LOOP:
699             det = "Detected an entity reference loop";
700             break;
701         case XML_ERR_ENTITY_NOT_STARTED:
702             det = "EntityValue: \" or ' expected";
703             break;
704         case XML_ERR_ENTITY_NOT_FINISHED:
705             det = "EntityValue: \" or ' expected";
706             break;
707         case XML_ERR_ATTRIBUTE_NOT_STARTED:
708             det = "AttValue: \" or ' expected";
709             break;
710         case XML_ERR_LT_IN_ATTRIBUTE:
711             det = "Unescaped '<' not allowed in attributes values";
712             break;
713         case XML_ERR_LITERAL_NOT_STARTED:
714             det = "SystemLiteral \" or ' expected";
715             break;
716         case XML_ERR_LITERAL_NOT_FINISHED:
717             det = "Unfinished System or Public ID \" or ' expected";
718             break;
719         case XML_ERR_MISPLACED_CDATA_END:
720             det = "Sequence ']]>' not allowed in content";
721             break;
722         case XML_ERR_URI_REQUIRED:
723             det = "SYSTEM or PUBLIC, the URI is missing";
724             break;
725         case XML_ERR_PUBID_REQUIRED:
726             det = "PUBLIC, the Public Identifier is missing";
727             break;
728         case XML_ERR_HYPHEN_IN_COMMENT:
729             det = "Comment must not contain '--' (double-hyphen)";
730             break;
731         case XML_ERR_PI_NOT_STARTED:
732             det = "xmlParsePI : no target name";
733             break;
734         case XML_ERR_RESERVED_XML_NAME:
735             det = "Invalid PI name";
736             break;
737         case XML_ERR_NOTATION_NOT_STARTED:
738             det = "NOTATION: Name expected here";
739             break;
740         case XML_ERR_NOTATION_NOT_FINISHED:
741             det = "'>' required to close NOTATION declaration";
742             break;
743         case XML_ERR_VALUE_REQUIRED:
744             det = "Entity value required";
745             break;
746         case XML_ERR_URI_FRAGMENT:
747             det = "Fragment not allowed";
748             break;
749         case XML_ERR_ATTLIST_NOT_STARTED:
750             det = "'(' required to start ATTLIST enumeration";
751             break;
752         case XML_ERR_NMTOKEN_REQUIRED:
753             det = "NmToken expected in ATTLIST enumeration";
754             break;
755         case XML_ERR_ATTLIST_NOT_FINISHED:
756             det = "')' required to finish ATTLIST enumeration";
757             break;
758         case XML_ERR_MIXED_NOT_STARTED:
759             det = "MixedContentDecl : '|' or ')*' expected";
760             break;
761         case XML_ERR_PCDATA_REQUIRED:
762             det = "MixedContentDecl : '#PCDATA' expected";
763             break;
764         case XML_ERR_ELEMCONTENT_NOT_STARTED:
765             det = "ContentDecl : Name or '(' expected";
766             break;
767         case XML_ERR_ELEMCONTENT_NOT_FINISHED:
768             det = "ContentDecl : ',' '|' or ')' expected";
769             break;
770         case XML_ERR_PEREF_IN_INT_SUBSET:
771             det = "PEReference: forbidden within markup decl in internal subset";
772             break;
773         case XML_ERR_GT_REQUIRED:
774             det = "Expected '>'";
775             break;
776         case XML_ERR_CONDSEC_INVALID:
777             det = "XML conditional section '[' expected";
778             break;
779         case XML_ERR_EXT_SUBSET_NOT_FINISHED:
780             det = "Content error in the external subset";
781             break;
782         case XML_ERR_CONDSEC_INVALID_KEYWORD:
783             det = "conditional section INCLUDE or IGNORE keyword expected";
784             break;
785         case XML_ERR_CONDSEC_NOT_FINISHED:
786             det = "XML conditional section not closed";
787             break;
788         case XML_ERR_XMLDECL_NOT_STARTED:
789             det = "Text declaration '<?xml' required";
790             break;
791         case XML_ERR_XMLDECL_NOT_FINISHED:
792             det = "parsing XML declaration: '?>' expected";
793             break;
794         case XML_ERR_EXT_ENTITY_STANDALONE:
795             det = "external parsed entities cannot be standalone";
796             break;
797         case XML_ERR_ENTITYREF_SEMICOL_MISSING:
798             det = "EntityRef: expecting ';'";
799             break;
800         case XML_ERR_DOCTYPE_NOT_FINISHED:
801             det = "DOCTYPE improperly terminated";
802             break;
803         case XML_ERR_LTSLASH_REQUIRED:
804             det = "EndTag: '</' not found";
805             break;
806         case XML_ERR_EQUAL_REQUIRED:
807             det = "Expected '='";
808             break;
809         case XML_ERR_STRING_NOT_CLOSED:
810             det = "String not closed expecting \" or '";
811             break;
812         case XML_ERR_STRING_NOT_STARTED:
813             det = "String not started expecting ' or \"";
814             break;
815         case XML_ERR_ENCODING_NAME:
816             det = "Invalid XML encoding name";
817             break;
818         case XML_ERR_STANDALONE_VALUE:
819             det = "Standalone accepts only 'yes' or 'no'";
820             break;
821         case XML_ERR_DOCUMENT_EMPTY:
822             det = "Document is empty";
823             break;
824         case XML_ERR_DOCUMENT_END:
825             det = "Extra content at the end of the document";
826             break;
827         case XML_ERR_NOT_WELL_BALANCED:
828             det = "Chunk is not well balanced";
829             break;
830         case XML_ERR_EXTRA_CONTENT:
831             det = "Extra content at the end of well balanced chunk";
832             break;
833         case XML_ERR_VERSION_MISSING:
834             det = "Malformed declaration expecting version";
835             break;
836         /* more err codes... Please, keep the order! */
837         case XML_ERR_ATTRIBUTE_WITHOUT_VALUE: /* 41 */
838                 det ="Attribute without value";
839                 break;
840         case XML_ERR_ATTRIBUTE_REDEFINED:
841                 det ="Attribute defined more than once in the same element";
842                 break;
843         case XML_ERR_COMMENT_NOT_FINISHED: /* 45 */
844             det = "Comment is not finished";
845             break;
846         case XML_ERR_NAME_REQUIRED: /* 68 */
847             det = "Element name not found";
848             break;
849         case XML_ERR_TAG_NOT_FINISHED: /* 77 */
850             det = "Closing tag not found";
851             break;
852         default:
853             det = "Unrecognized libxml error code: %d";
854                         break;
855         }
856
857         ereport(level,
858                         (errcode(sqlcode),
859                          errmsg("%s", msg),
860                          errdetail(det, code)));
861 }
862
863
864 /*
865  * Convert one char in the current server encoding to a Unicode codepoint.
866  */
867 static pg_wchar
868 sqlchar_to_unicode(char *s)
869 {
870         char *utf8string;
871         pg_wchar ret[2];                        /* need space for trailing zero */
872
873         utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s,
874                                                                                                         pg_mblen(s),
875                                                                                                         GetDatabaseEncoding(),
876                                                                                                         PG_UTF8);
877
878         pg_encoding_mb2wchar_with_len(PG_UTF8, utf8string, ret, pg_mblen(s));
879
880         return ret[0];
881 }
882
883
884 static bool
885 is_valid_xml_namefirst(pg_wchar c)
886 {
887         /* (Letter | '_' | ':') */
888         return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
889                         || c == '_' || c == ':');
890 }
891
892
893 static bool
894 is_valid_xml_namechar(pg_wchar c)
895 {
896         /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */
897         return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
898                         || xmlIsDigitQ(c)
899                         || c == '.' || c == '-' || c == '_' || c == ':'
900                         || xmlIsCombiningQ(c)
901                         || xmlIsExtenderQ(c));
902 }
903 #endif /* USE_LIBXML */
904
905
906 /*
907  * Map SQL identifier to XML name; see SQL/XML:2003 section 9.1.
908  */
909 char *
910 map_sql_identifier_to_xml_name(char *ident, bool fully_escaped)
911 {
912 #ifdef USE_LIBXML
913         StringInfoData buf;
914         char *p;
915
916         initStringInfo(&buf);
917
918         for (p = ident; *p; p += pg_mblen(p))
919         {
920                 if (*p == ':' && (p == ident || fully_escaped))
921                         appendStringInfo(&buf, "_x003A_");
922                 else if (*p == '_' && *(p+1) == 'x')
923                         appendStringInfo(&buf, "_x005F_");
924                 else if (fully_escaped && p == ident &&
925                                  pg_strncasecmp(p, "xml", 3) == 0)
926                 {
927                         if (*p == 'x')
928                                 appendStringInfo(&buf, "_x0078_");
929                         else
930                                 appendStringInfo(&buf, "_x0058_");
931                 }
932                 else
933                 {
934                         pg_wchar u = sqlchar_to_unicode(p);
935
936                         if ((p == ident)
937                                 ? !is_valid_xml_namefirst(u)
938                                 : !is_valid_xml_namechar(u))
939                                 appendStringInfo(&buf, "_x%04X_", (unsigned int) u);
940                         else
941                                 appendBinaryStringInfo(&buf, p, pg_mblen(p));
942                 }
943         }
944
945         return buf.data;
946 #else /* not USE_LIBXML */
947         NO_XML_SUPPORT();
948         return NULL;
949 #endif /* not USE_LIBXML */
950 }
951
952
953 /*
954  * Map a Unicode codepoint into the current server encoding.
955  */
956 static char *
957 unicode_to_sqlchar(pg_wchar c)
958 {
959         static unsigned char utf8string[4];
960
961         if (c <= 0x7F)
962         {
963                 utf8string[0] = c;
964         }
965         else if (c <= 0x7FF)
966         {
967                 utf8string[0] = 0xC0 | ((c >> 6) & 0x1F);
968                 utf8string[1] = 0x80 | (c & 0x3F);
969         }
970         else if (c <= 0xFFFF)
971         {
972                 utf8string[0] = 0xE0 | ((c >> 12) & 0x0F);
973                 utf8string[1] = 0x80 | ((c >> 6) & 0x3F);
974                 utf8string[2] = 0x80 | (c & 0x3F);
975         }
976         else
977         {
978                 utf8string[0] = 0xF0 | ((c >> 18) & 0x07);
979                 utf8string[1] = 0x80 | ((c >> 12) & 0x3F);
980                 utf8string[2] = 0x80 | ((c >> 6) & 0x3F);
981                 utf8string[3] = 0x80 | (c & 0x3F);
982         }
983
984         return (char *) pg_do_encoding_conversion(utf8string,
985                                                                                           pg_mblen((char *) utf8string),
986                                                                                           PG_UTF8,
987                                                                                           GetDatabaseEncoding());
988 }
989
990
991 /*
992  * Map XML name to SQL identifier; see SQL/XML:2003 section 9.17.
993  */
994 char *
995 map_xml_name_to_sql_identifier(char *name)
996 {
997         StringInfoData buf;
998         char *p;
999
1000         initStringInfo(&buf);
1001
1002         for (p = name; *p; p += pg_mblen(p))
1003         {
1004                 if (*p == '_' && *(p+1) == 'x'
1005                         && isxdigit((unsigned char) *(p+2))
1006                         && isxdigit((unsigned char) *(p+3))
1007                         && isxdigit((unsigned char) *(p+4))
1008                         && isxdigit((unsigned char) *(p+5))
1009                         && *(p+6) == '_')
1010                 {
1011                         unsigned int u;
1012
1013                         sscanf(p + 2, "%X", &u);
1014                         appendStringInfoString(&buf, unicode_to_sqlchar(u));
1015                         p += 6;
1016                 }
1017                 else
1018                         appendBinaryStringInfo(&buf, p, pg_mblen(p));
1019         }
1020
1021         return buf.data;
1022 }