Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_subr / xml.c
blob9c213bf6a26f9b92e701405669f2add683712cb5
1 /*
2 * xml.c: xml helper code shared among the Subversion libraries.
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <string.h>
22 #include <assert.h>
24 #include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */
25 #include "svn_pools.h"
26 #include "svn_xml.h"
27 #include "svn_error.h"
28 #include "svn_ctype.h"
29 #include "utf_impl.h"
31 #ifdef SVN_HAVE_OLD_EXPAT
32 #include <xmlparse.h>
33 #else
34 #include <expat.h>
35 #endif
37 #ifdef XML_UNICODE
38 #error Expat is unusable -- it has been compiled for wide characters
39 #endif
41 /* The private internals for a parser object. */
42 struct svn_xml_parser_t
44 /** the expat parser */
45 XML_Parser parser;
47 /** the SVN callbacks to call from the Expat callbacks */
48 svn_xml_start_elem start_handler;
49 svn_xml_end_elem end_handler;
50 svn_xml_char_data data_handler;
52 /** the user's baton for private data */
53 void *baton;
55 /** if non-@c NULL, an error happened while parsing */
56 svn_error_t *error;
58 /** where this object is allocated, so we can free it easily */
59 apr_pool_t *pool;
64 /*** XML character validation ***/
66 svn_boolean_t
67 svn_xml_is_xml_safe(const char *data, apr_size_t len)
69 const char *end = data + len;
70 const char *p;
72 if (! svn_utf__is_valid(data, len))
73 return FALSE;
75 for (p = data; p < end; p++)
77 unsigned char c = *p;
79 if (svn_ctype_iscntrl(c))
81 if ((c != SVN_CTYPE_ASCII_TAB)
82 && (c != SVN_CTYPE_ASCII_LINEFEED)
83 && (c != SVN_CTYPE_ASCII_CARRIAGERETURN)
84 && (c != SVN_CTYPE_ASCII_DELETE))
85 return FALSE;
88 return TRUE;
95 /*** XML escaping. ***/
97 static void
98 xml_escape_cdata(svn_stringbuf_t **outstr,
99 const char *data,
100 apr_size_t len,
101 apr_pool_t *pool)
103 const char *end = data + len;
104 const char *p = data, *q;
106 if (*outstr == NULL)
107 *outstr = svn_stringbuf_create("", pool);
109 while (1)
111 /* Find a character which needs to be quoted and append bytes up
112 to that point. Strictly speaking, '>' only needs to be
113 quoted if it follows "]]", but it's easier to quote it all
114 the time.
116 So, why are we escaping '\r' here? Well, according to the
117 XML spec, '\r\n' gets converted to '\n' during XML parsing.
118 Also, any '\r' not followed by '\n' is converted to '\n'. By
119 golly, if we say we want to escape a '\r', we want to make
120 sure it remains a '\r'! */
121 q = p;
122 while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
123 q++;
124 svn_stringbuf_appendbytes(*outstr, p, q - p);
126 /* We may already be a winner. */
127 if (q == end)
128 break;
130 /* Append the entity reference for the character. */
131 if (*q == '&')
132 svn_stringbuf_appendcstr(*outstr, "&amp;");
133 else if (*q == '<')
134 svn_stringbuf_appendcstr(*outstr, "&lt;");
135 else if (*q == '>')
136 svn_stringbuf_appendcstr(*outstr, "&gt;");
137 else if (*q == '\r')
138 svn_stringbuf_appendcstr(*outstr, "&#13;");
140 p = q + 1;
144 /* Essentially the same as xml_escape_cdata, with the addition of
145 whitespace and quote characters. */
146 static void
147 xml_escape_attr(svn_stringbuf_t **outstr,
148 const char *data,
149 apr_size_t len,
150 apr_pool_t *pool)
152 const char *end = data + len;
153 const char *p = data, *q;
155 if (*outstr == NULL)
156 *outstr = svn_stringbuf_create_ensure(len, pool);
158 while (1)
160 /* Find a character which needs to be quoted and append bytes up
161 to that point. */
162 q = p;
163 while (q < end && *q != '&' && *q != '<' && *q != '>'
164 && *q != '"' && *q != '\'' && *q != '\r'
165 && *q != '\n' && *q != '\t')
166 q++;
167 svn_stringbuf_appendbytes(*outstr, p, q - p);
169 /* We may already be a winner. */
170 if (q == end)
171 break;
173 /* Append the entity reference for the character. */
174 if (*q == '&')
175 svn_stringbuf_appendcstr(*outstr, "&amp;");
176 else if (*q == '<')
177 svn_stringbuf_appendcstr(*outstr, "&lt;");
178 else if (*q == '>')
179 svn_stringbuf_appendcstr(*outstr, "&gt;");
180 else if (*q == '"')
181 svn_stringbuf_appendcstr(*outstr, "&quot;");
182 else if (*q == '\'')
183 svn_stringbuf_appendcstr(*outstr, "&apos;");
184 else if (*q == '\r')
185 svn_stringbuf_appendcstr(*outstr, "&#13;");
186 else if (*q == '\n')
187 svn_stringbuf_appendcstr(*outstr, "&#10;");
188 else if (*q == '\t')
189 svn_stringbuf_appendcstr(*outstr, "&#9;");
191 p = q + 1;
196 void
197 svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
198 const svn_stringbuf_t *string,
199 apr_pool_t *pool)
201 xml_escape_cdata(outstr, string->data, string->len, pool);
205 void
206 svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
207 const svn_string_t *string,
208 apr_pool_t *pool)
210 xml_escape_cdata(outstr, string->data, string->len, pool);
214 void
215 svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
216 const char *string,
217 apr_pool_t *pool)
219 xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool);
223 void
224 svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
225 const svn_stringbuf_t *string,
226 apr_pool_t *pool)
228 xml_escape_attr(outstr, string->data, string->len, pool);
232 void
233 svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
234 const svn_string_t *string,
235 apr_pool_t *pool)
237 xml_escape_attr(outstr, string->data, string->len, pool);
241 void
242 svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
243 const char *string,
244 apr_pool_t *pool)
246 xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool);
250 const char *
251 svn_xml_fuzzy_escape(const char *string, apr_pool_t *pool)
253 const char *end = string + strlen(string);
254 const char *p = string, *q;
255 svn_stringbuf_t *outstr;
256 char escaped_char[6]; /* ? \ u u u \0 */
258 for (q = p; q < end; q++)
260 if (svn_ctype_iscntrl(*q)
261 && ! ((*q == '\n') || (*q == '\r') || (*q == '\t')))
262 break;
265 /* Return original string if no unsafe characters found. */
266 if (q == end)
267 return string;
269 outstr = svn_stringbuf_create("", pool);
270 while (1)
272 q = p;
274 /* Traverse till either unsafe character or eos. */
275 while ((q < end)
276 && ((! svn_ctype_iscntrl(*q))
277 || (*q == '\n') || (*q == '\r') || (*q == '\t')))
278 q++;
280 /* copy chunk before marker */
281 svn_stringbuf_appendbytes(outstr, p, q - p);
283 if (q == end)
284 break;
286 /* Append an escaped version of the unsafe character.
288 ### This format was chosen for consistency with
289 ### svn_utf__cstring_from_utf8_fuzzy(). The two functions
290 ### should probably share code, even though they escape
291 ### different characters.
293 sprintf(escaped_char, "?\\%03u", (unsigned char) *q);
294 svn_stringbuf_appendcstr(outstr, escaped_char);
296 p = q + 1;
299 return outstr->data;
303 /*** Map from the Expat callback types to the SVN XML types. ***/
305 static void expat_start_handler(void *userData,
306 const XML_Char *name,
307 const XML_Char **atts)
309 svn_xml_parser_t *svn_parser = userData;
311 (*svn_parser->start_handler)(svn_parser->baton, name, atts);
314 static void expat_end_handler(void *userData, const XML_Char *name)
316 svn_xml_parser_t *svn_parser = userData;
318 (*svn_parser->end_handler)(svn_parser->baton, name);
321 static void expat_data_handler(void *userData, const XML_Char *s, int len)
323 svn_xml_parser_t *svn_parser = userData;
325 (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len);
329 /*** Making a parser. ***/
331 svn_xml_parser_t *
332 svn_xml_make_parser(void *baton,
333 svn_xml_start_elem start_handler,
334 svn_xml_end_elem end_handler,
335 svn_xml_char_data data_handler,
336 apr_pool_t *pool)
338 svn_xml_parser_t *svn_parser;
339 apr_pool_t *subpool;
341 XML_Parser parser = XML_ParserCreate(NULL);
343 XML_SetElementHandler(parser,
344 start_handler ? expat_start_handler : NULL,
345 end_handler ? expat_end_handler : NULL);
346 XML_SetCharacterDataHandler(parser,
347 data_handler ? expat_data_handler : NULL);
349 /* ### we probably don't want this pool; or at least we should pass it
350 ### to the callbacks and clear it periodically. */
351 subpool = svn_pool_create(pool);
353 svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser));
355 svn_parser->parser = parser;
356 svn_parser->start_handler = start_handler;
357 svn_parser->end_handler = end_handler;
358 svn_parser->data_handler = data_handler;
359 svn_parser->baton = baton;
360 svn_parser->pool = subpool;
362 /* store our parser info as the UserData in the Expat parser */
363 XML_SetUserData(parser, svn_parser);
365 return svn_parser;
369 /* Free a parser */
370 void
371 svn_xml_free_parser(svn_xml_parser_t *svn_parser)
373 /* Free the expat parser */
374 XML_ParserFree(svn_parser->parser);
376 /* Free the subversion parser */
377 svn_pool_destroy(svn_parser->pool);
383 svn_error_t *
384 svn_xml_parse(svn_xml_parser_t *svn_parser,
385 const char *buf,
386 apr_size_t len,
387 svn_boolean_t is_final)
389 svn_error_t *err;
390 int success;
392 /* Parse some xml data */
393 success = XML_Parse(svn_parser->parser, buf, len, is_final);
395 /* If expat choked internally, return its error. */
396 if (! success)
398 /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */
399 long line = XML_GetCurrentLineNumber(svn_parser->parser);
401 err = svn_error_createf
402 (SVN_ERR_XML_MALFORMED, NULL,
403 _("Malformed XML: %s at line %ld"),
404 XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line);
406 /* Kill all parsers and return the expat error */
407 svn_xml_free_parser(svn_parser);
408 return err;
411 /* Did an error occur somewhere *inside* the expat callbacks? */
412 if (svn_parser->error)
414 err = svn_parser->error;
415 svn_xml_free_parser(svn_parser);
416 return err;
419 return SVN_NO_ERROR;
424 void svn_xml_signal_bailout(svn_error_t *error,
425 svn_xml_parser_t *svn_parser)
427 /* This will cause the current XML_Parse() call to finish quickly! */
428 XML_SetElementHandler(svn_parser->parser, NULL, NULL);
429 XML_SetCharacterDataHandler(svn_parser->parser, NULL);
431 /* Once outside of XML_Parse(), the existence of this field will
432 cause svn_delta_parse()'s main read-loop to return error. */
433 svn_parser->error = error;
443 /*** Attribute walking. ***/
445 const char *
446 svn_xml_get_attr_value(const char *name, const char **atts)
448 while (atts && (*atts))
450 if (strcmp(atts[0], name) == 0)
451 return atts[1];
452 else
453 atts += 2; /* continue looping */
456 /* Else no such attribute name seen. */
457 return NULL;
462 /*** Printing XML ***/
464 void
465 svn_xml_make_header(svn_stringbuf_t **str, apr_pool_t *pool)
467 if (*str == NULL)
468 *str = svn_stringbuf_create("", pool);
469 svn_stringbuf_appendcstr(*str,
470 "<?xml version=\"1.0\"?>\n");
475 /*** Creating attribute hashes. ***/
477 /* Combine an existing attribute list ATTS with a HASH that itself
478 represents an attribute list. Iff PRESERVE is true, then no value
479 already in HASH will be changed, else values from ATTS will
480 override previous values in HASH. */
481 static void
482 amalgamate(const char **atts,
483 apr_hash_t *ht,
484 svn_boolean_t preserve,
485 apr_pool_t *pool)
487 const char *key;
489 if (atts)
490 for (key = *atts; key; key = *(++atts))
492 const char *val = *(++atts);
493 size_t keylen;
494 assert(key != NULL);
495 /* kff todo: should we also insist that val be non-null here?
496 Probably. */
498 keylen = strlen(key);
499 if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL))
500 continue;
501 else
502 apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen,
503 val ? apr_pstrdup(pool, val) : NULL);
508 apr_hash_t *
509 svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool)
511 apr_hash_t *ht = apr_hash_make(pool);
512 const char *key;
514 while ((key = va_arg(ap, char *)) != NULL)
516 const char *val = va_arg(ap, const char *);
517 apr_hash_set(ht, key, APR_HASH_KEY_STRING, val);
520 return ht;
524 apr_hash_t *
525 svn_xml_make_att_hash(const char **atts, apr_pool_t *pool)
527 apr_hash_t *ht = apr_hash_make(pool);
528 amalgamate(atts, ht, 0, pool); /* third arg irrelevant in this case */
529 return ht;
533 void
534 svn_xml_hash_atts_overlaying(const char **atts,
535 apr_hash_t *ht,
536 apr_pool_t *pool)
538 amalgamate(atts, ht, 0, pool);
542 void
543 svn_xml_hash_atts_preserving(const char **atts,
544 apr_hash_t *ht,
545 apr_pool_t *pool)
547 amalgamate(atts, ht, 1, pool);
552 /*** Making XML tags. ***/
555 void
556 svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
557 apr_pool_t *pool,
558 enum svn_xml_open_tag_style style,
559 const char *tagname,
560 apr_hash_t *attributes)
562 apr_hash_index_t *hi;
563 apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30;
565 if (*str == NULL)
566 *str = svn_stringbuf_create_ensure(est_size, pool);
568 svn_stringbuf_appendcstr(*str, "<");
569 svn_stringbuf_appendcstr(*str, tagname);
571 for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi))
573 const void *key;
574 void *val;
576 apr_hash_this(hi, &key, NULL, &val);
577 assert(val != NULL);
579 svn_stringbuf_appendcstr(*str, "\n ");
580 svn_stringbuf_appendcstr(*str, key);
581 svn_stringbuf_appendcstr(*str, "=\"");
582 svn_xml_escape_attr_cstring(str, val, pool);
583 svn_stringbuf_appendcstr(*str, "\"");
586 if (style == svn_xml_self_closing)
587 svn_stringbuf_appendcstr(*str, "/");
588 svn_stringbuf_appendcstr(*str, ">");
589 if (style != svn_xml_protect_pcdata)
590 svn_stringbuf_appendcstr(*str, "\n");
594 void
595 svn_xml_make_open_tag_v(svn_stringbuf_t **str,
596 apr_pool_t *pool,
597 enum svn_xml_open_tag_style style,
598 const char *tagname,
599 va_list ap)
601 apr_pool_t *subpool = svn_pool_create(pool);
602 apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool);
604 svn_xml_make_open_tag_hash(str, pool, style, tagname, ht);
605 svn_pool_destroy(subpool);
610 void
611 svn_xml_make_open_tag(svn_stringbuf_t **str,
612 apr_pool_t *pool,
613 enum svn_xml_open_tag_style style,
614 const char *tagname,
615 ...)
617 va_list ap;
619 va_start(ap, tagname);
620 svn_xml_make_open_tag_v(str, pool, style, tagname, ap);
621 va_end(ap);
625 void svn_xml_make_close_tag(svn_stringbuf_t **str,
626 apr_pool_t *pool,
627 const char *tagname)
629 if (*str == NULL)
630 *str = svn_stringbuf_create("", pool);
632 svn_stringbuf_appendcstr(*str, "</");
633 svn_stringbuf_appendcstr(*str, tagname);
634 svn_stringbuf_appendcstr(*str, ">\n");