feat(yxml): support content fragment parsing

This commit is contained in:
Julius Pfrommer
2026-09-02 22:14:11 +02:00
committed by Julius Pfrommer
parent 437995bc22
commit e32ab7d181
3 changed files with 127 additions and 8 deletions
Vendored
+12 -3
View File
@@ -222,7 +222,7 @@ static yxml_ret_t yxml_selfclose(yxml_t *x, unsigned ch) {
return YXML_ELEMEND;
}
x->elem = (char *)x->stack;
x->state = YXMLS_misc3;
x->state = x->contentmode ? YXMLS_misc2 : YXMLS_misc3;
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)
return YXML_EREF;
yxml_setutf8(x->data, ch);
x->isReference = true;
return ret;
}
@@ -308,7 +309,15 @@ void yxml_init(yxml_t *x, void *stack, size_t stacksize) {
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) {
x->isReference = false;
/* Ensure that characters are in the range of 0..255 rather than -126..125.
* All character comparisons are done with positive integers. */
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) {
if(x->state != YXMLS_misc3)
if(x->state != YXMLS_misc3 &&
!(x->contentmode && x->state == YXMLS_misc2 && x->stacklen == 0))
return YXML_EEOF;
return YXML_OK;
}
Vendored
+17 -5
View File
@@ -23,6 +23,7 @@
#ifndef YXML_H
#define YXML_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
@@ -94,6 +95,10 @@ typedef struct {
*/
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
* and including the next YXML_ATTREND. */
char *attr;
@@ -119,6 +124,7 @@ typedef struct {
int nextstate; /* Used for '@' state remembering and for the "string" consuming state */
unsigned ignore;
unsigned char *string;
bool contentmode;
} yxml_t;
#ifdef __cplusplus
@@ -127,14 +133,20 @@ extern "C" {
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);
/* 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
* this function isn't really necessary, but can be used to detect documents
* that don't end correctly. In particular, an error is returned when the XML
* document did not contain a (complete) root element, or when the document
* ended while in a comment or processing instruction. */
* Returns YXML_OK if the XML document or content fragment is valid, YXML_EEOF
* otherwise. Using this function isn't really necessary, but can be used to
* detect input that doesn't end correctly. In document mode, an error is
* returned when there is no complete root element. In content mode, an error
* 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 *);
#ifdef __cplusplus
+98
View File
@@ -37,6 +37,31 @@ yxml_parse_full(const char *xml, size_t len, char *buf, size_t buflen,
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 === */
START_TEST(parseElement) {
@@ -344,6 +369,75 @@ START_TEST(yxmlEntityRef_content) {
ck_assert_int_gt(cnt, 0);
} END_TEST
START_TEST(yxmlEntityRef_flag) {
char buf[512];
const char *xml = "<r a=\"plain;&amp;plain\">plain;&#65;plain</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&amp;<?pi data?><!--ignored-->&lt;&#65;&#x20AC;"
"<![CDATA[&gt;]]>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&gt;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[] = {
"&amp", "<![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) {
/* Unknown entity -> YXML_EREF */
char buf[512];
@@ -898,6 +992,10 @@ static Suite *testSuite_yxml(void) {
TCase *tc_ref = tcase_create("yxml_refs");
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_tooLong);
tcase_add_test(tc_ref, yxmlCharRef_decimalASCII);