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, "&amp;");
145251881Speter      else if (*q == '<')
146251881Speter        svn_stringbuf_appendcstr(*outstr, "&lt;");
147251881Speter      else if (*q == '>')
148251881Speter        svn_stringbuf_appendcstr(*outstr, "&gt;");
149251881Speter      else if (*q == '\r')
150251881Speter        svn_stringbuf_appendcstr(*outstr, "&#13;");
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, "&amp;");
188251881Speter      else if (*q == '<')
189251881Speter        svn_stringbuf_appendcstr(*outstr, "&lt;");
190251881Speter      else if (*q == '>')
191251881Speter        svn_stringbuf_appendcstr(*outstr, "&gt;");
192251881Speter      else if (*q == '"')
193251881Speter        svn_stringbuf_appendcstr(*outstr, "&quot;");
194251881Speter      else if (*q == '\'')
195251881Speter        svn_stringbuf_appendcstr(*outstr, "&apos;");
196251881Speter      else if (*q == '\r')
197251881Speter        svn_stringbuf_appendcstr(*outstr, "&#13;");
198251881Speter      else if (*q == '\n')
199251881Speter        svn_stringbuf_appendcstr(*outstr, "&#10;");
200251881Speter      else if (*q == '\t')
201251881Speter        svn_stringbuf_appendcstr(*outstr, "&#9;");
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