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("", 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 err
= svn_error_createf
399 (SVN_ERR_XML_MALFORMED
, NULL
,
400 _("Malformed XML: %s at line %d"),
401 XML_ErrorString(XML_GetErrorCode(svn_parser
->parser
)),
402 XML_GetCurrentLineNumber(svn_parser
->parser
));
404 /* Kill all parsers and return the expat error */
405 svn_xml_free_parser(svn_parser
);
409 /* Did an error occur somewhere *inside* the expat callbacks? */
410 if (svn_parser
->error
)
412 err
= svn_parser
->error
;
413 svn_xml_free_parser(svn_parser
);
422 void svn_xml_signal_bailout(svn_error_t
*error
,
423 svn_xml_parser_t
*svn_parser
)
425 /* This will cause the current XML_Parse() call to finish quickly! */
426 XML_SetElementHandler(svn_parser
->parser
, NULL
, NULL
);
427 XML_SetCharacterDataHandler(svn_parser
->parser
, NULL
);
429 /* Once outside of XML_Parse(), the existence of this field will
430 cause svn_delta_parse()'s main read-loop to return error. */
431 svn_parser
->error
= error
;
441 /*** Attribute walking. ***/
444 svn_xml_get_attr_value(const char *name
, const char **atts
)
446 while (atts
&& (*atts
))
448 if (strcmp(atts
[0], name
) == 0)
451 atts
+= 2; /* continue looping */
454 /* Else no such attribute name seen. */
460 /*** Printing XML ***/
463 svn_xml_make_header(svn_stringbuf_t
**str
, apr_pool_t
*pool
)
466 *str
= svn_stringbuf_create("", pool
);
467 svn_stringbuf_appendcstr(*str
,
468 "<?xml version=\"1.0\"?>\n");
473 /*** Creating attribute hashes. ***/
475 /* Combine an existing attribute list ATTS with a HASH that itself
476 represents an attribute list. Iff PRESERVE is true, then no value
477 already in HASH will be changed, else values from ATTS will
478 override previous values in HASH. */
480 amalgamate(const char **atts
,
482 svn_boolean_t preserve
,
488 for (key
= *atts
; key
; key
= *(++atts
))
490 const char *val
= *(++atts
);
493 /* kff todo: should we also insist that val be non-null here?
496 keylen
= strlen(key
);
497 if (preserve
&& ((apr_hash_get(ht
, key
, keylen
)) != NULL
))
500 apr_hash_set(ht
, apr_pstrndup(pool
, key
, keylen
), keylen
,
501 val
? apr_pstrdup(pool
, val
) : NULL
);
507 svn_xml_ap_to_hash(va_list ap
, apr_pool_t
*pool
)
509 apr_hash_t
*ht
= apr_hash_make(pool
);
512 while ((key
= va_arg(ap
, char *)) != NULL
)
514 const char *val
= va_arg(ap
, const char *);
515 apr_hash_set(ht
, key
, APR_HASH_KEY_STRING
, val
);
523 svn_xml_make_att_hash(const char **atts
, apr_pool_t
*pool
)
525 apr_hash_t
*ht
= apr_hash_make(pool
);
526 amalgamate(atts
, ht
, 0, pool
); /* third arg irrelevant in this case */
532 svn_xml_hash_atts_overlaying(const char **atts
,
536 amalgamate(atts
, ht
, 0, pool
);
541 svn_xml_hash_atts_preserving(const char **atts
,
545 amalgamate(atts
, ht
, 1, pool
);
550 /*** Making XML tags. ***/
554 svn_xml_make_open_tag_hash(svn_stringbuf_t
**str
,
556 enum svn_xml_open_tag_style style
,
558 apr_hash_t
*attributes
)
560 apr_hash_index_t
*hi
;
563 *str
= svn_stringbuf_create("", pool
);
565 svn_stringbuf_appendcstr(*str
, "<");
566 svn_stringbuf_appendcstr(*str
, tagname
);
568 for (hi
= apr_hash_first(pool
, attributes
); hi
; hi
= apr_hash_next(hi
))
573 apr_hash_this(hi
, &key
, NULL
, &val
);
576 svn_stringbuf_appendcstr(*str
, "\n ");
577 svn_stringbuf_appendcstr(*str
, key
);
578 svn_stringbuf_appendcstr(*str
, "=\"");
579 svn_xml_escape_attr_cstring(str
, val
, pool
);
580 svn_stringbuf_appendcstr(*str
, "\"");
583 if (style
== svn_xml_self_closing
)
584 svn_stringbuf_appendcstr(*str
, "/");
585 svn_stringbuf_appendcstr(*str
, ">");
586 if (style
!= svn_xml_protect_pcdata
)
587 svn_stringbuf_appendcstr(*str
, "\n");
592 svn_xml_make_open_tag_v(svn_stringbuf_t
**str
,
594 enum svn_xml_open_tag_style style
,
598 apr_pool_t
*subpool
= svn_pool_create(pool
);
599 apr_hash_t
*ht
= svn_xml_ap_to_hash(ap
, subpool
);
601 svn_xml_make_open_tag_hash(str
, pool
, style
, tagname
, ht
);
602 svn_pool_destroy(subpool
);
608 svn_xml_make_open_tag(svn_stringbuf_t
**str
,
610 enum svn_xml_open_tag_style style
,
616 va_start(ap
, tagname
);
617 svn_xml_make_open_tag_v(str
, pool
, style
, tagname
, ap
);
622 void svn_xml_make_close_tag(svn_stringbuf_t
**str
,
627 *str
= svn_stringbuf_create("", pool
);
629 svn_stringbuf_appendcstr(*str
, "</");
630 svn_stringbuf_appendcstr(*str
, tagname
);
631 svn_stringbuf_appendcstr(*str
, ">\n");