/**
 * @copyright
 * ====================================================================
 * Copyright (c) 2000-2006, 2008 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 * @endcopyright
 *
 * @file svn_xml.h
 * @brief XML code shared by various Subversion libraries.
 */

#ifndef SVN_XML_H
#define SVN_XML_H

#include <apr.h>
#include <apr_pools.h>
#include <apr_hash.h>

#include "svn_types.h"
#include "svn_string.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/** The namespace all Subversion XML uses. */
#define SVN_XML_NAMESPACE "svn:"

/** Used as style argument to svn_xml_make_open_tag() and friends. */
enum svn_xml_open_tag_style {
  /** <tag ...> */
  svn_xml_normal = 1,

  /** <tag ...>, no cosmetic newline */
  svn_xml_protect_pcdata,

  /** <tag .../>  */
  svn_xml_self_closing
};



/** Determine if a string of character @a data of length @a len is a
 * safe bet for use with the svn_xml_escape_* functions found in this
 * header.
 *
 * Return @c TRUE if it is, @c FALSE otherwise.
 *
 * Essentially, this function exists to determine whether or not
 * simply running a string of bytes through the Subversion XML escape
 * routines will produce legitimate XML.  It should only be necessary
 * for data which might contain bytes that cannot be safely encoded
 * into XML (certain control characters, for example).
 */
svn_boolean_t
svn_xml_is_xml_safe(const char *data,
                    apr_size_t len);

/** Create or append in @a *outstr an xml-escaped version of @a string,
 * suitable for output as character data.
 *
 * If @a *outstr is @c NULL, set @a *outstr to a new stringbuf allocated
 * in @a pool, else append to the existing stringbuf there.
 */
void
svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
                               const svn_stringbuf_t *string,
                               apr_pool_t *pool);

/** Same as svn_xml_escape_cdata_stringbuf(), but @a string is an
 * @c svn_string_t.
 */
void
svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
                            const svn_string_t *string,
                            apr_pool_t *pool);

/** Same as svn_xml_escape_cdata_stringbuf(), but @a string is a
 * NULL-terminated C string.
 */
void
svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
                             const char *string,
                             apr_pool_t *pool);


/** Create or append in @a *outstr an xml-escaped version of @a string,
 * suitable for output as an attribute value.
 *
 * If @a *outstr is @c NULL, set @a *outstr to a new stringbuf allocated
 * in @a pool, else append to the existing stringbuf there.
 */
void
svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
                              const svn_stringbuf_t *string,
                              apr_pool_t *pool);

/** Same as svn_xml_escape_attr_stringbuf(), but @a string is an
 * @c svn_string_t.
 */
void
svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
                           const svn_string_t *string,
                           apr_pool_t *pool);

/** Same as svn_xml_escape_attr_stringbuf(), but @a string is a
 * NULL-terminated C string.
 */
void
svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
                            const char *string,
                            apr_pool_t *pool);

/**
 * Return UTF-8 string @a string if it contains no characters that are
 * unrepresentable in XML.  Else, return a copy of @a string,
 * allocated in @a pool, with each unrepresentable character replaced
 * by "?\uuu", where "uuu" is the three-digit unsigned decimal value
 * of that character.
 *
 * Neither the input nor the output need be valid XML; however, the
 * output can always be safely XML-escaped.
 *
 * @note The current implementation treats all Unicode characters as
 * representable, except for most ASCII control characters (the
 * exceptions being CR, LF, and TAB, which are valid in XML).  There
 * may be other UTF-8 characters that are invalid in XML; see
 * http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=90591
 * and its thread for details.
 *
 * @since New in 1.2.
 */
const char *
svn_xml_fuzzy_escape(const char *string,
                     apr_pool_t *pool);


/*---------------------------------------------------------------*/

/* Generalized Subversion XML Parsing */

/** A generalized Subversion XML parser object */
typedef struct svn_xml_parser_t svn_xml_parser_t;

typedef void (*svn_xml_start_elem)(void *baton,
                                   const char *name,
                                   const char **atts);

typedef void (*svn_xml_end_elem)(void *baton, const char *name);

/* data is not NULL-terminated. */
typedef void (*svn_xml_char_data)(void *baton,
                                  const char *data,
                                  apr_size_t len);


/** Create a general Subversion XML parser */
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);


/** Free a general Subversion XML parser */
void
svn_xml_free_parser(svn_xml_parser_t *svn_parser);


/** Push @a len bytes of xml data in @a buf at @a svn_parser.
 *
 * If this is the final push, @a is_final must be set.
 *
 * An error will be returned if there was a syntax problem in the XML,
 * or if any of the callbacks set an error using
 * svn_xml_signal_bailout().
 *
 * If an error is returned, the @c svn_xml_parser_t will have been freed
 * automatically, so the caller should not call svn_xml_free_parser().
 */
svn_error_t *
svn_xml_parse(svn_xml_parser_t *svn_parser,
              const char *buf,
              apr_size_t len,
              svn_boolean_t is_final);



/** The way to officially bail out of xml parsing.
 *
 * Store @a error in @a svn_parser and set all expat callbacks to @c NULL.
 */
void
svn_xml_signal_bailout(svn_error_t *error,
                       svn_xml_parser_t *svn_parser);





/*** Helpers for dealing with the data Expat gives us. ***/

/** Return the value associated with @a name in expat attribute array @a atts,
 * else return @c NULL.
 *
 * (There could never be a @c NULL attribute value in the XML,
 * although the empty string is possible.)
 *
 * @a atts is an array of c-strings: even-numbered indexes are names,
 * odd-numbers hold values.  If all is right, it should end on an
 * even-numbered index pointing to @c NULL.
 */
const char *
svn_xml_get_attr_value(const char *name,
                       const char **atts);



/* Converting between Expat attribute lists and APR hash tables. */


/** Create an attribute hash from @c va_list @a ap.
 *
 * The contents of @a ap are alternating <tt>char *</tt> keys and
 * <tt>char *</tt> vals, terminated by a final @c NULL falling on an
 * even index (zero-based).
 */
apr_hash_t *
svn_xml_ap_to_hash(va_list ap,
                   apr_pool_t *pool);

/** Create a hash that corresponds to Expat xml attribute list @a atts.
 *
 * The hash's keys and values are <tt>char *</tt>'s.
 *
 * @a atts may be NULL, in which case you just get an empty hash back
 * (this makes life more convenient for some callers).
 */
apr_hash_t *
svn_xml_make_att_hash(const char **atts,
                      apr_pool_t *pool);


/** Like svn_xml_make_att_hash(), but takes a hash and preserves any
 * key/value pairs already in it.
 */
void
svn_xml_hash_atts_preserving(const char **atts,
                             apr_hash_t *ht,
                             apr_pool_t *pool);

/** Like svn_xml_make_att_hash(), but takes a hash and overwrites
 * key/value pairs already in it that also appear in @a atts.
 */
void
svn_xml_hash_atts_overlaying(const char **atts,
                             apr_hash_t *ht,
                             apr_pool_t *pool);



/* Printing XML */

/** Create an XML header and return it in @a *str.
 *
 * Fully-formed XML documents should start out with a header,
 * something like
 *         \<?xml version="1.0" encoding="utf-8"?\>
 *
 * This function returns such a header.  @a *str must either be @c NULL, in
 * which case a new string is created, or it must point to an existing
 * string to be appended to.
 */
void
svn_xml_make_header(svn_stringbuf_t **str,
                    apr_pool_t *pool);


/** Store a new xml tag @a tagname in @a *str.
 *
 * If @a *str is @c NULL, set @a *str to a new stringbuf allocated
 * in @a pool, else append to the existing stringbuf there.
 *
 * Take the tag's attributes from varargs, a NULL-terminated list of
 * alternating <tt>char *</tt> key and <tt>char *</tt> val.  Do xml-escaping
 * on each val.
 *
 * @a style is one of the enumerated styles in @c svn_xml_open_tag_style.
 */
void
svn_xml_make_open_tag(svn_stringbuf_t **str,
                      apr_pool_t *pool,
                      enum svn_xml_open_tag_style style,
                      const char *tagname,
                      ...);


/** Like svn_xml_make_open_tag(), but takes a @c va_list instead of being
 * variadic.
 */
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);


/** Like svn_xml_make_open_tag(), but takes a hash table of attributes
 * (<tt>char *</tt> keys mapping to <tt>char *</tt> values).
 *
 * You might ask, why not just provide svn_xml_make_tag_atts()?
 *
 * The reason is that a hash table is the most natural interface to an
 * attribute list; the fact that Expat uses <tt>char **</tt> atts instead is
 * certainly a defensible implementation decision, but since we'd have
 * to have special code to support such lists throughout Subversion
 * anyway, we might as well write that code for the natural interface
 * (hashes) and then convert in the few cases where conversion is
 * needed.  Someday it might even be nice to change expat-lite to work
 * with apr hashes.
 *
 * See conversion functions svn_xml_make_att_hash() and
 * svn_xml_make_att_hash_overlaying().  Callers should use those to
 * convert Expat attr lists into hashes when necessary.
 */
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);


/** Store an xml close tag @a tagname in @a str.
 *
 * If @a *str is @c NULL, set @a *str to a new stringbuf allocated
 * in @a pool, else append to the existing stringbuf there.
 */
void
svn_xml_make_close_tag(svn_stringbuf_t **str,
                       apr_pool_t *pool,
                       const char *tagname);


#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* SVN_XML_H */
