Author: rinrab Date: Sun May 18 19:20:48 2025 New Revision: 1925686 URL: http://svn.apache.org/viewvc?rev=1925686&view=rev Log: On the 'xml-writer' branch: Implement the xml writer api.
* subversion/include/svn_xml.h (svn_xml_writer_t, (svn_xml_writer_create, svn_xml_writer_close, svn_xml_writer_flush, svn_xml_write_open_tag, svn_xml_write_open_tag_v, svn_xml_write_open_tag_hash, svn_xml_write_cdata_cstring, svn_xml_write_cdata, svn_xml_write_close_tag, svn_xml_write_header): Declare symbols. * subversion/libsvn_subr/xml_writer.c (xml_stream_write, xml_ensure_bytes, xml_write_byte, xml_write_bytes, xml_write_cstring): Implement functions for writing data to the buffer. (svn_xml_writer_create, svn_xml_writer_close, svn_xml_writer_flush, svn_xml_write_open_tag, svn_xml_write_open_tag_v, svn_xml_write_open_tag_hash, svn_xml_write_cdata_cstring, svn_xml_write_cdata, svn_xml_write_close_tag, svn_xml_write_header): Implement symbols. * subversion/tests/libsvn_subr/xml-test.c (test_xml_writer, test_xml_writer_always_flush): New tests. (test_funcs): Run those tests. Any kind of feedback will be much appreciated!! Added: subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c (with props) Modified: subversion/branches/xml-writer/subversion/include/svn_xml.h subversion/branches/xml-writer/subversion/tests/libsvn_subr/xml-test.c Modified: subversion/branches/xml-writer/subversion/include/svn_xml.h URL: http://svn.apache.org/viewvc/subversion/branches/xml-writer/subversion/include/svn_xml.h?rev=1925686&r1=1925685&r2=1925686&view=diff ============================================================================== --- subversion/branches/xml-writer/subversion/include/svn_xml.h (original) +++ subversion/branches/xml-writer/subversion/include/svn_xml.h Sun May 18 19:20:48 2025 @@ -399,6 +399,189 @@ svn_xml_make_close_tag(svn_stringbuf_t * apr_pool_t *pool, const char *tagname); +/*---------------------------------------------------------------*/ + +/** XML writer, writing XML tags to a generic stream. + * + * Summary + * --- + * + * The XML writer APIs provides functionality to write the XML tags to + * any writable stream, without having a need to manage a stringbuf, + * periodically write it to the output, and empty it then -- the XML + * writer implements this functionality. + * + * Usage + * --- + * + * 1. Create the writer using svn_xml_writer_create() before performing any + * writes. + * + * 2. During the lifetime of the XML writer, all the svn_xml_write_* functions + * can be safely invoked. Please note that they won't immediately write the + * data to the stream (more below), but you may force it using the + * svn_xml_writer_flush() function. + * + * 3. After the operation, the callers MUST close the writer using the + * svn_xml_writer_close() function. It will flush the remaining data in + * the buffer and close the ostream. + * + * Buffering + * --- + * + * The XML writer implements a temporary buffer, which will be filled with + * the content before it would have been flushed into the stream. This is + * used for optimization purposes, so we won't invoke the entire sequence + * of stream's write callbacks on each tag we want to write. The callers + * may use the svn_xml_writer_flush() function if they want to explicitly + * flush it to the stream. Otherwise, the writer will automatically flush + * the buffer as soon as it is about to exceed the limit. + * + * The buffering allows the callers to not care about how much tags do + * they want to write. If you are using it in a simple loop, no flushes are + * required. You may confidently rely to the automatic flush. However, if + * the code following after the write will wait for a long operation, for + * example, when the tag has been opened, but we need to do a request to a + * server, it's recommended to flush the buffer, so we won't have an opened + * tag partially written before the freeze. + * + * @since New in 1.15. + */ + +typedef struct svn_xml_writer_t svn_xml_writer_t; + +/** Create an XML writer for writing to @a ostream. Sets @a writer with + * the result, allocated in @a result_pool. + * + * @a ostream will be closed with the writer, in the svn_xml_writer_close() + * function. + * + * The callers MUST close the writer via svn_xml_writer_close(). + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_writer_create(svn_xml_writer_t **writer, + svn_stream_t *ostream, + apr_pool_t *result_pool); + +/** Close @a xml_writer. This will also close the stream. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_writer_close(svn_xml_writer_t *xml_writer); + +/** Flush the buffer of @a xml_writer to the stream. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_writer_flush(svn_xml_writer_t *xml_writer); + +/** Write a new xml tag @a tagname to @a xml_writer. + * + * Take the tag's attributes from varargs, a SVN_VA_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. + * + * Use @a scratch_pool for temporary allocations. + * + * This function is similar to svn_xml_make_open_tag(), but writes the + * result to an XML writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_open_tag(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, ...) SVN_NEEDS_SENTINEL_NULL; + +/** Like svn_xml_write_open_tag(), but takes a @c va_list instead of being + * variadic. + * + * This function is similar to svn_xml_make_open_tag_v(), but writes the + * result to an XML writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_open_tag_v(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, va_list ap); + +/** Like svn_xml_write_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. + * + * This function is similar to svn_xml_make_open_tag_hash(), but writes the + * result to an XML writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_open_tag_hash(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, apr_hash_t *attributes); + +/** Writes and escapes cdata from a NULL-terminated string @a str + * to @a xml_writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_cdata_cstring(svn_xml_writer_t *xml_writer, + const char *str); + +/** Writes and escapes @a len cdata chars from @a data to @a xml_writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_cdata(svn_xml_writer_t *xml_writer, + const char *data, apr_size_t len); + +/** Write @a tagname close tag to @a xml_writer. + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_close_tag(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + const char *tagname); + +/** Write an XML header to @a xml_writer. + * + * Fully-formed XML documents should start out with a header, + * something like <pre> + * \<?xml version="1.0" encoding="UTF-8"?\> + * </pre> + * + * @since New in 1.15. + */ +svn_error_t * +svn_xml_write_header(svn_xml_writer_t *xml_writer, + const char *encoding, + apr_pool_t *scratch_pool); #ifdef __cplusplus } Added: subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c URL: http://svn.apache.org/viewvc/subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c?rev=1925686&view=auto ============================================================================== --- subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c (added) +++ subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c Sun May 18 19:20:48 2025 @@ -0,0 +1,369 @@ +/* + * xml_writer.c: svn_xml_writer_t implementation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +/*** Includes. ***/ + +#include <assert.h> +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_io.h" +#include "svn_string.h" +#include "svn_xml.h" + +#include "svn_private_config.h" + +#define BUFFER_LENGTH 512 + + +/*** svn_xml_writer_t constructor and destructor ***/ + +struct svn_xml_writer_t +{ + /* buffer and its offset */ + char buffer[BUFFER_LENGTH]; + apr_size_t offset; + + /* an output stream, to write the XML to. */ + svn_stream_t *ostream; + + /* where this object is allocated, so we can free it easily */ + apr_pool_t *pool; +}; + +svn_error_t * +svn_xml_writer_create(svn_xml_writer_t **writer, + svn_stream_t *ostream, + apr_pool_t *result_pool) +{ + svn_xml_writer_t *result = apr_palloc(result_pool, sizeof(*result)); + + result->offset = 0; + result->ostream = ostream; + result->pool = result_pool; + + *writer = result; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_xml_writer_close(svn_xml_writer_t *writer) +{ + if (writer) + { + SVN_ERR(svn_xml_writer_flush(writer)); + SVN_ERR(svn_stream_close(writer->ostream)); + writer->ostream = NULL; + } + + return SVN_NO_ERROR; +} + + +/*** Buffering and writing routines ***/ + +static svn_error_t * +xml_stream_write(svn_xml_writer_t *writer, const char *data, apr_size_t len) +{ + apr_size_t write_len = len; + + /* We're gonna bail on an incomplete write here only because we know + that this stream is really stdout, which should never be blocking + on us. */ + SVN_ERR(svn_stream_write(writer->ostream, data, &write_len)); + if (write_len != len) + return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, + _("Error writing to stream")); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_xml_writer_flush(svn_xml_writer_t *writer) +{ + if (writer->offset > 0) + { + SVN_ERR(xml_stream_write(writer, writer->buffer, writer->offset)); + writer->offset = 0; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +xml_ensure_bytes(svn_xml_writer_t *writer, apr_size_t bytes) +{ + if (writer->offset + bytes > BUFFER_LENGTH) + SVN_ERR(svn_xml_writer_flush(writer)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +xml_write_byte(svn_xml_writer_t *writer, char b) +{ + SVN_ERR(xml_ensure_bytes(writer, 1)); + writer->buffer[writer->offset++] = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +xml_write_bytes(svn_xml_writer_t *writer, const char *data, apr_size_t len) +{ + if (len < BUFFER_LENGTH) + { + SVN_ERR(xml_ensure_bytes(writer, len)); + memcpy(writer->buffer + writer->offset, data, len); + writer->offset += len; + } + else + { + SVN_ERR(svn_xml_writer_flush(writer)); + SVN_ERR(xml_stream_write(writer, data, len)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +xml_write_cstring(svn_xml_writer_t *writer, const char *str) +{ + return svn_error_trace(xml_write_bytes(writer, str, strlen(str))); +} + +svn_error_t * +svn_xml_write_raw(svn_xml_writer_t *writer, + const char *data, apr_size_t len) +{ + return svn_error_trace(xml_write_bytes(writer, data, len)); +} + +/* XML Escaping */ + +static svn_error_t * +xml_write_escaped_attr(svn_xml_writer_t *xml_writer, + const char *data, apr_size_t len) +{ + const char *end = data + len; + const char *p = data, *q; + + while (1) + { + /* Find a character which needs to be quoted and append bytes up + to that point. */ + q = p; + while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '"' && + *q != '\'' && *q != '\r' && *q != '\n' && *q != '\t') + q++; + SVN_ERR(xml_write_bytes(xml_writer, p, q - p)); + + /* We may already be a winner. */ + if (q == end) + break; + + /* Append the entity reference for the character. */ + if (*q == '&') + SVN_ERR(xml_write_cstring(xml_writer, "&")); + else if (*q == '<') + SVN_ERR(xml_write_cstring(xml_writer, "<")); + else if (*q == '>') + SVN_ERR(xml_write_cstring(xml_writer, ">")); + else if (*q == '"') + SVN_ERR(xml_write_cstring(xml_writer, """)); + else if (*q == '\'') + SVN_ERR(xml_write_cstring(xml_writer, "'")); + else if (*q == '\r') + SVN_ERR(xml_write_cstring(xml_writer, " ")); + else if (*q == '\n') + SVN_ERR(xml_write_cstring(xml_writer, " ")); + else if (*q == '\t') + SVN_ERR(xml_write_cstring(xml_writer, "	")); + + p = q + 1; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_xml_write_cdata(svn_xml_writer_t *xml_writer, + const char *data, apr_size_t len) +{ + const char *end = data + len; + const char *p = data, *q; + + while (1) + { + /* Find a character which needs to be quoted and append bytes up + to that point. Strictly speaking, '>' only needs to be + quoted if it follows "]]", but it's easier to quote it all + the time. + + So, why are we escaping '\r' here? Well, according to the + XML spec, '\r\n' gets converted to '\n' during XML parsing. + Also, any '\r' not followed by '\n' is converted to '\n'. By + golly, if we say we want to escape a '\r', we want to make + sure it remains a '\r'! */ + q = p; + while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r') + q++; + SVN_ERR(xml_write_bytes(xml_writer, p, q - p)); + + /* We may already be a winner. */ + if (q == end) + break; + + /* Append the entity reference for the character. */ + if (*q == '&') + SVN_ERR(xml_write_cstring(xml_writer, "&")); + else if (*q == '<') + SVN_ERR(xml_write_cstring(xml_writer, "<")); + else if (*q == '>') + SVN_ERR(xml_write_cstring(xml_writer, ">")); + else if (*q == '\r') + SVN_ERR(xml_write_cstring(xml_writer, " ")); + + p = q + 1; + } + + return SVN_NO_ERROR; +} + + + +/*** Writing an open tag ***/ + +static svn_error_t * +xml_write_attribute(svn_xml_writer_t *xml_writer, + const char *key, const char *val) +{ + SVN_ERR(xml_write_cstring(xml_writer, "\n ")); + SVN_ERR(xml_write_cstring(xml_writer, key)); + SVN_ERR(xml_write_cstring(xml_writer, "=\"")); + SVN_ERR(xml_write_escaped_attr(xml_writer, val, strlen(val))); + SVN_ERR(xml_write_byte(xml_writer, '"')); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_xml_write_open_tag_hash(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, apr_hash_t *attributes) +{ + apr_hash_index_t *hi; + + SVN_ERR(xml_write_byte(xml_writer, '<')); + SVN_ERR(xml_write_cstring(xml_writer, tagname)); + + for (hi = apr_hash_first(scratch_pool, attributes); + hi; + hi = apr_hash_next(hi)) + { + const void *key; + void *val; + + apr_hash_this(hi, &key, NULL, &val); + assert(val != NULL); + + SVN_ERR(xml_write_attribute(xml_writer, key, val)); + } + + if (style == svn_xml_self_closing) + SVN_ERR(xml_write_byte(xml_writer, '/')); + SVN_ERR(xml_write_byte(xml_writer, '>')); + if (style != svn_xml_protect_pcdata) + SVN_ERR(xml_write_byte(xml_writer, '\n')); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_xml_write_open_tag_v(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, + va_list ap) +{ + apr_hash_t *ht = svn_xml_ap_to_hash(ap, scratch_pool); + return svn_error_trace(svn_xml_write_open_tag_hash(xml_writer, scratch_pool, + style, tagname, ht)); +} + +svn_error_t * +svn_xml_write_open_tag(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + enum svn_xml_open_tag_style style, + const char *tagname, + ...) +{ + va_list ap; + svn_error_t *err; + + va_start(ap, tagname); + err = svn_xml_write_open_tag_v(xml_writer, scratch_pool, style, tagname, ap); + va_end(ap); + + return svn_error_trace(err); +} + + + +svn_error_t * +svn_xml_write_cdata_cstring(svn_xml_writer_t *xml_writer, const char *str) +{ + return svn_error_trace(svn_xml_write_cdata(xml_writer, str, strlen(str))); +} + +/* close tag */ + +svn_error_t * +svn_xml_write_close_tag(svn_xml_writer_t *xml_writer, + apr_pool_t *scratch_pool, + const char *tagname) +{ + SVN_ERR(xml_write_cstring(xml_writer, "</")); + SVN_ERR(xml_write_cstring(xml_writer, tagname)); + SVN_ERR(xml_write_cstring(xml_writer, ">\n")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_xml_write_header(svn_xml_writer_t *xml_writer, + const char *encoding, + apr_pool_t *scratch_pool) +{ + SVN_ERR(xml_write_cstring(xml_writer, "<?xml version=\"1.0\"")); + if (encoding) + { + SVN_ERR(xml_write_cstring(xml_writer, " encoding=\"")); + SVN_ERR(xml_write_cstring(xml_writer, encoding)); + SVN_ERR(xml_write_cstring(xml_writer, "\"")); + } + SVN_ERR(xml_write_cstring(xml_writer, "?>\n")); + + return SVN_NO_ERROR; +} Propchange: subversion/branches/xml-writer/subversion/libsvn_subr/xml_writer.c ------------------------------------------------------------------------------ svn:eol-style = native Modified: subversion/branches/xml-writer/subversion/tests/libsvn_subr/xml-test.c URL: http://svn.apache.org/viewvc/subversion/branches/xml-writer/subversion/tests/libsvn_subr/xml-test.c?rev=1925686&r1=1925685&r2=1925686&view=diff ============================================================================== --- subversion/branches/xml-writer/subversion/tests/libsvn_subr/xml-test.c (original) +++ subversion/branches/xml-writer/subversion/tests/libsvn_subr/xml-test.c Sun May 18 19:20:48 2025 @@ -396,6 +396,85 @@ test_xml_simple_attr_escape(apr_pool_t * return SVN_NO_ERROR; } +static svn_error_t * +test_xml_writer(apr_pool_t *pool) +{ + svn_stringbuf_t *str = svn_stringbuf_create_empty(pool); + svn_stream_t *stream = svn_stream_from_stringbuf(str, pool); + svn_xml_writer_t *xml_writer; + + SVN_ERR(svn_xml_writer_create(&xml_writer, stream, pool)); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "root", + SVN_VA_NULL)); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "tag1", + SVN_VA_NULL)); + SVN_ERR(svn_xml_write_cdata_cstring(xml_writer, "value")); + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "tag1")); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "tag2", + "a", "v", SVN_VA_NULL)); + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "tag2")); + + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "root")); + + SVN_ERR(svn_xml_writer_close(xml_writer)); + + SVN_TEST_STRING_ASSERT(str->data, "<root>\n" + "<tag1>\n" + "value</tag1>\n" + "<tag2\n" + " a=\"v\">\n" + "</tag2>\n" + "</root>\n"); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_xml_writer_always_flush(apr_pool_t *pool) +{ + svn_stringbuf_t *str = svn_stringbuf_create_empty(pool); + svn_stream_t *stream = svn_stream_from_stringbuf(str, pool); + svn_xml_writer_t *xml_writer; + + SVN_ERR(svn_xml_writer_create(&xml_writer, stream, pool)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "root", + SVN_VA_NULL)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "tag1", + SVN_VA_NULL)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + SVN_ERR(svn_xml_write_cdata_cstring(xml_writer, "value")); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "tag1")); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + + SVN_ERR(svn_xml_write_open_tag(xml_writer, pool, svn_xml_normal, "tag2", + "a", "v", SVN_VA_NULL)); + SVN_ERR(svn_xml_writer_flush(xml_writer)); + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "tag2")); + SVN_ERR(svn_xml_write_close_tag(xml_writer, pool, "root")); + + SVN_ERR(svn_xml_writer_close(xml_writer)); + + SVN_TEST_STRING_ASSERT(str->data, "<root>\n" + "<tag1>\n" + "value</tag1>\n" + "<tag2\n" + " a=\"v\">\n" + "</tag2>\n" + "</root>\n"); + + return SVN_NO_ERROR; +} + /* The test table. */ static int max_threads = 1; @@ -426,6 +505,10 @@ static struct svn_test_descriptor_t test "simple XML cdata escaping test"), SVN_TEST_PASS2(test_xml_simple_attr_escape, "simple XML attribute escaping test"), + SVN_TEST_PASS2(test_xml_writer, + "test svn_xml_writer_t"), + SVN_TEST_PASS2(test_xml_writer_always_flush, + "test flush xml-writer after each action"), SVN_TEST_NULL };