OSDN Git Service

Add support for converting binary values (i.e. bytea) into xml values,
[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.19 2007/01/19 16:58:46 petere 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 #include <libxml/xmlsave.h>
35 #include <libxml/xmlwriter.h>
36 #endif /* USE_LIBXML */
37
38 #include "catalog/pg_type.h"
39 #include "executor/executor.h"
40 #include "fmgr.h"
41 #include "libpq/pqformat.h"
42 #include "mb/pg_wchar.h"
43 #include "nodes/execnodes.h"
44 #include "parser/parse_expr.h"
45 #include "utils/array.h"
46 #include "utils/builtins.h"
47 #include "utils/lsyscache.h"
48 #include "utils/memutils.h"
49 #include "utils/xml.h"
50
51
52 #ifdef USE_LIBXML
53
54 #define PG_XML_DEFAULT_URI "dummy.xml"
55
56 static StringInfo xml_err_buf = NULL;
57
58 static void     xml_init(void);
59 #ifdef NOT_USED
60 static void    *xml_palloc(size_t size);
61 static void    *xml_repalloc(void *ptr, size_t size);
62 static void     xml_pfree(void *ptr);
63 static char    *xml_pstrdup(const char *string);
64 #endif
65 static void     xml_ereport(int level, int sqlcode,
66                                                         const char *msg, void *ctxt);
67 static void     xml_errorHandler(void *ctxt, const char *msg, ...);
68 static void     xml_ereport_by_code(int level, int sqlcode,
69                                                                         const char *msg, int errcode);
70 static xmlChar *xml_text2xmlChar(text *in);
71 static int              parse_xml_decl(const xmlChar *str, size_t *lenp, xmlChar **version, xmlChar **encoding, int *standalone);
72 static xmlDocPtr xml_parse(text *data, bool is_document, bool preserve_whitespace, xmlChar *encoding);
73
74 #endif /* USE_LIBXML */
75
76 XmlBinaryType xmlbinary;
77
78 #define NO_XML_SUPPORT() \
79         ereport(ERROR, \
80                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
81                          errmsg("no XML support in this installation")))
82
83
84 Datum
85 xml_in(PG_FUNCTION_ARGS)
86 {
87 #ifdef USE_LIBXML
88         char            *s = PG_GETARG_CSTRING(0);
89         size_t          len;
90         xmltype         *vardata;
91         xmlDocPtr        doc;
92
93         len = strlen(s);
94         vardata = palloc(len + VARHDRSZ);
95         VARATT_SIZEP(vardata) = len + VARHDRSZ;
96         memcpy(VARDATA(vardata), s, len);
97
98         /*
99          * Parse the data to check if it is well-formed XML data.  Assume
100          * that ERROR occurred if parsing failed.
101          */
102         doc = xml_parse(vardata, false, true, NULL);
103         xmlFreeDoc(doc);
104
105         PG_RETURN_XML_P(vardata);
106 #else
107         NO_XML_SUPPORT();
108         return 0;
109 #endif
110 }
111
112
113 #define PG_XML_DEFAULT_VERSION "1.0"
114
115
116 static char *
117 xml_out_internal(xmltype *x, pg_enc target_encoding)
118 {
119         char            *str;
120         size_t          len;
121 #ifdef USE_LIBXML
122         xmlChar         *version;
123         xmlChar         *encoding;
124         int                     standalone;
125         int                     res_code;
126 #endif
127
128         len = VARSIZE(x) - VARHDRSZ;
129         str = palloc(len + 1);
130         memcpy(str, VARDATA(x), len);
131         str[len] = '\0';
132
133 #ifdef USE_LIBXML
134         /*
135          * On output, we adjust the XML declaration as follows.  (These
136          * rules are the moral equivalent of the clause "Serialization of
137          * an XML value" in the SQL standard.)
138          *
139          * We try to avoid generating an XML declaration if possible.
140          * This is so that you don't get trivial things like xml '<foo/>'
141          * resulting in '<?xml version="1.0"?><foo/>', which would surely
142          * be annoying.  We must provide a declaration if the standalone
143          * property is specified or if we include an encoding
144          * specification.  If we have a declaration, we must specify a
145          * version (XML requires this).  Otherwise we only make a
146          * declaration if the version is not "1.0", which is the default
147          * version specified in SQL:2003.
148          */
149         if ((res_code = parse_xml_decl((xmlChar *) str, &len, &version, &encoding, &standalone)) == 0)
150         {
151                 StringInfoData buf;
152
153                 initStringInfo(&buf);
154
155                 if ((version && strcmp((char *) version, PG_XML_DEFAULT_VERSION) != 0)
156                         || (target_encoding && target_encoding != PG_UTF8)
157                         || standalone != -1)
158                 {
159                         appendStringInfoString(&buf, "<?xml");
160                         if (version)
161                                 appendStringInfo(&buf, " version=\"%s\"", version);
162                         else
163                                 appendStringInfo(&buf, " version=\"%s\"", PG_XML_DEFAULT_VERSION);
164                         if (target_encoding && target_encoding != PG_UTF8)
165                                 /* XXX might be useful to convert this to IANA names
166                                  * (ISO-8859-1 instead of LATIN1 etc.); needs field
167                                  * experience */
168                                 appendStringInfo(&buf, " encoding=\"%s\"", pg_encoding_to_char(target_encoding));
169                         if (standalone == 1)
170                                 appendStringInfoString(&buf, " standalone=\"yes\"");
171                         else if (standalone == 0)
172                                 appendStringInfoString(&buf, " standalone=\"no\"");
173                         appendStringInfoString(&buf, "?>");
174                 }
175                 else
176                 {
177                         /*
178                          * If we are not going to produce an XML declaration, eat
179                          * a single newline in the original string to prevent
180                          * empty first lines in the output.
181                          */
182                         if (*(str + len) == '\n')
183                                 len += 1;
184                 }
185                 appendStringInfoString(&buf, str + len);
186
187                 return buf.data;
188         }
189
190         xml_ereport_by_code(WARNING, ERRCODE_INTERNAL_ERROR,
191                                                 "could not parse XML declaration in stored value", res_code);
192 #endif
193         return str;
194 }
195
196
197 Datum
198 xml_out(PG_FUNCTION_ARGS)
199 {
200         xmltype    *x = PG_GETARG_XML_P(0);
201
202         /*
203          * xml_out removes the encoding property in all cases.  This is
204          * because we cannot control from here whether the datum will be
205          * converted to a different client encoding, so we'd do more harm
206          * than good by including it.
207          */
208         PG_RETURN_CSTRING(xml_out_internal(x, 0));
209 }
210
211
212 Datum
213 xml_recv(PG_FUNCTION_ARGS)
214 {
215 #ifdef USE_LIBXML
216         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
217         xmltype    *result;
218         char       *str;
219         char       *newstr;
220         int                     nbytes;
221         xmlDocPtr       doc;
222         xmlChar    *encoding = NULL;
223
224         str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
225
226         result = palloc(nbytes + VARHDRSZ);
227         VARATT_SIZEP(result) = nbytes + VARHDRSZ;
228         memcpy(VARDATA(result), str, nbytes);
229
230         parse_xml_decl((xmlChar *) str, NULL, NULL, &encoding, NULL);
231
232         /*
233          * Parse the data to check if it is well-formed XML data.  Assume
234          * that ERROR occurred if parsing failed.
235          */
236         doc = xml_parse(result, false, true, encoding);
237         xmlFreeDoc(doc);
238
239         newstr = (char *) pg_do_encoding_conversion((unsigned char *) str,
240                                                                                                 nbytes,
241                                                                                                 encoding ? pg_char_to_encoding((char *) encoding) : PG_UTF8,
242                                                                                                 GetDatabaseEncoding());
243
244         pfree(str);
245
246         if (newstr != str)
247         {
248                 free(result);
249
250                 nbytes = strlen(newstr);
251
252                 result = palloc(nbytes + VARHDRSZ);
253                 VARATT_SIZEP(result) = nbytes + VARHDRSZ;
254                 memcpy(VARDATA(result), newstr, nbytes);
255         }
256
257         PG_RETURN_XML_P(result);
258 #else
259         NO_XML_SUPPORT();
260         return 0;
261 #endif
262 }
263
264
265 Datum
266 xml_send(PG_FUNCTION_ARGS)
267 {
268         xmltype    *x = PG_GETARG_XML_P(0);
269         char       *outval = xml_out_internal(x, pg_get_client_encoding());
270         StringInfoData buf;
271
272         pq_begintypsend(&buf);
273         pq_sendstring(&buf, outval);
274         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
275 }
276
277
278 #ifdef USE_LIBXML
279 static void
280 appendStringInfoText(StringInfo str, const text *t)
281 {
282         appendBinaryStringInfo(str, VARDATA(t), VARSIZE(t) - VARHDRSZ);
283 }
284
285
286 static xmltype *
287 stringinfo_to_xmltype(StringInfo buf)
288 {
289         int32 len;
290         xmltype *result;
291
292         len = buf->len + VARHDRSZ;
293         result = palloc(len);
294         VARATT_SIZEP(result) = len;
295         memcpy(VARDATA(result), buf->data, buf->len);
296
297         return result;
298 }
299
300
301 static xmltype *
302 cstring_to_xmltype(const char *string)
303 {
304         int32           len;
305         xmltype    *result;
306
307         len = strlen(string) + VARHDRSZ;
308         result = palloc(len);
309         VARATT_SIZEP(result) = len;
310         memcpy(VARDATA(result), string, len - VARHDRSZ);
311
312         return result;
313 }
314
315
316 static xmltype *
317 xmlBuffer_to_xmltype(xmlBufferPtr buf)
318 {
319         int32           len;
320         xmltype    *result;
321
322         len = xmlBufferLength(buf) + VARHDRSZ;
323         result = palloc(len);
324         VARATT_SIZEP(result) = len;
325         memcpy(VARDATA(result), xmlBufferContent(buf), len - VARHDRSZ);
326
327         return result;
328 }
329 #endif
330
331
332 Datum
333 xmlcomment(PG_FUNCTION_ARGS)
334 {
335 #ifdef USE_LIBXML
336         text *arg = PG_GETARG_TEXT_P(0);
337         int len =  VARSIZE(arg) - VARHDRSZ;
338         StringInfoData buf;
339         int i;
340
341         /* check for "--" in string or "-" at the end */
342         for (i = 1; i < len; i++)
343                 if ((VARDATA(arg)[i] == '-' && VARDATA(arg)[i - 1] == '-')
344                         || (VARDATA(arg)[i] == '-' && i == len - 1))
345                                         ereport(ERROR,
346                                                         (errcode(ERRCODE_INVALID_XML_COMMENT),
347                                                          errmsg("invalid XML comment")));
348
349         initStringInfo(&buf);
350         appendStringInfo(&buf, "<!--");
351         appendStringInfoText(&buf, arg);
352         appendStringInfo(&buf, "-->");
353
354         PG_RETURN_XML_P(stringinfo_to_xmltype(&buf));
355 #else
356         NO_XML_SUPPORT();
357         return 0;
358 #endif
359 }
360
361
362 Datum
363 texttoxml(PG_FUNCTION_ARGS)
364 {
365         text       *data = PG_GETARG_TEXT_P(0);
366
367         PG_RETURN_XML_P(xmlparse(data, false, true));
368 }
369
370
371 xmltype *
372 xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
373 {
374 #ifdef USE_LIBXML
375         XmlExpr    *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
376         int                     i;
377         ListCell   *arg;
378         ListCell   *narg;
379         bool            isnull;
380         xmltype    *result;
381         Datum           value;
382         char       *str;
383
384         xmlBufferPtr buf;
385         xmlTextWriterPtr writer;
386
387         buf = xmlBufferCreate();
388         writer = xmlNewTextWriterMemory(buf, 0);
389
390         xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name);
391
392         i = 0;
393         forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names)
394         {
395                 ExprState       *e = (ExprState *) lfirst(arg);
396                 char    *argname = strVal(lfirst(narg));
397
398                 value = ExecEvalExpr(e, econtext, &isnull, NULL);
399                 if (!isnull)
400                 {
401                         str = OutputFunctionCall(&xmlExpr->named_outfuncs[i], value);
402                         xmlTextWriterWriteAttribute(writer, (xmlChar *) argname, (xmlChar *) str);
403                         pfree(str);
404                 }
405                 i++;
406         }
407
408         foreach(arg, xmlExpr->args)
409         {
410                 ExprState       *e = (ExprState *) lfirst(arg);
411
412                 value = ExecEvalExpr(e, econtext, &isnull, NULL);
413                 if (!isnull)
414                         xmlTextWriterWriteRaw(writer, (xmlChar *) map_sql_value_to_xml_value(value, exprType((Node *) e->expr)));
415         }
416
417         xmlTextWriterEndElement(writer);
418         xmlFreeTextWriter(writer);
419
420         result = xmlBuffer_to_xmltype(buf);
421         xmlBufferFree(buf);
422         return result;
423 #else
424         NO_XML_SUPPORT();
425         return NULL;
426 #endif
427 }
428
429
430 xmltype *
431 xmlparse(text *data, bool is_document, bool preserve_whitespace)
432 {
433 #ifdef USE_LIBXML
434         xmlDocPtr       doc;
435
436         doc = xml_parse(data, is_document, preserve_whitespace, NULL);
437         xmlFreeDoc(doc);
438
439         return (xmltype *) data;
440 #else
441         NO_XML_SUPPORT();
442         return NULL;
443 #endif
444 }
445
446
447 xmltype *
448 xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null)
449 {
450 #ifdef USE_LIBXML
451         xmltype *result;
452         StringInfoData buf;
453
454         if (pg_strncasecmp(target, "xml", 3) == 0)
455                 ereport(ERROR,
456                                 (errcode(ERRCODE_SYNTAX_ERROR), /* really */
457                                  errmsg("invalid XML processing instruction"),
458                                  errdetail("XML processing instruction target name cannot start with \"xml\".")));
459
460         /*
461          * Following the SQL standard, the null check comes after the
462          * syntax check above.
463          */
464         *result_is_null = arg_is_null;
465         if (*result_is_null)
466                 return NULL;            
467
468         initStringInfo(&buf);
469
470         appendStringInfo(&buf, "<?%s", target);
471
472         if (arg != NULL)
473         {
474                 char *string;
475
476                 string = DatumGetCString(DirectFunctionCall1(textout,
477                                                                                                          PointerGetDatum(arg)));
478                 if (strstr(string, "?>") != NULL)
479                 ereport(ERROR,
480                                 (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
481                                  errmsg("invalid XML processing instruction"),
482                                  errdetail("XML processing instruction cannot contain \"?>\".")));
483
484                 appendStringInfoChar(&buf, ' ');
485                 appendStringInfoString(&buf, string + strspn(string, " "));
486                 pfree(string);
487         }
488         appendStringInfoString(&buf, "?>");
489
490         result = stringinfo_to_xmltype(&buf);
491         pfree(buf.data);
492         return result;
493 #else
494         NO_XML_SUPPORT();
495         return NULL;
496 #endif
497 }
498
499
500 xmltype *
501 xmlroot(xmltype *data, text *version, int standalone)
502 {
503 #ifdef USE_LIBXML
504         xmltype    *result;
505         xmlDocPtr       doc;
506         xmlBufferPtr buffer;
507         xmlSaveCtxtPtr save;
508
509         doc = xml_parse((text *) data, true, true, NULL);
510
511         if (version)
512                 doc->version = xmlStrdup(xml_text2xmlChar(version));
513         else
514                 doc->version = NULL;
515
516         switch (standalone)
517         {
518                 case 1:
519                         doc->standalone = 1;
520                         break;
521                 case -1:
522                         doc->standalone = 0;
523                         break;
524                 default:
525                         doc->standalone = -1;
526                         break;
527         }
528
529         buffer = xmlBufferCreate();
530         save = xmlSaveToBuffer(buffer, "UTF-8", 0);
531         xmlSaveDoc(save, doc);
532         xmlSaveClose(save);
533
534         xmlFreeDoc(doc);
535
536         result = cstring_to_xmltype((char *) pg_do_encoding_conversion((unsigned char *) xmlBufferContent(buffer),
537                                                                                                                                    xmlBufferLength(buffer),
538                                                                                                                                    PG_UTF8,
539                                                                                                                                    GetDatabaseEncoding()));
540         xmlBufferFree(buffer);
541         return result;
542 #else
543         NO_XML_SUPPORT();
544         return NULL;
545 #endif
546 }
547
548
549 /*
550  * Validate document (given as string) against DTD (given as external link)
551  * TODO !!! use text instead of cstring for second arg
552  * TODO allow passing DTD as a string value (not only as an URI)
553  * TODO redesign (see comment with '!!!' below)
554  */
555 Datum
556 xmlvalidate(PG_FUNCTION_ARGS)
557 {
558 #ifdef USE_LIBXML
559         text                            *data = PG_GETARG_TEXT_P(0);
560         text                            *dtdOrUri = PG_GETARG_TEXT_P(1);
561         bool                            result = false;
562         xmlParserCtxtPtr        ctxt = NULL;
563         xmlDocPtr                       doc = NULL;
564         xmlDtdPtr                       dtd = NULL;
565
566         xml_init();
567
568         /* We use a PG_TRY block to ensure libxml is cleaned up on error */
569         PG_TRY();
570         {
571                 ctxt = xmlNewParserCtxt();
572                 if (ctxt == NULL)
573                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
574                                                 "could not allocate parser context", ctxt);
575
576                 doc = xmlCtxtReadMemory(ctxt, (char *) VARDATA(data),
577                                                                 VARSIZE(data) - VARHDRSZ,
578                                                                 PG_XML_DEFAULT_URI, NULL, 0);
579                 if (doc == NULL)
580                         xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
581                                                 "could not parse XML data", ctxt);
582
583 #if 0
584                 uri = xmlCreateURI();
585                 elog(NOTICE, "dtd - %s", dtdOrUri);
586                 dtd = palloc(sizeof(xmlDtdPtr));
587                 uri = xmlParseURI(dtdOrUri);
588                 if (uri == NULL)
589                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
590                                                 "not implemented yet... (TODO)", ctxt);
591                 else
592 #endif
593                         dtd = xmlParseDTD(NULL, xml_text2xmlChar(dtdOrUri));
594
595                 if (dtd == NULL)
596                         xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
597                                                 "could not load DTD", ctxt);
598
599                 if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) == 1)
600                         result = true;
601
602                 if (!result)
603                         xml_ereport(NOTICE, ERRCODE_INVALID_XML_DOCUMENT,
604                                                 "validation against DTD failed", ctxt);
605
606 #if 0
607                 if (uri)
608                         xmlFreeURI(uri);
609 #endif
610                 if (dtd)
611                         xmlFreeDtd(dtd);
612                 if (doc)
613                         xmlFreeDoc(doc);
614                 if (ctxt)
615                         xmlFreeParserCtxt(ctxt);
616                 xmlCleanupParser();
617         }
618         PG_CATCH();
619         {
620 #if 0
621                 if (uri)
622                         xmlFreeURI(uri);
623 #endif
624                 if (dtd)
625                         xmlFreeDtd(dtd);
626                 if (doc)
627                         xmlFreeDoc(doc);
628                 if (ctxt)
629                         xmlFreeParserCtxt(ctxt);
630                 xmlCleanupParser();
631
632                 PG_RE_THROW();
633         }
634         PG_END_TRY();
635
636         PG_RETURN_BOOL(result);
637 #else /* not USE_LIBXML */
638         NO_XML_SUPPORT();
639         return 0;
640 #endif /* not USE_LIBXML */
641 }
642
643
644 bool
645 xml_is_document(xmltype *arg)
646 {
647 #ifdef USE_LIBXML
648         bool            result;
649         xmlDocPtr       doc = NULL;
650         MemoryContext ccxt = CurrentMemoryContext;
651
652         PG_TRY();
653         {
654                 doc = xml_parse((text *) arg, true, true, NULL);
655                 result = true;
656         }
657         PG_CATCH();
658         {
659                 ErrorData *errdata;
660                 MemoryContext ecxt;
661
662                 ecxt = MemoryContextSwitchTo(ccxt);
663                 errdata = CopyErrorData();
664                 if (errdata->sqlerrcode == ERRCODE_INVALID_XML_DOCUMENT)
665                 {
666                         FlushErrorState();
667                         result = false;
668                 }
669                 else
670                 {
671                         MemoryContextSwitchTo(ecxt);
672                         PG_RE_THROW();
673                 }
674         }
675         PG_END_TRY();
676
677         if (doc)
678                 xmlFreeDoc(doc);
679
680         return result;
681 #else /* not USE_LIBXML */
682         NO_XML_SUPPORT();
683         return false;
684 #endif /* not USE_LIBXML */
685 }
686
687
688 #ifdef USE_LIBXML
689
690 /*
691  * Container for some init stuff (not good design!)
692  * TODO xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and check)
693  */
694 static void
695 xml_init(void)
696 {
697         /*
698          * Currently, we have no pure UTF-8 support for internals -- check
699          * if we can work.
700          */
701         if (sizeof (char) != sizeof (xmlChar))
702                 ereport(ERROR,
703                                 (errmsg("could not initialize XML library"),
704                                  errdetail("libxml2 has incompatible char type: sizeof(char)=%u, sizeof(xmlChar)=%u.",
705                                                    (int) sizeof(char), (int) sizeof(xmlChar))));
706
707         if (xml_err_buf == NULL)
708         {
709                 /* First time through: create error buffer in permanent context */
710                 MemoryContext oldcontext;
711
712                 oldcontext = MemoryContextSwitchTo(TopMemoryContext);
713                 xml_err_buf = makeStringInfo();
714                 MemoryContextSwitchTo(oldcontext);
715         }
716         else
717         {
718                 /* Reset pre-existing buffer to empty */
719                 xml_err_buf->data[0] = '\0';
720                 xml_err_buf->len = 0;
721         }
722         /* Now that xml_err_buf exists, safe to call xml_errorHandler */
723         xmlSetGenericErrorFunc(NULL, xml_errorHandler);
724
725 #ifdef NOT_USED
726         /*
727          * FIXME: This doesn't work because libxml assumes that whatever
728          * libxml allocates, only libxml will free, so we can't just drop
729          * memory contexts behind it.  This needs to be refined.
730          */
731         xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup);
732 #endif
733         xmlInitParser();
734         LIBXML_TEST_VERSION;
735 }
736
737
738 /*
739  * SQL/XML allows storing "XML documents" or "XML content".  "XML
740  * documents" are specified by the XML specification and are parsed
741  * easily by libxml.  "XML content" is specified by SQL/XML as the
742  * production "XMLDecl? content".  But libxml can only parse the
743  * "content" part, so we have to parse the XML declaration ourselves
744  * to complete this.
745  */
746
747 #define CHECK_XML_SPACE(p) if (!xmlIsBlank_ch(*(p))) return XML_ERR_SPACE_REQUIRED
748 #define SKIP_XML_SPACE(p) while (xmlIsBlank_ch(*(p))) (p)++
749
750 static int
751 parse_xml_decl(const xmlChar *str, size_t *lenp, xmlChar **version, xmlChar **encoding, int *standalone)
752 {
753         const xmlChar *p;
754         const xmlChar *save_p;
755         size_t          len;
756
757         p = str;
758
759         if (version)
760                 *version = NULL;
761         if (encoding)
762                 *encoding = NULL;
763         if (standalone)
764                 *standalone = -1;
765
766         if (xmlStrncmp(p, (xmlChar *)"<?xml", 5) != 0)
767                 goto finished;
768
769         p += 5;
770
771         /* version */
772         CHECK_XML_SPACE(p);
773         SKIP_XML_SPACE(p);
774         if (xmlStrncmp(p, (xmlChar *)"version", 7) != 0)
775                 return XML_ERR_VERSION_MISSING;
776         p += 7;
777         SKIP_XML_SPACE(p);
778         if (*p != '=')
779                 return XML_ERR_VERSION_MISSING;
780         p += 1;
781         SKIP_XML_SPACE(p);
782
783         if (*p == '\'' || *p == '"')
784         {
785                 const xmlChar *q;
786
787                 q = xmlStrchr(p + 1, *p);
788                 if (!q)
789                         return XML_ERR_VERSION_MISSING;
790
791                 if (version)
792                         *version = xmlStrndup(p + 1, q - p - 1);
793                 p = q + 1;
794         }
795         else
796                 return XML_ERR_VERSION_MISSING;
797
798         /* encoding */
799         save_p = p;
800         SKIP_XML_SPACE(p);
801         if (xmlStrncmp(p, (xmlChar *)"encoding", 8) == 0)
802         {
803                 CHECK_XML_SPACE(save_p);
804                 p += 8;
805                 SKIP_XML_SPACE(p);
806                 if (*p != '=')
807                         return XML_ERR_MISSING_ENCODING;
808                 p += 1;
809                 SKIP_XML_SPACE(p);
810
811                 if (*p == '\'' || *p == '"')
812                 {
813                         const xmlChar *q;
814
815                         q = xmlStrchr(p + 1, *p);
816                         if (!q)
817                                 return XML_ERR_MISSING_ENCODING;
818
819                         if (encoding)
820                         *encoding = xmlStrndup(p + 1, q - p - 1);
821                         p = q + 1;
822                 }
823                 else
824                         return XML_ERR_MISSING_ENCODING;
825         }
826         else
827         {
828                 p = save_p;
829         }
830
831         /* standalone */
832         save_p = p;
833         SKIP_XML_SPACE(p);
834         if (xmlStrncmp(p, (xmlChar *)"standalone", 10) == 0)
835         {
836                 CHECK_XML_SPACE(save_p);
837                 p += 10;
838                 SKIP_XML_SPACE(p);
839                 if (*p != '=')
840                         return XML_ERR_STANDALONE_VALUE;
841                 p += 1;
842                 SKIP_XML_SPACE(p);
843                 if (xmlStrncmp(p, (xmlChar *)"'yes'", 5) == 0 || xmlStrncmp(p, (xmlChar *)"\"yes\"", 5) == 0)
844                 {
845                         *standalone = 1;
846                         p += 5;
847                 }
848                 else if (xmlStrncmp(p, (xmlChar *)"'no'", 4) == 0 || xmlStrncmp(p, (xmlChar *)"\"no\"", 4) == 0)
849                 {
850                         *standalone = 0;
851                         p += 4;
852                 }
853                 else
854                         return XML_ERR_STANDALONE_VALUE;
855         }
856         else
857         {
858                 p = save_p;
859         }
860
861         SKIP_XML_SPACE(p);
862         if (xmlStrncmp(p, (xmlChar *)"?>", 2) != 0)
863                 return XML_ERR_XMLDECL_NOT_FINISHED;
864         p += 2;
865
866 finished:
867         len = p - str;
868
869         for (p = str; p < str + len; p++)
870                 if (*p > 127)
871                         return XML_ERR_INVALID_CHAR;
872
873         if (lenp)
874                 *lenp = len;
875
876         return XML_ERR_OK;
877 }
878
879
880 /*
881  * Convert a C string to XML internal representation
882  *
883  * TODO maybe, libxml2's xmlreader is better? (do not construct DOM, yet do not use SAX - see xml_reader.c)
884  * TODO what about internal URI for docs? (see PG_XML_DEFAULT_URI below)
885  */
886 static xmlDocPtr
887 xml_parse(text *data, bool is_document, bool preserve_whitespace, xmlChar *encoding)
888 {
889         int32                           len;
890         xmlChar                         *string;
891         xmlChar                         *utf8string;
892         xmlParserCtxtPtr        ctxt = NULL;
893         xmlDocPtr                       doc = NULL;
894
895         len = VARSIZE(data) - VARHDRSZ; /* will be useful later */
896         string = xml_text2xmlChar(data);
897
898         utf8string = pg_do_encoding_conversion(string,
899                                                                                    len,
900                                                                                    encoding
901                                                                                    ? pg_char_to_encoding((char *) encoding)
902                                                                                    : GetDatabaseEncoding(),
903                                                                                    PG_UTF8);
904
905         xml_init();
906
907         /* We use a PG_TRY block to ensure libxml is cleaned up on error */
908         PG_TRY();
909         {
910                 ctxt = xmlNewParserCtxt();
911                 if (ctxt == NULL)
912                         xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR,
913                                                 "could not allocate parser context", ctxt);
914
915                 if (is_document)
916                 {
917                         /*
918                          * Note, that here we try to apply DTD defaults
919                          * (XML_PARSE_DTDATTR) according to SQL/XML:10.16.7.d:
920                          * 'Default valies defined by internal DTD are applied'.
921                          * As for external DTDs, we try to support them too, (see
922                          * SQL/XML:10.16.7.e)
923                          */
924                         doc = xmlCtxtReadDoc(ctxt, utf8string,
925                                                                  PG_XML_DEFAULT_URI,
926                                                                  "UTF-8",
927                                                                         XML_PARSE_NOENT | XML_PARSE_DTDATTR
928                                                                         | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS));
929                         if (doc == NULL)
930                                 xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT,
931                                                         "invalid XML document", ctxt);
932                 }
933                 else
934                 {
935                         int                     res_code;
936                         size_t count;
937                         xmlChar    *version = NULL;
938                         int standalone = -1;
939
940                         doc = xmlNewDoc(NULL);
941
942                         res_code = parse_xml_decl(utf8string, &count, &version, NULL, &standalone);
943
944                         if (res_code == 0)
945                                 res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, utf8string + count, NULL);
946                         if (res_code != 0)
947                                 xml_ereport_by_code(ERROR, ERRCODE_INVALID_XML_CONTENT,
948                                                                         "invalid XML content", res_code);
949
950                         doc->version = xmlStrdup(version);
951                         doc->encoding = xmlStrdup((xmlChar *) "UTF-8");
952                         doc->standalone = standalone;
953                 }
954
955                 if (ctxt)
956                         xmlFreeParserCtxt(ctxt);
957                 xmlCleanupParser();
958         }
959         PG_CATCH();
960         {
961                 if (doc)
962                         xmlFreeDoc(doc);
963                 doc = NULL;
964                 if (ctxt)
965                         xmlFreeParserCtxt(ctxt);
966                 xmlCleanupParser();
967
968                 PG_RE_THROW();
969         }
970         PG_END_TRY();
971
972         return doc;
973 }
974
975
976 /*
977  * xmlChar<->text convertions
978  */
979 static xmlChar *
980 xml_text2xmlChar(text *in)
981 {
982         int32           len = VARSIZE(in) - VARHDRSZ;
983         xmlChar         *res;
984
985         res = palloc(len + 1);
986         memcpy(res, VARDATA(in), len);
987         res[len] = '\0';
988
989         return(res);
990 }
991
992
993 #ifdef NOT_USED
994 /*
995  * Wrappers for memory management functions
996  */
997 static void *
998 xml_palloc(size_t size)
999 {
1000         return palloc(size);
1001 }
1002
1003
1004 static void *
1005 xml_repalloc(void *ptr, size_t size)
1006 {
1007         return repalloc(ptr, size);
1008 }
1009
1010
1011 static void
1012 xml_pfree(void *ptr)
1013 {
1014         pfree(ptr);
1015 }
1016
1017
1018 static char *
1019 xml_pstrdup(const char *string)
1020 {
1021         return pstrdup(string);
1022 }
1023 #endif /* NOT_USED */
1024
1025
1026 /*
1027  * Wrapper for "ereport" function.
1028  * Adds detail - libxml's native error message, if any.
1029  */
1030 static void
1031 xml_ereport(int level, int sqlcode,
1032                         const char *msg, void *ctxt)
1033 {
1034         xmlErrorPtr libxmlErr = NULL;
1035
1036         if (xml_err_buf->len > 0)
1037         {
1038                 ereport(DEBUG1,
1039                                 (errmsg("%s", xml_err_buf->data)));
1040                 xml_err_buf->data[0] = '\0';
1041                 xml_err_buf->len = 0;
1042         }
1043
1044         if (ctxt != NULL)
1045                 libxmlErr = xmlCtxtGetLastError(ctxt);
1046
1047         if (libxmlErr == NULL)
1048         {
1049                 ereport(level,
1050                                 (errcode(sqlcode),
1051                                  errmsg("%s", msg)));
1052         }
1053         else
1054         {
1055                 /* as usual, libxml error message contains '\n'; get rid of it */
1056                 char *xmlErrDetail;
1057                 int xmlErrLen, i;
1058
1059                 xmlErrDetail = pstrdup(libxmlErr->message);
1060                 xmlErrLen = strlen(xmlErrDetail);
1061                 for (i = 0; i < xmlErrLen; i++)
1062                 {
1063                         if (xmlErrDetail[i] == '\n')
1064                                 xmlErrDetail[i] = '.';
1065                 }
1066                 ereport(level,
1067                                 (errcode(sqlcode),
1068                                  errmsg("%s", msg),
1069                                  errdetail("%s", xmlErrDetail)));
1070         }
1071 }
1072
1073
1074 /*
1075  * Error handler for libxml error messages
1076  */
1077 static void
1078 xml_errorHandler(void *ctxt, const char *msg,...)
1079 {
1080         /* Append the formatted text to xml_err_buf */
1081         for (;;)
1082         {
1083                 va_list         args;
1084                 bool            success;
1085
1086                 /* Try to format the data. */
1087                 va_start(args, msg);
1088                 success = appendStringInfoVA(xml_err_buf, msg, args);
1089                 va_end(args);
1090
1091                 if (success)
1092                         break;
1093
1094                 /* Double the buffer size and try again. */
1095                 enlargeStringInfo(xml_err_buf, xml_err_buf->maxlen);
1096         }
1097 }
1098
1099
1100 /*
1101  * Return error message by libxml error code
1102  * TODO make them closer to recommendations from Postgres manual
1103  */
1104 static void
1105 xml_ereport_by_code(int level, int sqlcode,
1106                                         const char *msg, int code)
1107 {
1108     const char *det;
1109
1110         if (xml_err_buf->len > 0)
1111         {
1112                 ereport(DEBUG1,
1113                                 (errmsg("%s", xml_err_buf->data)));
1114                 xml_err_buf->data[0] = '\0';
1115                 xml_err_buf->len = 0;
1116         }
1117
1118     switch (code)
1119         {
1120         case XML_ERR_INTERNAL_ERROR:
1121             det = "libxml internal error";
1122             break;
1123         case XML_ERR_ENTITY_LOOP:
1124             det = "Detected an entity reference loop";
1125             break;
1126         case XML_ERR_ENTITY_NOT_STARTED:
1127             det = "EntityValue: \" or ' expected";
1128             break;
1129         case XML_ERR_ENTITY_NOT_FINISHED:
1130             det = "EntityValue: \" or ' expected";
1131             break;
1132         case XML_ERR_ATTRIBUTE_NOT_STARTED:
1133             det = "AttValue: \" or ' expected";
1134             break;
1135         case XML_ERR_LT_IN_ATTRIBUTE:
1136             det = "Unescaped '<' not allowed in attributes values";
1137             break;
1138         case XML_ERR_LITERAL_NOT_STARTED:
1139             det = "SystemLiteral \" or ' expected";
1140             break;
1141         case XML_ERR_LITERAL_NOT_FINISHED:
1142             det = "Unfinished System or Public ID \" or ' expected";
1143             break;
1144         case XML_ERR_MISPLACED_CDATA_END:
1145             det = "Sequence ']]>' not allowed in content";
1146             break;
1147         case XML_ERR_URI_REQUIRED:
1148             det = "SYSTEM or PUBLIC, the URI is missing";
1149             break;
1150         case XML_ERR_PUBID_REQUIRED:
1151             det = "PUBLIC, the Public Identifier is missing";
1152             break;
1153         case XML_ERR_HYPHEN_IN_COMMENT:
1154             det = "Comment must not contain '--' (double-hyphen)";
1155             break;
1156         case XML_ERR_PI_NOT_STARTED:
1157             det = "xmlParsePI : no target name";
1158             break;
1159         case XML_ERR_RESERVED_XML_NAME:
1160             det = "Invalid PI name";
1161             break;
1162         case XML_ERR_NOTATION_NOT_STARTED:
1163             det = "NOTATION: Name expected here";
1164             break;
1165         case XML_ERR_NOTATION_NOT_FINISHED:
1166             det = "'>' required to close NOTATION declaration";
1167             break;
1168         case XML_ERR_VALUE_REQUIRED:
1169             det = "Entity value required";
1170             break;
1171         case XML_ERR_URI_FRAGMENT:
1172             det = "Fragment not allowed";
1173             break;
1174         case XML_ERR_ATTLIST_NOT_STARTED:
1175             det = "'(' required to start ATTLIST enumeration";
1176             break;
1177         case XML_ERR_NMTOKEN_REQUIRED:
1178             det = "NmToken expected in ATTLIST enumeration";
1179             break;
1180         case XML_ERR_ATTLIST_NOT_FINISHED:
1181             det = "')' required to finish ATTLIST enumeration";
1182             break;
1183         case XML_ERR_MIXED_NOT_STARTED:
1184             det = "MixedContentDecl : '|' or ')*' expected";
1185             break;
1186         case XML_ERR_PCDATA_REQUIRED:
1187             det = "MixedContentDecl : '#PCDATA' expected";
1188             break;
1189         case XML_ERR_ELEMCONTENT_NOT_STARTED:
1190             det = "ContentDecl : Name or '(' expected";
1191             break;
1192         case XML_ERR_ELEMCONTENT_NOT_FINISHED:
1193             det = "ContentDecl : ',' '|' or ')' expected";
1194             break;
1195         case XML_ERR_PEREF_IN_INT_SUBSET:
1196             det = "PEReference: forbidden within markup decl in internal subset";
1197             break;
1198         case XML_ERR_GT_REQUIRED:
1199             det = "Expected '>'";
1200             break;
1201         case XML_ERR_CONDSEC_INVALID:
1202             det = "XML conditional section '[' expected";
1203             break;
1204         case XML_ERR_EXT_SUBSET_NOT_FINISHED:
1205             det = "Content error in the external subset";
1206             break;
1207         case XML_ERR_CONDSEC_INVALID_KEYWORD:
1208             det = "conditional section INCLUDE or IGNORE keyword expected";
1209             break;
1210         case XML_ERR_CONDSEC_NOT_FINISHED:
1211             det = "XML conditional section not closed";
1212             break;
1213         case XML_ERR_XMLDECL_NOT_STARTED:
1214             det = "Text declaration '<?xml' required";
1215             break;
1216         case XML_ERR_XMLDECL_NOT_FINISHED:
1217             det = "parsing XML declaration: '?>' expected";
1218             break;
1219         case XML_ERR_EXT_ENTITY_STANDALONE:
1220             det = "external parsed entities cannot be standalone";
1221             break;
1222         case XML_ERR_ENTITYREF_SEMICOL_MISSING:
1223             det = "EntityRef: expecting ';'";
1224             break;
1225         case XML_ERR_DOCTYPE_NOT_FINISHED:
1226             det = "DOCTYPE improperly terminated";
1227             break;
1228         case XML_ERR_LTSLASH_REQUIRED:
1229             det = "EndTag: '</' not found";
1230             break;
1231         case XML_ERR_EQUAL_REQUIRED:
1232             det = "Expected '='";
1233             break;
1234         case XML_ERR_STRING_NOT_CLOSED:
1235             det = "String not closed expecting \" or '";
1236             break;
1237         case XML_ERR_STRING_NOT_STARTED:
1238             det = "String not started expecting ' or \"";
1239             break;
1240         case XML_ERR_ENCODING_NAME:
1241             det = "Invalid XML encoding name";
1242             break;
1243         case XML_ERR_STANDALONE_VALUE:
1244             det = "Standalone accepts only 'yes' or 'no'";
1245             break;
1246         case XML_ERR_DOCUMENT_EMPTY:
1247             det = "Document is empty";
1248             break;
1249         case XML_ERR_DOCUMENT_END:
1250             det = "Extra content at the end of the document";
1251             break;
1252         case XML_ERR_NOT_WELL_BALANCED:
1253             det = "Chunk is not well balanced";
1254             break;
1255         case XML_ERR_EXTRA_CONTENT:
1256             det = "Extra content at the end of well balanced chunk";
1257             break;
1258         case XML_ERR_VERSION_MISSING:
1259             det = "Malformed declaration expecting version";
1260             break;
1261         /* more err codes... Please, keep the order! */
1262         case XML_ERR_ATTRIBUTE_WITHOUT_VALUE: /* 41 */
1263                 det ="Attribute without value";
1264                 break;
1265         case XML_ERR_ATTRIBUTE_REDEFINED:
1266                 det ="Attribute defined more than once in the same element";
1267                 break;
1268         case XML_ERR_COMMENT_NOT_FINISHED: /* 45 */
1269             det = "Comment is not finished";
1270             break;
1271         case XML_ERR_NAME_REQUIRED: /* 68 */
1272             det = "Element name not found";
1273             break;
1274         case XML_ERR_TAG_NOT_FINISHED: /* 77 */
1275             det = "Closing tag not found";
1276             break;
1277         default:
1278             det = "Unrecognized libxml error code: %d";
1279                         break;
1280         }
1281
1282         ereport(level,
1283                         (errcode(sqlcode),
1284                          errmsg("%s", msg),
1285                          errdetail(det, code)));
1286 }
1287
1288
1289 /*
1290  * Convert one char in the current server encoding to a Unicode codepoint.
1291  */
1292 static pg_wchar
1293 sqlchar_to_unicode(char *s)
1294 {
1295         char *utf8string;
1296         pg_wchar ret[2];                        /* need space for trailing zero */
1297
1298         utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s,
1299                                                                                                         pg_mblen(s),
1300                                                                                                         GetDatabaseEncoding(),
1301                                                                                                         PG_UTF8);
1302
1303         pg_encoding_mb2wchar_with_len(PG_UTF8, utf8string, ret, pg_mblen(s));
1304
1305         return ret[0];
1306 }
1307
1308
1309 static bool
1310 is_valid_xml_namefirst(pg_wchar c)
1311 {
1312         /* (Letter | '_' | ':') */
1313         return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
1314                         || c == '_' || c == ':');
1315 }
1316
1317
1318 static bool
1319 is_valid_xml_namechar(pg_wchar c)
1320 {
1321         /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */
1322         return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
1323                         || xmlIsDigitQ(c)
1324                         || c == '.' || c == '-' || c == '_' || c == ':'
1325                         || xmlIsCombiningQ(c)
1326                         || xmlIsExtenderQ(c));
1327 }
1328 #endif /* USE_LIBXML */
1329
1330
1331 /*
1332  * Map SQL identifier to XML name; see SQL/XML:2003 section 9.1.
1333  */
1334 char *
1335 map_sql_identifier_to_xml_name(char *ident, bool fully_escaped)
1336 {
1337 #ifdef USE_LIBXML
1338         StringInfoData buf;
1339         char *p;
1340
1341         initStringInfo(&buf);
1342
1343         for (p = ident; *p; p += pg_mblen(p))
1344         {
1345                 if (*p == ':' && (p == ident || fully_escaped))
1346                         appendStringInfo(&buf, "_x003A_");
1347                 else if (*p == '_' && *(p+1) == 'x')
1348                         appendStringInfo(&buf, "_x005F_");
1349                 else if (fully_escaped && p == ident &&
1350                                  pg_strncasecmp(p, "xml", 3) == 0)
1351                 {
1352                         if (*p == 'x')
1353                                 appendStringInfo(&buf, "_x0078_");
1354                         else
1355                                 appendStringInfo(&buf, "_x0058_");
1356                 }
1357                 else
1358                 {
1359                         pg_wchar u = sqlchar_to_unicode(p);
1360
1361                         if ((p == ident)
1362                                 ? !is_valid_xml_namefirst(u)
1363                                 : !is_valid_xml_namechar(u))
1364                                 appendStringInfo(&buf, "_x%04X_", (unsigned int) u);
1365                         else
1366                                 appendBinaryStringInfo(&buf, p, pg_mblen(p));
1367                 }
1368         }
1369
1370         return buf.data;
1371 #else /* not USE_LIBXML */
1372         NO_XML_SUPPORT();
1373         return NULL;
1374 #endif /* not USE_LIBXML */
1375 }
1376
1377
1378 /*
1379  * Map a Unicode codepoint into the current server encoding.
1380  */
1381 static char *
1382 unicode_to_sqlchar(pg_wchar c)
1383 {
1384         static unsigned char utf8string[4];
1385
1386         if (c <= 0x7F)
1387         {
1388                 utf8string[0] = c;
1389         }
1390         else if (c <= 0x7FF)
1391         {
1392                 utf8string[0] = 0xC0 | ((c >> 6) & 0x1F);
1393                 utf8string[1] = 0x80 | (c & 0x3F);
1394         }
1395         else if (c <= 0xFFFF)
1396         {
1397                 utf8string[0] = 0xE0 | ((c >> 12) & 0x0F);
1398                 utf8string[1] = 0x80 | ((c >> 6) & 0x3F);
1399                 utf8string[2] = 0x80 | (c & 0x3F);
1400         }
1401         else
1402         {
1403                 utf8string[0] = 0xF0 | ((c >> 18) & 0x07);
1404                 utf8string[1] = 0x80 | ((c >> 12) & 0x3F);
1405                 utf8string[2] = 0x80 | ((c >> 6) & 0x3F);
1406                 utf8string[3] = 0x80 | (c & 0x3F);
1407         }
1408
1409         return (char *) pg_do_encoding_conversion(utf8string,
1410                                                                                           pg_mblen((char *) utf8string),
1411                                                                                           PG_UTF8,
1412                                                                                           GetDatabaseEncoding());
1413 }
1414
1415
1416 /*
1417  * Map XML name to SQL identifier; see SQL/XML:2003 section 9.17.
1418  */
1419 char *
1420 map_xml_name_to_sql_identifier(char *name)
1421 {
1422         StringInfoData buf;
1423         char *p;
1424
1425         initStringInfo(&buf);
1426
1427         for (p = name; *p; p += pg_mblen(p))
1428         {
1429                 if (*p == '_' && *(p+1) == 'x'
1430                         && isxdigit((unsigned char) *(p+2))
1431                         && isxdigit((unsigned char) *(p+3))
1432                         && isxdigit((unsigned char) *(p+4))
1433                         && isxdigit((unsigned char) *(p+5))
1434                         && *(p+6) == '_')
1435                 {
1436                         unsigned int u;
1437
1438                         sscanf(p + 2, "%X", &u);
1439                         appendStringInfoString(&buf, unicode_to_sqlchar(u));
1440                         p += 6;
1441                 }
1442                 else
1443                         appendBinaryStringInfo(&buf, p, pg_mblen(p));
1444         }
1445
1446         return buf.data;
1447 }
1448
1449
1450 /*
1451  * Map SQL value to XML value; see SQL/XML:2003 section 9.16.
1452  */
1453 char *
1454 map_sql_value_to_xml_value(Datum value, Oid type)
1455 {
1456         StringInfoData buf;
1457
1458         initStringInfo(&buf);
1459
1460         if (is_array_type(type))
1461         {
1462                 int i;
1463                 ArrayType *array;
1464                 Oid elmtype;
1465                 int16 elmlen;
1466                 bool elmbyval;
1467                 char elmalign;
1468
1469                 array = DatumGetArrayTypeP(value);
1470
1471                 /* TODO: need some code-fu here to remove this limitation */
1472                 if (ARR_NDIM(array) != 1)
1473                         ereport(ERROR,
1474                                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1475                                          errmsg("only supported for one-dimensional array")));
1476
1477                 elmtype = ARR_ELEMTYPE(array);
1478                 get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
1479
1480                 for (i = ARR_LBOUND(array)[0];
1481                          i < ARR_LBOUND(array)[0] + ARR_DIMS(array)[0];
1482                          i++)
1483                 {
1484                         Datum subval;
1485                         bool isnull;
1486
1487                         subval = array_ref(array, 1, &i, -1, elmlen, elmbyval, elmalign, &isnull);
1488                         appendStringInfoString(&buf, "<element>");
1489                         appendStringInfoString(&buf, map_sql_value_to_xml_value(subval, elmtype));
1490                         appendStringInfoString(&buf, "</element>");
1491                 }
1492         }
1493         else
1494         {
1495                 Oid typeOut;
1496                 bool isvarlena;
1497                 char *p, *str;
1498
1499                 getTypeOutputInfo(type, &typeOut, &isvarlena);
1500                 str = OidOutputFunctionCall(typeOut, value);
1501
1502                 if (type == XMLOID)
1503                         return str;
1504
1505 #ifdef USE_LIBXML
1506                 if (type == BYTEAOID)
1507                 {
1508                         xmlBufferPtr buf;
1509                         xmlTextWriterPtr writer;
1510                         char *result;
1511
1512                         buf = xmlBufferCreate();
1513                         writer = xmlNewTextWriterMemory(buf, 0);
1514
1515                         if (xmlbinary == XMLBINARY_BASE64)
1516                                 xmlTextWriterWriteBase64(writer, VARDATA(value), 0, VARSIZE(value) - VARHDRSZ);
1517                         else
1518                                 xmlTextWriterWriteBinHex(writer, VARDATA(value), 0, VARSIZE(value) - VARHDRSZ);
1519
1520                         xmlFreeTextWriter(writer);
1521                         result = pstrdup((const char *) xmlBufferContent(buf));
1522                         xmlBufferFree(buf);
1523                         return result;
1524                 }
1525 #endif /* USE_LIBXML */
1526
1527                 for (p = str; *p; p += pg_mblen(p))
1528                 {
1529                         switch (*p)
1530                         {
1531                                 case '&':
1532                                         appendStringInfo(&buf, "&amp;");
1533                                         break;
1534                                 case '<':
1535                                         appendStringInfo(&buf, "&lt;");
1536                                         break;
1537                                 case '>':
1538                                         appendStringInfo(&buf, "&gt;");
1539                                         break;
1540                                 case '\r':
1541                                         appendStringInfo(&buf, "&#x0d;");
1542                                         break;
1543                                 default:
1544                                         appendBinaryStringInfo(&buf, p, pg_mblen(p));
1545                                         break;
1546                         }
1547                 }
1548         }
1549
1550         return buf.data;
1551 }