mirror of
https://github.com/open62541/open62541.git
synced 2026-10-16 20:33:18 +08:00
feat(yxml): support content fragment parsing
This commit is contained in:
committed by
Julius Pfrommer
parent
437995bc22
commit
e32ab7d181
Vendored
+12
-3
@@ -222,7 +222,7 @@ static yxml_ret_t yxml_selfclose(yxml_t *x, unsigned ch) {
|
|||||||
return YXML_ELEMEND;
|
return YXML_ELEMEND;
|
||||||
}
|
}
|
||||||
x->elem = (char *)x->stack;
|
x->elem = (char *)x->stack;
|
||||||
x->state = YXMLS_misc3;
|
x->state = x->contentmode ? YXMLS_misc2 : YXMLS_misc3;
|
||||||
return YXML_ELEMEND;
|
return YXML_ELEMEND;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +292,7 @@ static yxml_ret_t yxml_refend(yxml_t *x, yxml_ret_t ret) {
|
|||||||
if(!ch || ch > 0x10FFFF || ch == 0xFFFE || ch == 0xFFFF || (ch-0xDFFF) < 0x7FF)
|
if(!ch || ch > 0x10FFFF || ch == 0xFFFE || ch == 0xFFFF || (ch-0xDFFF) < 0x7FF)
|
||||||
return YXML_EREF;
|
return YXML_EREF;
|
||||||
yxml_setutf8(x->data, ch);
|
yxml_setutf8(x->data, ch);
|
||||||
|
x->isReference = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +309,15 @@ void yxml_init(yxml_t *x, void *stack, size_t stacksize) {
|
|||||||
x->state = YXMLS_init;
|
x->state = YXMLS_init;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void yxml_init_content(yxml_t *x, void *stack, size_t stacksize) {
|
||||||
|
yxml_init(x, stack, stacksize);
|
||||||
|
x->contentmode = true;
|
||||||
|
x->state = YXMLS_misc2;
|
||||||
|
}
|
||||||
|
|
||||||
yxml_ret_t yxml_parse(yxml_t *x, int _ch) {
|
yxml_ret_t yxml_parse(yxml_t *x, int _ch) {
|
||||||
|
x->isReference = false;
|
||||||
|
|
||||||
/* Ensure that characters are in the range of 0..255 rather than -126..125.
|
/* Ensure that characters are in the range of 0..255 rather than -126..125.
|
||||||
* All character comparisons are done with positive integers. */
|
* All character comparisons are done with positive integers. */
|
||||||
unsigned ch = (unsigned)(_ch+256) & 0xff;
|
unsigned ch = (unsigned)(_ch+256) & 0xff;
|
||||||
@@ -1026,8 +1035,8 @@ yxml_ret_t yxml_parse(yxml_t *x, int _ch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yxml_ret_t yxml_eof(yxml_t *x) {
|
yxml_ret_t yxml_eof(yxml_t *x) {
|
||||||
if(x->state != YXMLS_misc3)
|
if(x->state != YXMLS_misc3 &&
|
||||||
|
!(x->contentmode && x->state == YXMLS_misc2 && x->stacklen == 0))
|
||||||
return YXML_EEOF;
|
return YXML_EEOF;
|
||||||
return YXML_OK;
|
return YXML_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+17
-5
@@ -23,6 +23,7 @@
|
|||||||
#ifndef YXML_H
|
#ifndef YXML_H
|
||||||
#define YXML_H
|
#define YXML_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
@@ -94,6 +95,10 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
char data[8];
|
char data[8];
|
||||||
|
|
||||||
|
/* True if the current YXML_CONTENT or YXML_ATTRVAL event was produced from
|
||||||
|
* a character or entity reference. Valid until the next yxml_parse() call. */
|
||||||
|
bool isReference;
|
||||||
|
|
||||||
/* Name of the current attribute. Changed after YXML_ATTRSTART, valid up to
|
/* Name of the current attribute. Changed after YXML_ATTRSTART, valid up to
|
||||||
* and including the next YXML_ATTREND. */
|
* and including the next YXML_ATTREND. */
|
||||||
char *attr;
|
char *attr;
|
||||||
@@ -119,6 +124,7 @@ typedef struct {
|
|||||||
int nextstate; /* Used for '@' state remembering and for the "string" consuming state */
|
int nextstate; /* Used for '@' state remembering and for the "string" consuming state */
|
||||||
unsigned ignore;
|
unsigned ignore;
|
||||||
unsigned char *string;
|
unsigned char *string;
|
||||||
|
bool contentmode;
|
||||||
} yxml_t;
|
} yxml_t;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -127,14 +133,20 @@ extern "C" {
|
|||||||
|
|
||||||
void yxml_init(yxml_t *, void *, size_t);
|
void yxml_init(yxml_t *, void *, size_t);
|
||||||
|
|
||||||
|
/* Initialize the parser for an XML content fragment without an enclosing
|
||||||
|
* element. Character data, references, CDATA sections, comments, processing
|
||||||
|
* instructions and balanced child elements are accepted. */
|
||||||
|
void yxml_init_content(yxml_t *, void *, size_t);
|
||||||
|
|
||||||
yxml_ret_t yxml_parse(yxml_t *, int);
|
yxml_ret_t yxml_parse(yxml_t *, int);
|
||||||
|
|
||||||
/* May be called after the last character has been given to yxml_parse().
|
/* May be called after the last character has been given to yxml_parse().
|
||||||
* Returns YXML_OK if the XML document is valid, YXML_EEOF otherwise. Using
|
* Returns YXML_OK if the XML document or content fragment is valid, YXML_EEOF
|
||||||
* this function isn't really necessary, but can be used to detect documents
|
* otherwise. Using this function isn't really necessary, but can be used to
|
||||||
* that don't end correctly. In particular, an error is returned when the XML
|
* detect input that doesn't end correctly. In document mode, an error is
|
||||||
* document did not contain a (complete) root element, or when the document
|
* returned when there is no complete root element. In content mode, an error
|
||||||
* ended while in a comment or processing instruction. */
|
* is returned for unbalanced child elements. Both modes reject input ending
|
||||||
|
* inside a reference, CDATA section, comment or processing instruction. */
|
||||||
yxml_ret_t yxml_eof(yxml_t *);
|
yxml_ret_t yxml_eof(yxml_t *);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -37,6 +37,31 @@ yxml_parse_full(const char *xml, size_t len, char *buf, size_t buflen,
|
|||||||
return yxml_eof(&x);
|
return yxml_eof(&x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static yxml_ret_t
|
||||||
|
yxml_parse_content_full(const char *xml, char *buf, size_t buflen,
|
||||||
|
char *out, size_t outsize, size_t *outlen,
|
||||||
|
size_t *references) {
|
||||||
|
yxml_t x;
|
||||||
|
yxml_init_content(&x, buf, buflen);
|
||||||
|
*outlen = 0;
|
||||||
|
*references = 0;
|
||||||
|
for(size_t i = 0; i < strlen(xml); i++) {
|
||||||
|
yxml_ret_t ret = yxml_parse(&x, (unsigned char)xml[i]);
|
||||||
|
if(ret < YXML_OK)
|
||||||
|
return ret;
|
||||||
|
if(x.isReference)
|
||||||
|
(*references)++;
|
||||||
|
if(ret != YXML_CONTENT)
|
||||||
|
continue;
|
||||||
|
size_t length = strlen(x.data);
|
||||||
|
if(*outlen > outsize || length > outsize - *outlen)
|
||||||
|
return YXML_ESTACK;
|
||||||
|
memcpy(&out[*outlen], x.data, length);
|
||||||
|
*outlen += length;
|
||||||
|
}
|
||||||
|
return yxml_eof(&x);
|
||||||
|
}
|
||||||
|
|
||||||
/* === xml_tokenize wrapper tests === */
|
/* === xml_tokenize wrapper tests === */
|
||||||
|
|
||||||
START_TEST(parseElement) {
|
START_TEST(parseElement) {
|
||||||
@@ -344,6 +369,75 @@ START_TEST(yxmlEntityRef_content) {
|
|||||||
ck_assert_int_gt(cnt, 0);
|
ck_assert_int_gt(cnt, 0);
|
||||||
} END_TEST
|
} END_TEST
|
||||||
|
|
||||||
|
START_TEST(yxmlEntityRef_flag) {
|
||||||
|
char buf[512];
|
||||||
|
const char *xml = "<r a=\"plain;&plain\">plain;Aplain</r>";
|
||||||
|
yxml_t x;
|
||||||
|
yxml_init(&x, buf, sizeof(buf));
|
||||||
|
size_t contentReferences = 0;
|
||||||
|
size_t attributeReferences = 0;
|
||||||
|
for(size_t i = 0; i < strlen(xml); i++) {
|
||||||
|
yxml_ret_t ret = yxml_parse(&x, (unsigned char)xml[i]);
|
||||||
|
ck_assert_int_ge(ret, YXML_OK);
|
||||||
|
if(x.isReference) {
|
||||||
|
if(ret == YXML_CONTENT)
|
||||||
|
contentReferences++;
|
||||||
|
else if(ret == YXML_ATTRVAL)
|
||||||
|
attributeReferences++;
|
||||||
|
else
|
||||||
|
ck_abort_msg("Reference flag set for event %d", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ck_assert_uint_eq(contentReferences, 1);
|
||||||
|
ck_assert_uint_eq(attributeReferences, 1);
|
||||||
|
} END_TEST
|
||||||
|
|
||||||
|
START_TEST(yxmlContentFragment) {
|
||||||
|
char buf[512];
|
||||||
|
char out[128];
|
||||||
|
size_t outlen;
|
||||||
|
size_t references;
|
||||||
|
const char *content =
|
||||||
|
"a&<?pi data?><!--ignored--><A€"
|
||||||
|
"<![CDATA[>]]>z";
|
||||||
|
yxml_ret_t ret = yxml_parse_content_full(
|
||||||
|
content, buf, sizeof(buf), out, sizeof(out) - 1,
|
||||||
|
&outlen, &references);
|
||||||
|
ck_assert_int_eq(ret, YXML_OK);
|
||||||
|
out[outlen] = 0;
|
||||||
|
ck_assert_str_eq(out, "a&<A\xE2\x82\xAC>z");
|
||||||
|
ck_assert_uint_eq(references, 4);
|
||||||
|
|
||||||
|
yxml_t empty;
|
||||||
|
yxml_init_content(&empty, buf, sizeof(buf));
|
||||||
|
ck_assert_int_eq(yxml_eof(&empty), YXML_OK);
|
||||||
|
} END_TEST
|
||||||
|
|
||||||
|
START_TEST(yxmlContentFragment_nested) {
|
||||||
|
char buf[512];
|
||||||
|
yxml_t x;
|
||||||
|
yxml_init_content(&x, buf, sizeof(buf));
|
||||||
|
const char *content = "before<child>inside</child>after";
|
||||||
|
for(size_t i = 0; i < strlen(content); i++)
|
||||||
|
ck_assert_int_ge(yxml_parse(&x, (unsigned char)content[i]), YXML_OK);
|
||||||
|
ck_assert_int_eq(yxml_eof(&x), YXML_OK);
|
||||||
|
} END_TEST
|
||||||
|
|
||||||
|
START_TEST(yxmlContentFragment_incomplete) {
|
||||||
|
const char *invalid[] = {
|
||||||
|
"&", "<![CDATA[text", "<!-- comment", "<?pi data", "<child>"
|
||||||
|
};
|
||||||
|
for(size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
|
||||||
|
char buf[512];
|
||||||
|
yxml_t x;
|
||||||
|
yxml_init_content(&x, buf, sizeof(buf));
|
||||||
|
for(size_t j = 0; j < strlen(invalid[i]); j++)
|
||||||
|
ck_assert_int_ge(yxml_parse(&x, (unsigned char)invalid[i][j]),
|
||||||
|
YXML_OK);
|
||||||
|
ck_assert_int_eq(yxml_eof(&x), YXML_EEOF);
|
||||||
|
}
|
||||||
|
} END_TEST
|
||||||
|
|
||||||
START_TEST(yxmlEntityRef_unknown) {
|
START_TEST(yxmlEntityRef_unknown) {
|
||||||
/* Unknown entity -> YXML_EREF */
|
/* Unknown entity -> YXML_EREF */
|
||||||
char buf[512];
|
char buf[512];
|
||||||
@@ -898,6 +992,10 @@ static Suite *testSuite_yxml(void) {
|
|||||||
|
|
||||||
TCase *tc_ref = tcase_create("yxml_refs");
|
TCase *tc_ref = tcase_create("yxml_refs");
|
||||||
tcase_add_test(tc_ref, yxmlEntityRef_content);
|
tcase_add_test(tc_ref, yxmlEntityRef_content);
|
||||||
|
tcase_add_test(tc_ref, yxmlEntityRef_flag);
|
||||||
|
tcase_add_test(tc_ref, yxmlContentFragment);
|
||||||
|
tcase_add_test(tc_ref, yxmlContentFragment_nested);
|
||||||
|
tcase_add_test(tc_ref, yxmlContentFragment_incomplete);
|
||||||
tcase_add_test(tc_ref, yxmlEntityRef_unknown);
|
tcase_add_test(tc_ref, yxmlEntityRef_unknown);
|
||||||
tcase_add_test(tc_ref, yxmlEntityRef_tooLong);
|
tcase_add_test(tc_ref, yxmlEntityRef_tooLong);
|
||||||
tcase_add_test(tc_ref, yxmlCharRef_decimalASCII);
|
tcase_add_test(tc_ref, yxmlCharRef_decimalASCII);
|
||||||
|
|||||||
Reference in New Issue
Block a user