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 * ====================================================================
24 #include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */
25 #include "svn_pools.h"
27 #include "svn_error.h"
28 #include "svn_ctype.h"
31 #ifdef SVN_HAVE_OLD_EXPAT
38 #error Expat is unusable -- it has been compiled for wide characters
41 /* The private internals for a parser object. */
42 struct svn_xml_parser_t
44 /** the expat 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 */
55 /** if non-@c NULL, an error happened while parsing */
58 /** where this object is allocated, so we can free it easily */
64 /*** XML character validation ***/
67 svn_xml_is_xml_safe(const char *data
, apr_size_t len
)
69 const char *end
= data
+ len
;
72 if (! svn_utf__is_valid(data
, len
))
75 for (p
= data
; p
< end
; 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
))
95 /*** XML escaping. ***/
98 xml_escape_cdata(svn_stringbuf_t
**outstr
,
103 const char *end
= data
+ len
;
104 const char *p
= data
, *q
;
107 *outstr
= svn_stringbuf_create("", pool
);
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
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'! */
122 while (q
< end
&& *q
!= '&' && *q
!= '<' && *q
!= '>' && *q
!= '\r')
124 svn_stringbuf_appendbytes(*outstr
, p
, q
- p
);
126 /* We may already be a winner. */
130 /* Append the entity reference for the character. */
132 svn_stringbuf_appendcstr(*outstr
, "&");
134 svn_stringbuf_appendcstr(*outstr
, "<");
136 svn_stringbuf_appendcstr(*outstr
, ">");
138 svn_stringbuf_appendcstr(*outstr
, " ");
144 /* Essentially the same as xml_escape_cdata, with the addition of
145 whitespace and quote characters. */
147 xml_escape_attr(svn_stringbuf_t
**outstr
,
152 const char *end
= data
+ len
;
153 const char *p
= data
, *q
;
156 *outstr
= svn_stringbuf_create_ensure(len
, pool
);
160 /* Find a character which needs to be quoted and append bytes up
163 while (q
< end
&& *q
!= '&' && *q
!= '<' && *q
!= '>'
164 && *q
!= '"' && *q
!= '\'' && *q
!= '\r'
165 && *q
!= '\n' && *q
!= '\t')
167 svn_stringbuf_appendbytes(*outstr
, p
, q
- p
);
169 /* We may already be a winner. */
173 /* Append the entity reference for the character. */
175 svn_stringbuf_appendcstr(*outstr
, "&");
177 svn_stringbuf_appendcstr(*outstr
, "<");
179 svn_stringbuf_appendcstr(*outstr
, ">");
181 svn_stringbuf_appendcstr(*outstr
, """);
183 svn_stringbuf_appendcstr(*outstr
, "'");
185 svn_stringbuf_appendcstr(*outstr
, " ");
187 svn_stringbuf_appendcstr(*outstr
, " ");
189 svn_stringbuf_appendcstr(*outstr
, "	");
197 svn_xml_escape_cdata_stringbuf(svn_stringbuf_t
**outstr
,
198 const svn_stringbuf_t
*string
,
201 xml_escape_cdata(outstr
, string
->data
, string
->len
, pool
);
206 svn_xml_escape_cdata_string(svn_stringbuf_t
**outstr
,
207 const svn_string_t
*string
,
210 xml_escape_cdata(outstr
, string
->data
, string
->len
, pool
);
215 svn_xml_escape_cdata_cstring(svn_stringbuf_t
**outstr
,
219 xml_escape_cdata(outstr
, string
, (apr_size_t
) strlen(string
), pool
);
224 svn_xml_escape_attr_stringbuf(svn_stringbuf_t
**outstr
,
225 const svn_stringbuf_t
*string
,
228 xml_escape_attr(outstr
, string
->data
, string
->len
, pool
);
233 svn_xml_escape_attr_string(svn_stringbuf_t
**outstr
,
234 const svn_string_t
*string
,
237 xml_escape_attr(outstr
, string
->data
, string
->len
, pool
);
242 svn_xml_escape_attr_cstring(svn_stringbuf_t
**outstr
,
246 xml_escape_attr(outstr
, string
, (apr_size_t
) strlen(string
), pool
);
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')))
265 /* Return original string if no unsafe characters found. */
269 outstr
= svn_stringbuf_create("", pool
);
274 /* Traverse till either unsafe character or eos. */
276 && ((! svn_ctype_iscntrl(*q
))
277 || (*q
== '\n') || (*q
== '\r') || (*q
== '\t')))
280 /* copy chunk before marker */
281 svn_stringbuf_appendbytes(outstr
, p
, q
- p
);
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
);
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. ***/
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
,
338 svn_xml_parser_t
*svn_parser
;
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
);
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
);
384 svn_xml_parse(svn_xml_parser_t
*svn_parser
,
387 svn_boolean_t is_final
)
392 /* Parse some xml data */
393 success
= XML_Parse(svn_parser
->parser
, buf
, len
, is_final
);
395 /* If expat choked internally, return its error. */
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
);
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
);
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. ***/
446 svn_xml_get_attr_value(const char *name
, const char **atts
)
448 while (atts
&& (*atts
))
450 if (strcmp(atts
[0], name
) == 0)
453 atts
+= 2; /* continue looping */
456 /* Else no such attribute name seen. */
462 /*** Printing XML ***/
465 svn_xml_make_header(svn_stringbuf_t
**str
, apr_pool_t
*pool
)
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. */
482 amalgamate(const char **atts
,
484 svn_boolean_t preserve
,
490 for (key
= *atts
; key
; key
= *(++atts
))
492 const char *val
= *(++atts
);
495 /* kff todo: should we also insist that val be non-null here?
498 keylen
= strlen(key
);
499 if (preserve
&& ((apr_hash_get(ht
, key
, keylen
)) != NULL
))
502 apr_hash_set(ht
, apr_pstrndup(pool
, key
, keylen
), keylen
,
503 val
? apr_pstrdup(pool
, val
) : NULL
);
509 svn_xml_ap_to_hash(va_list ap
, apr_pool_t
*pool
)
511 apr_hash_t
*ht
= apr_hash_make(pool
);
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
);
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 */
534 svn_xml_hash_atts_overlaying(const char **atts
,
538 amalgamate(atts
, ht
, 0, pool
);
543 svn_xml_hash_atts_preserving(const char **atts
,
547 amalgamate(atts
, ht
, 1, pool
);
552 /*** Making XML tags. ***/
556 svn_xml_make_open_tag_hash(svn_stringbuf_t
**str
,
558 enum svn_xml_open_tag_style style
,
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;
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
))
576 apr_hash_this(hi
, &key
, NULL
, &val
);
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");
595 svn_xml_make_open_tag_v(svn_stringbuf_t
**str
,
597 enum svn_xml_open_tag_style style
,
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
);
611 svn_xml_make_open_tag(svn_stringbuf_t
**str
,
613 enum svn_xml_open_tag_style style
,
619 va_start(ap
, tagname
);
620 svn_xml_make_open_tag_v(str
, pool
, style
, tagname
, ap
);
625 void svn_xml_make_close_tag(svn_stringbuf_t
**str
,
630 *str
= svn_stringbuf_create("", pool
);
632 svn_stringbuf_appendcstr(*str
, "</");
633 svn_stringbuf_appendcstr(*str
, tagname
);
634 svn_stringbuf_appendcstr(*str
, ">\n");