Skip to content

Commit

Permalink
Implement wrapper around XML parser into an svn_stream_t.
Browse files Browse the repository at this point in the history
The current XML parser API needs a bunch of function to invoke. Firstly
it requires to create a parser using svn_xml_make_parser(), then push
the chunks using svn_xml_parse(), and finally close/free/dispose the
parser using svn_xml_free_parser(). This API sometimes over complicate
the usage or may require some additional work for the users.

This commit introduces a wrapper into an svn_stream_t. It may help the
users to create a parser from any kind of stream or a file.

Use code like this to parse an XML file, assuming callbacks implemented
separately:

[[[
svn_stream_t *fstream;
svn_stream_t *xml_stream;
svn_xml_parser_t *xml_parser;

SVN_ERR(svn_stream_open_readonly(&fstream, fpath, pool, pool));

xml_parser = svn_xml_make_parser(baton, start_handler, end_handler,
                                 data_handler, pool);
xml_stream = svn_xml_make_parse_stream(xml_parser, pool);

SVN_ERR(svn_stream_copy3(fstream, xml_stream, NULL, NULL, pool));
/* svn_stream_copy3() automatically closes fstream and xml_stream */
]]]

...This makes it simpler a lot, due to a unified stream.

Also adding the declaration of the svn_xml_make_parse_stream() function with
its docstring to the log-message:

[[[
/** Create a stream that wraps the XML parser described at @A parser.
 *
 * The stream produced will implement 'write' and 'close' methods. It
 * will push the data to the parser on write operation, and flush it on
 * close.
 *
 * This stream can be used as a generic writable stream, so the callers
 * may pipe any data there, for example, using the svn_stream_copy3
 * function, in case of a file source.
 *
 * @SInCE New in 1.15.
 */
svn_stream_t *
svn_xml_make_parse_stream(svn_xml_parser_t *parser,
                          apr_pool_t *result_pool);
]]]

Currently there are no usages of this function, only the tests have been
introduced. In the feature, this could be used for the xpatch parser.

This stream would have been implemented in a separate file, xml_stream.c, as
part of the libsvn_subr API.

* subversion/include/svn_xml.h
  (includes): Add 'svn_io.h' for svn_stream_t.
  (svn_xml_make_parse_stream): Declare function.
* subversion/libsvn_subr/xml_stream.c: New file.
  (xml_stream_baton_t): New baton.
  (xml_stream_write, xml_stream_close): New functions, implementing
   svn_stream_t interface.
  (svn_xml_make_parse_stream): Implement function.
* subversion/tests/libsvn_subr/xml-test.c
  (test_xml_parse_stream, test_xml_parse_stream_invalid_xml): New tests.
  (test_funcs): Run these tests.


git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1922567 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
rinrab committed Dec 17, 2024
1 parent b2ae6ef commit dc7fe2f
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
18 changes: 18 additions & 0 deletions subversion/include/svn_xml.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "svn_types.h"
#include "svn_string.h"
#include "svn_io.h" /* for svn_stream_t */

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -191,6 +192,23 @@ void
svn_xml_free_parser(svn_xml_parser_t *svn_parser);


/** Create a stream that wraps the XML parser described at @a parser.
*
* The stream produced will implement 'write' and 'close' methods. It
* will push the data to the parser on write operation, and flush it on
* close.
*
* This stream can be used as a generic writable stream, so the callers
* may pipe any data there, for example, using the svn_stream_copy3
* function, in case of a file source.
*
* @since New in 1.15.
*/
svn_stream_t *
svn_xml_make_parse_stream(svn_xml_parser_t *parser,
apr_pool_t *result_pool);


/** 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.
Expand Down
103 changes: 103 additions & 0 deletions subversion/libsvn_subr/xml_stream.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* xml_stream.c: implements a writable XML parse stream
*
* ====================================================================
* 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 "svn_error.h"
#include "svn_io.h"
#include "svn_xml.h"


typedef struct xml_stream_baton_t
{
/* Handle to an XML parser. NULL means that the parser has been already
disposed or we've closed the stream. */
svn_xml_parser_t *parser;
} xml_stream_baton_t;


/* This implements svn_write_fn_t. */
static svn_error_t *
xml_stream_write(void *baton, const char *data, apr_size_t *len)
{
xml_stream_baton_t *b = baton;
svn_error_t *err;

err = svn_xml_parse(b->parser, data, *len, FALSE);

if (err)
{
/* Dispose the parser due to an error. */
svn_xml_free_parser(b->parser);
b->parser = NULL;
}

return svn_error_trace(err);
}

/* This implements svn_close_fn_t. */
static svn_error_t *
xml_stream_close(void *baton)
{
xml_stream_baton_t *b = baton;

if (b->parser)
{
/* Dispose the parser with a final push because we are closing
the stream. */
SVN_ERR(svn_xml_parse(b->parser, NULL, 0, TRUE));
svn_xml_free_parser(b->parser);
b->parser = NULL;
}
else
{
/* We have nothing to do with a disposed stream; Probably it failed
with an error before or we are now closing it second time. */
}

return SVN_NO_ERROR;
}



/* Public Interface */

svn_stream_t *
svn_xml_make_parse_stream(svn_xml_parser_t *parser,
apr_pool_t *result_pool)
{
svn_stream_t *result;
xml_stream_baton_t *baton;

baton = apr_pcalloc(result_pool, sizeof(*baton));
baton->parser = parser;

result = svn_stream_create(baton, result_pool);

svn_stream_set_write(result, xml_stream_write);
svn_stream_set_close(result, xml_stream_close);

return result;
}
49 changes: 49 additions & 0 deletions subversion/tests/libsvn_subr/xml-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,51 @@ test_xml_doctype_declaration(apr_pool_t *pool)
return SVN_NO_ERROR;
}

static svn_error_t *
test_xml_parse_stream(apr_pool_t *pool)
{
const char *xml = "<root><tag1>value</tag1><tag2 a='v' /></root>";
xml_callbacks_baton_t b;
svn_stream_t *stream;

/* Test parsing XML in one chunk.*/
b.buf = svn_stringbuf_create_empty(pool);
b.parser = svn_xml_make_parser(&b, strbuf_start_elem, strbuf_end_elem,
strbuf_cdata, pool);
stream = svn_xml_make_parse_stream(b.parser, pool);

SVN_ERR(svn_stream_puts(stream, xml));
SVN_ERR(svn_stream_close(stream));

SVN_TEST_STRING_ASSERT(b.buf->data,
"<root><tag1>value</tag1><tag2 a=v></tag2></root>");

return SVN_NO_ERROR;
}

static svn_error_t *
test_xml_parse_stream_invalid_xml(apr_pool_t *pool)
{
const char *xml = "<root><tag1></tag1>";
xml_callbacks_baton_t b;
svn_stream_t *stream;
svn_error_t *err;
apr_status_t status;

/* Test parsing XML in one chunk.*/
b.buf = svn_stringbuf_create_empty(pool);
b.parser = svn_xml_make_parser(&b, strbuf_start_elem, strbuf_end_elem,
strbuf_cdata, pool);
stream = svn_xml_make_parse_stream(b.parser, pool);

err = svn_error_compose_create(svn_stream_puts(stream, xml),
svn_stream_close(stream));

SVN_TEST_ASSERT_ANY_ERROR(err);

return SVN_NO_ERROR;
}

/* The test table. */
static int max_threads = 1;

Expand All @@ -354,6 +399,10 @@ static struct svn_test_descriptor_t test_funcs[] =
"test XML custom entity expansion"),
SVN_TEST_PASS2(test_xml_doctype_declaration,
"test XML doctype declaration"),
SVN_TEST_PASS2(test_xml_parse_stream,
"test XML's svn_stream_t wrapper"),
SVN_TEST_PASS2(test_xml_parse_stream_invalid_xml,
"test XML's svn_stream_t wrapper for invalid XML"),
SVN_TEST_NULL
};

Expand Down

0 comments on commit dc7fe2f

Please sign in to comment.