1 /*
2 * xml.c: xml helper code shared among the Subversion libraries.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <string.h>
27 #include <assert.h>
28
29 #include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */
30 #include "svn_hash.h"
31 #include "svn_pools.h"
32 #include "svn_xml.h"
33 #include "svn_error.h"
34 #include "svn_ctype.h"
35
36 #include "private/svn_utf_private.h"
37 #include "private/svn_subr_private.h"
38
39 #ifdef SVN_HAVE_OLD_EXPAT
40 #include <xmlparse.h>
41 #else
42 #include <expat.h>
43 #endif
44
45 #ifdef XML_UNICODE
46 #error Expat is unusable -- it has been compiled for wide characters
47 #endif
48
49 #ifndef XML_VERSION_AT_LEAST
50 #define XML_VERSION_AT_LEAST(major,minor,patch) \
51 (((major) < XML_MAJOR_VERSION) \
52 || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
53 || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
54 (patch) <= XML_MICRO_VERSION))
55 #endif /* XML_VERSION_AT_LEAST */
56
57 const char *
svn_xml__compiled_version(void)58 svn_xml__compiled_version(void)
59 {
60 static const char xml_version_str[] = APR_STRINGIFY(XML_MAJOR_VERSION)
61 "." APR_STRINGIFY(XML_MINOR_VERSION)
62 "." APR_STRINGIFY(XML_MICRO_VERSION);
63
64 return xml_version_str;
65 }
66
67 const char *
svn_xml__runtime_version(void)68 svn_xml__runtime_version(void)
69 {
70 const char *expat_version = XML_ExpatVersion();
71
72 if (!strncmp(expat_version, "expat_", 6))
73 expat_version += 6;
74
75 return expat_version;
76 }
77
78
79 /* The private internals for a parser object. */
80 struct svn_xml_parser_t
81 {
82 /** the expat parser */
83 XML_Parser parser;
84
85 /** the SVN callbacks to call from the Expat callbacks */
86 svn_xml_start_elem start_handler;
87 svn_xml_end_elem end_handler;
88 svn_xml_char_data data_handler;
89
90 /** the user's baton for private data */
91 void *baton;
92
93 /** if non-@c NULL, an error happened while parsing */
94 svn_error_t *error;
95
96 /** where this object is allocated, so we can free it easily */
97 apr_pool_t *pool;
98
99 };
100
101
102 /*** XML character validation ***/
103
104 svn_boolean_t
svn_xml_is_xml_safe(const char * data,apr_size_t len)105 svn_xml_is_xml_safe(const char *data, apr_size_t len)
106 {
107 const char *end = data + len;
108 const char *p;
109
110 if (! svn_utf__is_valid(data, len))
111 return FALSE;
112
113 for (p = data; p < end; p++)
114 {
115 unsigned char c = *p;
116
117 if (svn_ctype_iscntrl(c))
118 {
119 if ((c != SVN_CTYPE_ASCII_TAB)
120 && (c != SVN_CTYPE_ASCII_LINEFEED)
121 && (c != SVN_CTYPE_ASCII_CARRIAGERETURN)
122 && (c != SVN_CTYPE_ASCII_DELETE))
123 return FALSE;
124 }
125 }
126 return TRUE;
127 }
128
129
130
131
132
133 /*** XML escaping. ***/
134
135 /* ### ...?
136 *
137 * If *OUTSTR is @c NULL, set *OUTSTR to a new stringbuf allocated
138 * in POOL, else append to the existing stringbuf there.
139 */
140 static void
xml_escape_cdata(svn_stringbuf_t ** outstr,const char * data,apr_size_t len,apr_pool_t * pool)141 xml_escape_cdata(svn_stringbuf_t **outstr,
142 const char *data,
143 apr_size_t len,
144 apr_pool_t *pool)
145 {
146 const char *end = data + len;
147 const char *p = data, *q;
148
149 if (*outstr == NULL)
150 *outstr = svn_stringbuf_create_empty(pool);
151
152 while (1)
153 {
154 /* Find a character which needs to be quoted and append bytes up
155 to that point. Strictly speaking, '>' only needs to be
156 quoted if it follows "]]", but it's easier to quote it all
157 the time.
158
159 So, why are we escaping '\r' here? Well, according to the
160 XML spec, '\r\n' gets converted to '\n' during XML parsing.
161 Also, any '\r' not followed by '\n' is converted to '\n'. By
162 golly, if we say we want to escape a '\r', we want to make
163 sure it remains a '\r'! */
164 q = p;
165 while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
166 q++;
167 svn_stringbuf_appendbytes(*outstr, p, q - p);
168
169 /* We may already be a winner. */
170 if (q == end)
171 break;
172
173 /* Append the entity reference for the character. */
174 if (*q == '&')
175 svn_stringbuf_appendcstr(*outstr, "&");
176 else if (*q == '<')
177 svn_stringbuf_appendcstr(*outstr, "<");
178 else if (*q == '>')
179 svn_stringbuf_appendcstr(*outstr, ">");
180 else if (*q == '\r')
181 svn_stringbuf_appendcstr(*outstr, " ");
182
183 p = q + 1;
184 }
185 }
186
187 /* Essentially the same as xml_escape_cdata, with the addition of
188 whitespace and quote characters. */
189 static void
xml_escape_attr(svn_stringbuf_t ** outstr,const char * data,apr_size_t len,apr_pool_t * pool)190 xml_escape_attr(svn_stringbuf_t **outstr,
191 const char *data,
192 apr_size_t len,
193 apr_pool_t *pool)
194 {
195 const char *end = data + len;
196 const char *p = data, *q;
197
198 if (*outstr == NULL)
199 *outstr = svn_stringbuf_create_ensure(len, pool);
200
201 while (1)
202 {
203 /* Find a character which needs to be quoted and append bytes up
204 to that point. */
205 q = p;
206 while (q < end && *q != '&' && *q != '<' && *q != '>'
207 && *q != '"' && *q != '\'' && *q != '\r'
208 && *q != '\n' && *q != '\t')
209 q++;
210 svn_stringbuf_appendbytes(*outstr, p, q - p);
211
212 /* We may already be a winner. */
213 if (q == end)
214 break;
215
216 /* Append the entity reference for the character. */
217 if (*q == '&')
218 svn_stringbuf_appendcstr(*outstr, "&");
219 else if (*q == '<')
220 svn_stringbuf_appendcstr(*outstr, "<");
221 else if (*q == '>')
222 svn_stringbuf_appendcstr(*outstr, ">");
223 else if (*q == '"')
224 svn_stringbuf_appendcstr(*outstr, """);
225 else if (*q == '\'')
226 svn_stringbuf_appendcstr(*outstr, "'");
227 else if (*q == '\r')
228 svn_stringbuf_appendcstr(*outstr, " ");
229 else if (*q == '\n')
230 svn_stringbuf_appendcstr(*outstr, " ");
231 else if (*q == '\t')
232 svn_stringbuf_appendcstr(*outstr, "	");
233
234 p = q + 1;
235 }
236 }
237
238
239 void
svn_xml_escape_cdata_stringbuf(svn_stringbuf_t ** outstr,const svn_stringbuf_t * string,apr_pool_t * pool)240 svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
241 const svn_stringbuf_t *string,
242 apr_pool_t *pool)
243 {
244 xml_escape_cdata(outstr, string->data, string->len, pool);
245 }
246
247
248 void
svn_xml_escape_cdata_string(svn_stringbuf_t ** outstr,const svn_string_t * string,apr_pool_t * pool)249 svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
250 const svn_string_t *string,
251 apr_pool_t *pool)
252 {
253 xml_escape_cdata(outstr, string->data, string->len, pool);
254 }
255
256
257 void
svn_xml_escape_cdata_cstring(svn_stringbuf_t ** outstr,const char * string,apr_pool_t * pool)258 svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
259 const char *string,
260 apr_pool_t *pool)
261 {
262 xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool);
263 }
264
265
266 void
svn_xml_escape_attr_stringbuf(svn_stringbuf_t ** outstr,const svn_stringbuf_t * string,apr_pool_t * pool)267 svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
268 const svn_stringbuf_t *string,
269 apr_pool_t *pool)
270 {
271 xml_escape_attr(outstr, string->data, string->len, pool);
272 }
273
274
275 void
svn_xml_escape_attr_string(svn_stringbuf_t ** outstr,const svn_string_t * string,apr_pool_t * pool)276 svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
277 const svn_string_t *string,
278 apr_pool_t *pool)
279 {
280 xml_escape_attr(outstr, string->data, string->len, pool);
281 }
282
283
284 void
svn_xml_escape_attr_cstring(svn_stringbuf_t ** outstr,const char * string,apr_pool_t * pool)285 svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
286 const char *string,
287 apr_pool_t *pool)
288 {
289 xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool);
290 }
291
292
293 const char *
svn_xml_fuzzy_escape(const char * string,apr_pool_t * pool)294 svn_xml_fuzzy_escape(const char *string, apr_pool_t *pool)
295 {
296 const char *end = string + strlen(string);
297 const char *p = string, *q;
298 svn_stringbuf_t *outstr;
299 char escaped_char[6]; /* ? \ u u u \0 */
300
301 for (q = p; q < end; q++)
302 {
303 if (svn_ctype_iscntrl(*q)
304 && ! ((*q == '\n') || (*q == '\r') || (*q == '\t')))
305 break;
306 }
307
308 /* Return original string if no unsafe characters found. */
309 if (q == end)
310 return string;
311
312 outstr = svn_stringbuf_create_empty(pool);
313 while (1)
314 {
315 q = p;
316
317 /* Traverse till either unsafe character or eos. */
318 while ((q < end)
319 && ((! svn_ctype_iscntrl(*q))
320 || (*q == '\n') || (*q == '\r') || (*q == '\t')))
321 q++;
322
323 /* copy chunk before marker */
324 svn_stringbuf_appendbytes(outstr, p, q - p);
325
326 if (q == end)
327 break;
328
329 /* Append an escaped version of the unsafe character.
330
331 ### This format was chosen for consistency with
332 ### svn_utf__cstring_from_utf8_fuzzy(). The two functions
333 ### should probably share code, even though they escape
334 ### different characters.
335 */
336 apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u",
337 (unsigned char) *q);
338 svn_stringbuf_appendcstr(outstr, escaped_char);
339
340 p = q + 1;
341 }
342
343 return outstr->data;
344 }
345
346
347 /*** Map from the Expat callback types to the SVN XML types. ***/
348
expat_start_handler(void * userData,const XML_Char * name,const XML_Char ** atts)349 static void expat_start_handler(void *userData,
350 const XML_Char *name,
351 const XML_Char **atts)
352 {
353 svn_xml_parser_t *svn_parser = userData;
354
355 (*svn_parser->start_handler)(svn_parser->baton, name, atts);
356 }
357
expat_end_handler(void * userData,const XML_Char * name)358 static void expat_end_handler(void *userData, const XML_Char *name)
359 {
360 svn_xml_parser_t *svn_parser = userData;
361
362 (*svn_parser->end_handler)(svn_parser->baton, name);
363 }
364
expat_data_handler(void * userData,const XML_Char * s,int len)365 static void expat_data_handler(void *userData, const XML_Char *s, int len)
366 {
367 svn_xml_parser_t *svn_parser = userData;
368
369 (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len);
370 }
371
372 #if XML_VERSION_AT_LEAST(1, 95, 8)
expat_entity_declaration(void * userData,const XML_Char * entityName,int is_parameter_entity,const XML_Char * value,int value_length,const XML_Char * base,const XML_Char * systemId,const XML_Char * publicId,const XML_Char * notationName)373 static void expat_entity_declaration(void *userData,
374 const XML_Char *entityName,
375 int is_parameter_entity,
376 const XML_Char *value,
377 int value_length,
378 const XML_Char *base,
379 const XML_Char *systemId,
380 const XML_Char *publicId,
381 const XML_Char *notationName)
382 {
383 svn_xml_parser_t *svn_parser = userData;
384
385 /* Stop the parser if an entity declaration is hit. */
386 XML_StopParser(svn_parser->parser, 0 /* resumable */);
387 }
388 #else
389 /* A noop default_handler. */
expat_default_handler(void * userData,const XML_Char * s,int len)390 static void expat_default_handler(void *userData, const XML_Char *s, int len)
391 {
392 }
393 #endif
394
395 /*** Making a parser. ***/
396
397 svn_xml_parser_t *
svn_xml_make_parser(void * baton,svn_xml_start_elem start_handler,svn_xml_end_elem end_handler,svn_xml_char_data data_handler,apr_pool_t * pool)398 svn_xml_make_parser(void *baton,
399 svn_xml_start_elem start_handler,
400 svn_xml_end_elem end_handler,
401 svn_xml_char_data data_handler,
402 apr_pool_t *pool)
403 {
404 svn_xml_parser_t *svn_parser;
405 apr_pool_t *subpool;
406
407 XML_Parser parser = XML_ParserCreate(NULL);
408
409 XML_SetElementHandler(parser,
410 start_handler ? expat_start_handler : NULL,
411 end_handler ? expat_end_handler : NULL);
412 XML_SetCharacterDataHandler(parser,
413 data_handler ? expat_data_handler : NULL);
414
415 #if XML_VERSION_AT_LEAST(1, 95, 8)
416 XML_SetEntityDeclHandler(parser, expat_entity_declaration);
417 #else
418 XML_SetDefaultHandler(parser, expat_default_handler);
419 #endif
420
421 /* ### we probably don't want this pool; or at least we should pass it
422 ### to the callbacks and clear it periodically. */
423 subpool = svn_pool_create(pool);
424
425 svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser));
426
427 svn_parser->parser = parser;
428 svn_parser->start_handler = start_handler;
429 svn_parser->end_handler = end_handler;
430 svn_parser->data_handler = data_handler;
431 svn_parser->baton = baton;
432 svn_parser->pool = subpool;
433
434 /* store our parser info as the UserData in the Expat parser */
435 XML_SetUserData(parser, svn_parser);
436
437 return svn_parser;
438 }
439
440
441 /* Free a parser */
442 void
svn_xml_free_parser(svn_xml_parser_t * svn_parser)443 svn_xml_free_parser(svn_xml_parser_t *svn_parser)
444 {
445 /* Free the expat parser */
446 XML_ParserFree(svn_parser->parser);
447
448 /* Free the subversion parser */
449 svn_pool_destroy(svn_parser->pool);
450 }
451
452
453
454
455 svn_error_t *
svn_xml_parse(svn_xml_parser_t * svn_parser,const char * buf,apr_size_t len,svn_boolean_t is_final)456 svn_xml_parse(svn_xml_parser_t *svn_parser,
457 const char *buf,
458 apr_size_t len,
459 svn_boolean_t is_final)
460 {
461 svn_error_t *err;
462 int success;
463
464 /* Parse some xml data */
465 success = XML_Parse(svn_parser->parser, buf, (int) len, is_final);
466
467 /* If expat choked internally, return its error. */
468 if (! success)
469 {
470 /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */
471 long line = XML_GetCurrentLineNumber(svn_parser->parser);
472
473 err = svn_error_createf
474 (SVN_ERR_XML_MALFORMED, NULL,
475 _("Malformed XML: %s at line %ld"),
476 XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line);
477
478 /* Kill all parsers and return the expat error */
479 svn_xml_free_parser(svn_parser);
480 return err;
481 }
482
483 /* Did an error occur somewhere *inside* the expat callbacks? */
484 if (svn_parser->error)
485 {
486 err = svn_parser->error;
487 svn_xml_free_parser(svn_parser);
488 return err;
489 }
490
491 return SVN_NO_ERROR;
492 }
493
494
495
svn_xml_signal_bailout(svn_error_t * error,svn_xml_parser_t * svn_parser)496 void svn_xml_signal_bailout(svn_error_t *error,
497 svn_xml_parser_t *svn_parser)
498 {
499 /* This will cause the current XML_Parse() call to finish quickly! */
500 XML_SetElementHandler(svn_parser->parser, NULL, NULL);
501 XML_SetCharacterDataHandler(svn_parser->parser, NULL);
502 #if XML_VERSION_AT_LEAST(1, 95, 8)
503 XML_SetEntityDeclHandler(svn_parser->parser, NULL);
504 #endif
505
506 /* Once outside of XML_Parse(), the existence of this field will
507 cause svn_delta_parse()'s main read-loop to return error. */
508 svn_parser->error = error;
509 }
510
511
512
513
514
515
516
517
518 /*** Attribute walking. ***/
519
520 const char *
svn_xml_get_attr_value(const char * name,const char * const * atts)521 svn_xml_get_attr_value(const char *name, const char *const *atts)
522 {
523 while (atts && (*atts))
524 {
525 if (strcmp(atts[0], name) == 0)
526 return atts[1];
527 else
528 atts += 2; /* continue looping */
529 }
530
531 /* Else no such attribute name seen. */
532 return NULL;
533 }
534
535
536
537 /*** Printing XML ***/
538
539 void
svn_xml_make_header2(svn_stringbuf_t ** str,const char * encoding,apr_pool_t * pool)540 svn_xml_make_header2(svn_stringbuf_t **str, const char *encoding,
541 apr_pool_t *pool)
542 {
543
544 if (*str == NULL)
545 *str = svn_stringbuf_create_empty(pool);
546 svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\"");
547 if (encoding)
548 {
549 encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding);
550 svn_stringbuf_appendcstr(*str, encoding);
551 }
552 svn_stringbuf_appendcstr(*str, "?>\n");
553 }
554
555
556
557 /*** Creating attribute hashes. ***/
558
559 /* Combine an existing attribute list ATTS with a HASH that itself
560 represents an attribute list. Iff PRESERVE is true, then no value
561 already in HASH will be changed, else values from ATTS will
562 override previous values in HASH. */
563 static void
amalgamate(const char ** atts,apr_hash_t * ht,svn_boolean_t preserve,apr_pool_t * pool)564 amalgamate(const char **atts,
565 apr_hash_t *ht,
566 svn_boolean_t preserve,
567 apr_pool_t *pool)
568 {
569 const char *key;
570
571 if (atts)
572 for (key = *atts; key; key = *(++atts))
573 {
574 const char *val = *(++atts);
575 size_t keylen;
576 assert(key != NULL);
577 /* kff todo: should we also insist that val be non-null here?
578 Probably. */
579
580 keylen = strlen(key);
581 if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL))
582 continue;
583 else
584 apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen,
585 val ? apr_pstrdup(pool, val) : NULL);
586 }
587 }
588
589
590 apr_hash_t *
svn_xml_ap_to_hash(va_list ap,apr_pool_t * pool)591 svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool)
592 {
593 apr_hash_t *ht = apr_hash_make(pool);
594 const char *key;
595
596 while ((key = va_arg(ap, char *)) != NULL)
597 {
598 const char *val = va_arg(ap, const char *);
599 svn_hash_sets(ht, key, val);
600 }
601
602 return ht;
603 }
604
605
606 apr_hash_t *
svn_xml_make_att_hash(const char ** atts,apr_pool_t * pool)607 svn_xml_make_att_hash(const char **atts, apr_pool_t *pool)
608 {
609 apr_hash_t *ht = apr_hash_make(pool);
610 amalgamate(atts, ht, 0, pool); /* third arg irrelevant in this case */
611 return ht;
612 }
613
614
615 void
svn_xml_hash_atts_overlaying(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)616 svn_xml_hash_atts_overlaying(const char **atts,
617 apr_hash_t *ht,
618 apr_pool_t *pool)
619 {
620 amalgamate(atts, ht, 0, pool);
621 }
622
623
624 void
svn_xml_hash_atts_preserving(const char ** atts,apr_hash_t * ht,apr_pool_t * pool)625 svn_xml_hash_atts_preserving(const char **atts,
626 apr_hash_t *ht,
627 apr_pool_t *pool)
628 {
629 amalgamate(atts, ht, 1, pool);
630 }
631
632
633
634 /*** Making XML tags. ***/
635
636
637 void
svn_xml_make_open_tag_hash(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,apr_hash_t * attributes)638 svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
639 apr_pool_t *pool,
640 enum svn_xml_open_tag_style style,
641 const char *tagname,
642 apr_hash_t *attributes)
643 {
644 apr_hash_index_t *hi;
645 apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30;
646
647 if (*str == NULL)
648 *str = svn_stringbuf_create_ensure(est_size, pool);
649
650 svn_stringbuf_appendcstr(*str, "<");
651 svn_stringbuf_appendcstr(*str, tagname);
652
653 for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi))
654 {
655 const void *key;
656 void *val;
657
658 apr_hash_this(hi, &key, NULL, &val);
659 assert(val != NULL);
660
661 svn_stringbuf_appendcstr(*str, "\n ");
662 svn_stringbuf_appendcstr(*str, key);
663 svn_stringbuf_appendcstr(*str, "=\"");
664 svn_xml_escape_attr_cstring(str, val, pool);
665 svn_stringbuf_appendcstr(*str, "\"");
666 }
667
668 if (style == svn_xml_self_closing)
669 svn_stringbuf_appendcstr(*str, "/");
670 svn_stringbuf_appendcstr(*str, ">");
671 if (style != svn_xml_protect_pcdata)
672 svn_stringbuf_appendcstr(*str, "\n");
673 }
674
675
676 void
svn_xml_make_open_tag_v(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,va_list ap)677 svn_xml_make_open_tag_v(svn_stringbuf_t **str,
678 apr_pool_t *pool,
679 enum svn_xml_open_tag_style style,
680 const char *tagname,
681 va_list ap)
682 {
683 apr_pool_t *subpool = svn_pool_create(pool);
684 apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool);
685
686 svn_xml_make_open_tag_hash(str, pool, style, tagname, ht);
687 svn_pool_destroy(subpool);
688 }
689
690
691
692 void
svn_xml_make_open_tag(svn_stringbuf_t ** str,apr_pool_t * pool,enum svn_xml_open_tag_style style,const char * tagname,...)693 svn_xml_make_open_tag(svn_stringbuf_t **str,
694 apr_pool_t *pool,
695 enum svn_xml_open_tag_style style,
696 const char *tagname,
697 ...)
698 {
699 va_list ap;
700
701 va_start(ap, tagname);
702 svn_xml_make_open_tag_v(str, pool, style, tagname, ap);
703 va_end(ap);
704 }
705
706
svn_xml_make_close_tag(svn_stringbuf_t ** str,apr_pool_t * pool,const char * tagname)707 void svn_xml_make_close_tag(svn_stringbuf_t **str,
708 apr_pool_t *pool,
709 const char *tagname)
710 {
711 if (*str == NULL)
712 *str = svn_stringbuf_create_empty(pool);
713
714 svn_stringbuf_appendcstr(*str, "</");
715 svn_stringbuf_appendcstr(*str, tagname);
716 svn_stringbuf_appendcstr(*str, ">\n");
717 }
718