1251881Speter/* 2251881Speter * xml.c: xml helper code shared among the Subversion libraries. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <string.h> 27251881Speter#include <assert.h> 28251881Speter 29251881Speter#include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */ 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_pools.h" 32251881Speter#include "svn_xml.h" 33251881Speter#include "svn_error.h" 34251881Speter#include "svn_ctype.h" 35251881Speter 36251881Speter#include "private/svn_utf_private.h" 37251881Speter 38251881Speter#ifdef SVN_HAVE_OLD_EXPAT 39251881Speter#include <xmlparse.h> 40251881Speter#else 41251881Speter#include <expat.h> 42251881Speter#endif 43251881Speter 44251881Speter#ifdef XML_UNICODE 45251881Speter#error Expat is unusable -- it has been compiled for wide characters 46251881Speter#endif 47251881Speter 48251881Speter/* The private internals for a parser object. */ 49251881Speterstruct svn_xml_parser_t 50251881Speter{ 51251881Speter /** the expat parser */ 52251881Speter XML_Parser parser; 53251881Speter 54251881Speter /** the SVN callbacks to call from the Expat callbacks */ 55251881Speter svn_xml_start_elem start_handler; 56251881Speter svn_xml_end_elem end_handler; 57251881Speter svn_xml_char_data data_handler; 58251881Speter 59251881Speter /** the user's baton for private data */ 60251881Speter void *baton; 61251881Speter 62251881Speter /** if non-@c NULL, an error happened while parsing */ 63251881Speter svn_error_t *error; 64251881Speter 65251881Speter /** where this object is allocated, so we can free it easily */ 66251881Speter apr_pool_t *pool; 67251881Speter 68251881Speter}; 69251881Speter 70251881Speter 71251881Speter/*** XML character validation ***/ 72251881Speter 73251881Spetersvn_boolean_t 74251881Spetersvn_xml_is_xml_safe(const char *data, apr_size_t len) 75251881Speter{ 76251881Speter const char *end = data + len; 77251881Speter const char *p; 78251881Speter 79251881Speter if (! svn_utf__is_valid(data, len)) 80251881Speter return FALSE; 81251881Speter 82251881Speter for (p = data; p < end; p++) 83251881Speter { 84251881Speter unsigned char c = *p; 85251881Speter 86251881Speter if (svn_ctype_iscntrl(c)) 87251881Speter { 88251881Speter if ((c != SVN_CTYPE_ASCII_TAB) 89251881Speter && (c != SVN_CTYPE_ASCII_LINEFEED) 90251881Speter && (c != SVN_CTYPE_ASCII_CARRIAGERETURN) 91251881Speter && (c != SVN_CTYPE_ASCII_DELETE)) 92251881Speter return FALSE; 93251881Speter } 94251881Speter } 95251881Speter return TRUE; 96251881Speter} 97251881Speter 98251881Speter 99251881Speter 100251881Speter 101251881Speter 102251881Speter/*** XML escaping. ***/ 103251881Speter 104251881Speter/* ### ...? 105251881Speter * 106251881Speter * If *OUTSTR is @c NULL, set *OUTSTR to a new stringbuf allocated 107251881Speter * in POOL, else append to the existing stringbuf there. 108251881Speter */ 109251881Speterstatic void 110251881Speterxml_escape_cdata(svn_stringbuf_t **outstr, 111251881Speter const char *data, 112251881Speter apr_size_t len, 113251881Speter apr_pool_t *pool) 114251881Speter{ 115251881Speter const char *end = data + len; 116251881Speter const char *p = data, *q; 117251881Speter 118251881Speter if (*outstr == NULL) 119251881Speter *outstr = svn_stringbuf_create_empty(pool); 120251881Speter 121251881Speter while (1) 122251881Speter { 123251881Speter /* Find a character which needs to be quoted and append bytes up 124251881Speter to that point. Strictly speaking, '>' only needs to be 125251881Speter quoted if it follows "]]", but it's easier to quote it all 126251881Speter the time. 127251881Speter 128251881Speter So, why are we escaping '\r' here? Well, according to the 129251881Speter XML spec, '\r\n' gets converted to '\n' during XML parsing. 130251881Speter Also, any '\r' not followed by '\n' is converted to '\n'. By 131251881Speter golly, if we say we want to escape a '\r', we want to make 132251881Speter sure it remains a '\r'! */ 133251881Speter q = p; 134251881Speter while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r') 135251881Speter q++; 136251881Speter svn_stringbuf_appendbytes(*outstr, p, q - p); 137251881Speter 138251881Speter /* We may already be a winner. */ 139251881Speter if (q == end) 140251881Speter break; 141251881Speter 142251881Speter /* Append the entity reference for the character. */ 143251881Speter if (*q == '&') 144251881Speter svn_stringbuf_appendcstr(*outstr, "&"); 145251881Speter else if (*q == '<') 146251881Speter svn_stringbuf_appendcstr(*outstr, "<"); 147251881Speter else if (*q == '>') 148251881Speter svn_stringbuf_appendcstr(*outstr, ">"); 149251881Speter else if (*q == '\r') 150251881Speter svn_stringbuf_appendcstr(*outstr, " "); 151251881Speter 152251881Speter p = q + 1; 153251881Speter } 154251881Speter} 155251881Speter 156251881Speter/* Essentially the same as xml_escape_cdata, with the addition of 157251881Speter whitespace and quote characters. */ 158251881Speterstatic void 159251881Speterxml_escape_attr(svn_stringbuf_t **outstr, 160251881Speter const char *data, 161251881Speter apr_size_t len, 162251881Speter apr_pool_t *pool) 163251881Speter{ 164251881Speter const char *end = data + len; 165251881Speter const char *p = data, *q; 166251881Speter 167251881Speter if (*outstr == NULL) 168251881Speter *outstr = svn_stringbuf_create_ensure(len, pool); 169251881Speter 170251881Speter while (1) 171251881Speter { 172251881Speter /* Find a character which needs to be quoted and append bytes up 173251881Speter to that point. */ 174251881Speter q = p; 175251881Speter while (q < end && *q != '&' && *q != '<' && *q != '>' 176251881Speter && *q != '"' && *q != '\'' && *q != '\r' 177251881Speter && *q != '\n' && *q != '\t') 178251881Speter q++; 179251881Speter svn_stringbuf_appendbytes(*outstr, p, q - p); 180251881Speter 181251881Speter /* We may already be a winner. */ 182251881Speter if (q == end) 183251881Speter break; 184251881Speter 185251881Speter /* Append the entity reference for the character. */ 186251881Speter if (*q == '&') 187251881Speter svn_stringbuf_appendcstr(*outstr, "&"); 188251881Speter else if (*q == '<') 189251881Speter svn_stringbuf_appendcstr(*outstr, "<"); 190251881Speter else if (*q == '>') 191251881Speter svn_stringbuf_appendcstr(*outstr, ">"); 192251881Speter else if (*q == '"') 193251881Speter svn_stringbuf_appendcstr(*outstr, """); 194251881Speter else if (*q == '\'') 195251881Speter svn_stringbuf_appendcstr(*outstr, "'"); 196251881Speter else if (*q == '\r') 197251881Speter svn_stringbuf_appendcstr(*outstr, " "); 198251881Speter else if (*q == '\n') 199251881Speter svn_stringbuf_appendcstr(*outstr, " "); 200251881Speter else if (*q == '\t') 201251881Speter svn_stringbuf_appendcstr(*outstr, "	"); 202251881Speter 203251881Speter p = q + 1; 204251881Speter } 205251881Speter} 206251881Speter 207251881Speter 208251881Spetervoid 209251881Spetersvn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr, 210251881Speter const svn_stringbuf_t *string, 211251881Speter apr_pool_t *pool) 212251881Speter{ 213251881Speter xml_escape_cdata(outstr, string->data, string->len, pool); 214251881Speter} 215251881Speter 216251881Speter 217251881Spetervoid 218251881Spetersvn_xml_escape_cdata_string(svn_stringbuf_t **outstr, 219251881Speter const svn_string_t *string, 220251881Speter apr_pool_t *pool) 221251881Speter{ 222251881Speter xml_escape_cdata(outstr, string->data, string->len, pool); 223251881Speter} 224251881Speter 225251881Speter 226251881Spetervoid 227251881Spetersvn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr, 228251881Speter const char *string, 229251881Speter apr_pool_t *pool) 230251881Speter{ 231251881Speter xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool); 232251881Speter} 233251881Speter 234251881Speter 235251881Spetervoid 236251881Spetersvn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr, 237251881Speter const svn_stringbuf_t *string, 238251881Speter apr_pool_t *pool) 239251881Speter{ 240251881Speter xml_escape_attr(outstr, string->data, string->len, pool); 241251881Speter} 242251881Speter 243251881Speter 244251881Spetervoid 245251881Spetersvn_xml_escape_attr_string(svn_stringbuf_t **outstr, 246251881Speter const svn_string_t *string, 247251881Speter apr_pool_t *pool) 248251881Speter{ 249251881Speter xml_escape_attr(outstr, string->data, string->len, pool); 250251881Speter} 251251881Speter 252251881Speter 253251881Spetervoid 254251881Spetersvn_xml_escape_attr_cstring(svn_stringbuf_t **outstr, 255251881Speter const char *string, 256251881Speter apr_pool_t *pool) 257251881Speter{ 258251881Speter xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool); 259251881Speter} 260251881Speter 261251881Speter 262251881Speterconst char * 263251881Spetersvn_xml_fuzzy_escape(const char *string, apr_pool_t *pool) 264251881Speter{ 265251881Speter const char *end = string + strlen(string); 266251881Speter const char *p = string, *q; 267251881Speter svn_stringbuf_t *outstr; 268251881Speter char escaped_char[6]; /* ? \ u u u \0 */ 269251881Speter 270251881Speter for (q = p; q < end; q++) 271251881Speter { 272251881Speter if (svn_ctype_iscntrl(*q) 273251881Speter && ! ((*q == '\n') || (*q == '\r') || (*q == '\t'))) 274251881Speter break; 275251881Speter } 276251881Speter 277251881Speter /* Return original string if no unsafe characters found. */ 278251881Speter if (q == end) 279251881Speter return string; 280251881Speter 281251881Speter outstr = svn_stringbuf_create_empty(pool); 282251881Speter while (1) 283251881Speter { 284251881Speter q = p; 285251881Speter 286251881Speter /* Traverse till either unsafe character or eos. */ 287251881Speter while ((q < end) 288251881Speter && ((! svn_ctype_iscntrl(*q)) 289251881Speter || (*q == '\n') || (*q == '\r') || (*q == '\t'))) 290251881Speter q++; 291251881Speter 292251881Speter /* copy chunk before marker */ 293251881Speter svn_stringbuf_appendbytes(outstr, p, q - p); 294251881Speter 295251881Speter if (q == end) 296251881Speter break; 297251881Speter 298251881Speter /* Append an escaped version of the unsafe character. 299251881Speter 300251881Speter ### This format was chosen for consistency with 301251881Speter ### svn_utf__cstring_from_utf8_fuzzy(). The two functions 302251881Speter ### should probably share code, even though they escape 303251881Speter ### different characters. 304251881Speter */ 305251881Speter apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u", 306251881Speter (unsigned char) *q); 307251881Speter svn_stringbuf_appendcstr(outstr, escaped_char); 308251881Speter 309251881Speter p = q + 1; 310251881Speter } 311251881Speter 312251881Speter return outstr->data; 313251881Speter} 314251881Speter 315251881Speter 316251881Speter/*** Map from the Expat callback types to the SVN XML types. ***/ 317251881Speter 318251881Speterstatic void expat_start_handler(void *userData, 319251881Speter const XML_Char *name, 320251881Speter const XML_Char **atts) 321251881Speter{ 322251881Speter svn_xml_parser_t *svn_parser = userData; 323251881Speter 324251881Speter (*svn_parser->start_handler)(svn_parser->baton, name, atts); 325251881Speter} 326251881Speter 327251881Speterstatic void expat_end_handler(void *userData, const XML_Char *name) 328251881Speter{ 329251881Speter svn_xml_parser_t *svn_parser = userData; 330251881Speter 331251881Speter (*svn_parser->end_handler)(svn_parser->baton, name); 332251881Speter} 333251881Speter 334251881Speterstatic void expat_data_handler(void *userData, const XML_Char *s, int len) 335251881Speter{ 336251881Speter svn_xml_parser_t *svn_parser = userData; 337251881Speter 338251881Speter (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len); 339251881Speter} 340251881Speter 341251881Speter 342251881Speter/*** Making a parser. ***/ 343251881Speter 344251881Spetersvn_xml_parser_t * 345251881Spetersvn_xml_make_parser(void *baton, 346251881Speter svn_xml_start_elem start_handler, 347251881Speter svn_xml_end_elem end_handler, 348251881Speter svn_xml_char_data data_handler, 349251881Speter apr_pool_t *pool) 350251881Speter{ 351251881Speter svn_xml_parser_t *svn_parser; 352251881Speter apr_pool_t *subpool; 353251881Speter 354251881Speter XML_Parser parser = XML_ParserCreate(NULL); 355251881Speter 356251881Speter XML_SetElementHandler(parser, 357251881Speter start_handler ? expat_start_handler : NULL, 358251881Speter end_handler ? expat_end_handler : NULL); 359251881Speter XML_SetCharacterDataHandler(parser, 360251881Speter data_handler ? expat_data_handler : NULL); 361251881Speter 362251881Speter /* ### we probably don't want this pool; or at least we should pass it 363251881Speter ### to the callbacks and clear it periodically. */ 364251881Speter subpool = svn_pool_create(pool); 365251881Speter 366251881Speter svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser)); 367251881Speter 368251881Speter svn_parser->parser = parser; 369251881Speter svn_parser->start_handler = start_handler; 370251881Speter svn_parser->end_handler = end_handler; 371251881Speter svn_parser->data_handler = data_handler; 372251881Speter svn_parser->baton = baton; 373251881Speter svn_parser->pool = subpool; 374251881Speter 375251881Speter /* store our parser info as the UserData in the Expat parser */ 376251881Speter XML_SetUserData(parser, svn_parser); 377251881Speter 378251881Speter return svn_parser; 379251881Speter} 380251881Speter 381251881Speter 382251881Speter/* Free a parser */ 383251881Spetervoid 384251881Spetersvn_xml_free_parser(svn_xml_parser_t *svn_parser) 385251881Speter{ 386251881Speter /* Free the expat parser */ 387251881Speter XML_ParserFree(svn_parser->parser); 388251881Speter 389251881Speter /* Free the subversion parser */ 390251881Speter svn_pool_destroy(svn_parser->pool); 391251881Speter} 392251881Speter 393251881Speter 394251881Speter 395251881Speter 396251881Spetersvn_error_t * 397251881Spetersvn_xml_parse(svn_xml_parser_t *svn_parser, 398251881Speter const char *buf, 399251881Speter apr_size_t len, 400251881Speter svn_boolean_t is_final) 401251881Speter{ 402251881Speter svn_error_t *err; 403251881Speter int success; 404251881Speter 405251881Speter /* Parse some xml data */ 406251881Speter success = XML_Parse(svn_parser->parser, buf, (int) len, is_final); 407251881Speter 408251881Speter /* If expat choked internally, return its error. */ 409251881Speter if (! success) 410251881Speter { 411251881Speter /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */ 412251881Speter long line = XML_GetCurrentLineNumber(svn_parser->parser); 413251881Speter 414251881Speter err = svn_error_createf 415251881Speter (SVN_ERR_XML_MALFORMED, NULL, 416251881Speter _("Malformed XML: %s at line %ld"), 417251881Speter XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line); 418251881Speter 419251881Speter /* Kill all parsers and return the expat error */ 420251881Speter svn_xml_free_parser(svn_parser); 421251881Speter return err; 422251881Speter } 423251881Speter 424251881Speter /* Did an error occur somewhere *inside* the expat callbacks? */ 425251881Speter if (svn_parser->error) 426251881Speter { 427251881Speter err = svn_parser->error; 428251881Speter svn_xml_free_parser(svn_parser); 429251881Speter return err; 430251881Speter } 431251881Speter 432251881Speter return SVN_NO_ERROR; 433251881Speter} 434251881Speter 435251881Speter 436251881Speter 437251881Spetervoid svn_xml_signal_bailout(svn_error_t *error, 438251881Speter svn_xml_parser_t *svn_parser) 439251881Speter{ 440251881Speter /* This will cause the current XML_Parse() call to finish quickly! */ 441251881Speter XML_SetElementHandler(svn_parser->parser, NULL, NULL); 442251881Speter XML_SetCharacterDataHandler(svn_parser->parser, NULL); 443251881Speter 444251881Speter /* Once outside of XML_Parse(), the existence of this field will 445251881Speter cause svn_delta_parse()'s main read-loop to return error. */ 446251881Speter svn_parser->error = error; 447251881Speter} 448251881Speter 449251881Speter 450251881Speter 451251881Speter 452251881Speter 453251881Speter 454251881Speter 455251881Speter 456251881Speter/*** Attribute walking. ***/ 457251881Speter 458251881Speterconst char * 459251881Spetersvn_xml_get_attr_value(const char *name, const char *const *atts) 460251881Speter{ 461251881Speter while (atts && (*atts)) 462251881Speter { 463251881Speter if (strcmp(atts[0], name) == 0) 464251881Speter return atts[1]; 465251881Speter else 466251881Speter atts += 2; /* continue looping */ 467251881Speter } 468251881Speter 469251881Speter /* Else no such attribute name seen. */ 470251881Speter return NULL; 471251881Speter} 472251881Speter 473251881Speter 474251881Speter 475251881Speter/*** Printing XML ***/ 476251881Speter 477251881Spetervoid 478251881Spetersvn_xml_make_header2(svn_stringbuf_t **str, const char *encoding, 479251881Speter apr_pool_t *pool) 480251881Speter{ 481251881Speter 482251881Speter if (*str == NULL) 483251881Speter *str = svn_stringbuf_create_empty(pool); 484251881Speter svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\""); 485251881Speter if (encoding) 486251881Speter { 487251881Speter encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding); 488251881Speter svn_stringbuf_appendcstr(*str, encoding); 489251881Speter } 490251881Speter svn_stringbuf_appendcstr(*str, "?>\n"); 491251881Speter} 492251881Speter 493251881Speter 494251881Speter 495251881Speter/*** Creating attribute hashes. ***/ 496251881Speter 497251881Speter/* Combine an existing attribute list ATTS with a HASH that itself 498251881Speter represents an attribute list. Iff PRESERVE is true, then no value 499251881Speter already in HASH will be changed, else values from ATTS will 500251881Speter override previous values in HASH. */ 501251881Speterstatic void 502251881Speteramalgamate(const char **atts, 503251881Speter apr_hash_t *ht, 504251881Speter svn_boolean_t preserve, 505251881Speter apr_pool_t *pool) 506251881Speter{ 507251881Speter const char *key; 508251881Speter 509251881Speter if (atts) 510251881Speter for (key = *atts; key; key = *(++atts)) 511251881Speter { 512251881Speter const char *val = *(++atts); 513251881Speter size_t keylen; 514251881Speter assert(key != NULL); 515251881Speter /* kff todo: should we also insist that val be non-null here? 516251881Speter Probably. */ 517251881Speter 518251881Speter keylen = strlen(key); 519251881Speter if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL)) 520251881Speter continue; 521251881Speter else 522251881Speter apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen, 523251881Speter val ? apr_pstrdup(pool, val) : NULL); 524251881Speter } 525251881Speter} 526251881Speter 527251881Speter 528251881Speterapr_hash_t * 529251881Spetersvn_xml_ap_to_hash(va_list ap, apr_pool_t *pool) 530251881Speter{ 531251881Speter apr_hash_t *ht = apr_hash_make(pool); 532251881Speter const char *key; 533251881Speter 534251881Speter while ((key = va_arg(ap, char *)) != NULL) 535251881Speter { 536251881Speter const char *val = va_arg(ap, const char *); 537251881Speter svn_hash_sets(ht, key, val); 538251881Speter } 539251881Speter 540251881Speter return ht; 541251881Speter} 542251881Speter 543251881Speter 544251881Speterapr_hash_t * 545251881Spetersvn_xml_make_att_hash(const char **atts, apr_pool_t *pool) 546251881Speter{ 547251881Speter apr_hash_t *ht = apr_hash_make(pool); 548251881Speter amalgamate(atts, ht, 0, pool); /* third arg irrelevant in this case */ 549251881Speter return ht; 550251881Speter} 551251881Speter 552251881Speter 553251881Spetervoid 554251881Spetersvn_xml_hash_atts_overlaying(const char **atts, 555251881Speter apr_hash_t *ht, 556251881Speter apr_pool_t *pool) 557251881Speter{ 558251881Speter amalgamate(atts, ht, 0, pool); 559251881Speter} 560251881Speter 561251881Speter 562251881Spetervoid 563251881Spetersvn_xml_hash_atts_preserving(const char **atts, 564251881Speter apr_hash_t *ht, 565251881Speter apr_pool_t *pool) 566251881Speter{ 567251881Speter amalgamate(atts, ht, 1, pool); 568251881Speter} 569251881Speter 570251881Speter 571251881Speter 572251881Speter/*** Making XML tags. ***/ 573251881Speter 574251881Speter 575251881Spetervoid 576251881Spetersvn_xml_make_open_tag_hash(svn_stringbuf_t **str, 577251881Speter apr_pool_t *pool, 578251881Speter enum svn_xml_open_tag_style style, 579251881Speter const char *tagname, 580251881Speter apr_hash_t *attributes) 581251881Speter{ 582251881Speter apr_hash_index_t *hi; 583251881Speter apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30; 584251881Speter 585251881Speter if (*str == NULL) 586251881Speter *str = svn_stringbuf_create_ensure(est_size, pool); 587251881Speter 588251881Speter svn_stringbuf_appendcstr(*str, "<"); 589251881Speter svn_stringbuf_appendcstr(*str, tagname); 590251881Speter 591251881Speter for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi)) 592251881Speter { 593251881Speter const void *key; 594251881Speter void *val; 595251881Speter 596251881Speter apr_hash_this(hi, &key, NULL, &val); 597251881Speter assert(val != NULL); 598251881Speter 599251881Speter svn_stringbuf_appendcstr(*str, "\n "); 600251881Speter svn_stringbuf_appendcstr(*str, key); 601251881Speter svn_stringbuf_appendcstr(*str, "=\""); 602251881Speter svn_xml_escape_attr_cstring(str, val, pool); 603251881Speter svn_stringbuf_appendcstr(*str, "\""); 604251881Speter } 605251881Speter 606251881Speter if (style == svn_xml_self_closing) 607251881Speter svn_stringbuf_appendcstr(*str, "/"); 608251881Speter svn_stringbuf_appendcstr(*str, ">"); 609251881Speter if (style != svn_xml_protect_pcdata) 610251881Speter svn_stringbuf_appendcstr(*str, "\n"); 611251881Speter} 612251881Speter 613251881Speter 614251881Spetervoid 615251881Spetersvn_xml_make_open_tag_v(svn_stringbuf_t **str, 616251881Speter apr_pool_t *pool, 617251881Speter enum svn_xml_open_tag_style style, 618251881Speter const char *tagname, 619251881Speter va_list ap) 620251881Speter{ 621251881Speter apr_pool_t *subpool = svn_pool_create(pool); 622251881Speter apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool); 623251881Speter 624251881Speter svn_xml_make_open_tag_hash(str, pool, style, tagname, ht); 625251881Speter svn_pool_destroy(subpool); 626251881Speter} 627251881Speter 628251881Speter 629251881Speter 630251881Spetervoid 631251881Spetersvn_xml_make_open_tag(svn_stringbuf_t **str, 632251881Speter apr_pool_t *pool, 633251881Speter enum svn_xml_open_tag_style style, 634251881Speter const char *tagname, 635251881Speter ...) 636251881Speter{ 637251881Speter va_list ap; 638251881Speter 639251881Speter va_start(ap, tagname); 640251881Speter svn_xml_make_open_tag_v(str, pool, style, tagname, ap); 641251881Speter va_end(ap); 642251881Speter} 643251881Speter 644251881Speter 645251881Spetervoid svn_xml_make_close_tag(svn_stringbuf_t **str, 646251881Speter apr_pool_t *pool, 647251881Speter const char *tagname) 648251881Speter{ 649251881Speter if (*str == NULL) 650251881Speter *str = svn_stringbuf_create_empty(pool); 651251881Speter 652251881Speter svn_stringbuf_appendcstr(*str, "</"); 653251881Speter svn_stringbuf_appendcstr(*str, tagname); 654251881Speter svn_stringbuf_appendcstr(*str, ">\n"); 655251881Speter} 656