libxml / tests /tests_HTMLparser_htmlAutoCloseOnClose.c
AryaWu's picture
Upload folder using huggingface_hub
6baed57 verified
#include "unity/unity.h"
#include <libxml/HTMLparser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlstring.h>
#include <libxml/encoding.h>
#include <string.h>
#include <stdlib.h>
/* The function under test is exposed via a test wrapper */
void test_htmlAutoCloseOnClose(htmlParserCtxtPtr ctxt, const xmlChar * newtag);
/* Helper to collect endElement events */
typedef struct {
xmlChar **names;
int count;
int cap;
} EndEvents;
static void EndEvents_init(EndEvents *ev) {
ev->names = NULL;
ev->count = 0;
ev->cap = 0;
}
static void EndEvents_push(EndEvents *ev, const xmlChar *name) {
if (ev->count == ev->cap) {
int newCap = (ev->cap == 0) ? 8 : ev->cap * 2;
xmlChar **newArr = (xmlChar **)realloc(ev->names, (size_t)newCap * sizeof(xmlChar *));
TEST_ASSERT_NOT_NULL_MESSAGE(newArr, "Failed to allocate event array");
ev->names = newArr;
ev->cap = newCap;
}
ev->names[ev->count++] = xmlStrdup(name);
}
static void EndEvents_free(EndEvents *ev) {
if (ev->names) {
for (int i = 0; i < ev->count; i++) {
if (ev->names[i]) xmlFree(ev->names[i]);
}
free(ev->names);
}
ev->names = NULL;
ev->count = 0;
ev->cap = 0;
}
/* SAX handler that records endElement calls */
static void recordingEndElement(void *userData, const xmlChar *name) {
EndEvents *ev = (EndEvents *)userData;
if (ev && name) {
EndEvents_push(ev, name);
}
}
/* We use a static SAX handler to ensure its lifetime outlives the parser context */
static xmlSAXHandler gSAX;
/* Build a parser context, attach SAX handler to capture endElement events, and feed HTML chunk */
static htmlParserCtxtPtr build_ctx_with_stack(const char *htmlChunk, EndEvents *events) {
memset(&gSAX, 0, sizeof(gSAX));
gSAX.endElement = recordingEndElement; /* SAX1 endElement will be called by the target function */
htmlParserCtxtPtr ctxt = htmlCreatePushParserCtxt(&gSAX, events, NULL, 0, NULL, XML_CHAR_ENCODING_NONE);
TEST_ASSERT_NOT_NULL_MESSAGE(ctxt, "Failed to create HTML push parser context");
/* Feed only opening tags to build a stack of open elements */
int len = (int)strlen(htmlChunk);
int rc = htmlParseChunk(ctxt, htmlChunk, len, 0);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "htmlParseChunk returned error building initial stack");
return ctxt;
}
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
/* Test: closes intermediate elements until the matching tag is on top; endElement called for each */
void test_htmlAutoCloseOnClose_closes_intermediate_elements_until_match(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> (xem is on top) */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
TEST_ASSERT_NOT_NULL(ctxt->name);
int beforeNr = ctxt->nameNr;
/* Trigger autoclose with closing tag for xdiv */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv");
/* Should have closed xem then xspan (LIFO), leaving xdiv on top */
TEST_ASSERT_EQUAL_INT(2, events.count);
TEST_ASSERT_TRUE(xmlStrEqual(events.names[0], BAD_CAST "xem"));
TEST_ASSERT_TRUE(xmlStrEqual(events.names[1], BAD_CAST "xspan"));
TEST_ASSERT_NOT_NULL(ctxt->name);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, BAD_CAST "xdiv"));
TEST_ASSERT_EQUAL_INT(beforeNr - 2, ctxt->nameNr);
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: no action when target tag is not found in the open element stack */
void test_htmlAutoCloseOnClose_no_action_if_target_not_found(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
/* Target "xfoo" doesn't exist in the stack: function should do nothing */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xfoo");
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, beforeTop));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: no action when the target tag already matches the top of the stack */
void test_htmlAutoCloseOnClose_no_action_if_match_at_top(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan> (xspan is on top) */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
TEST_ASSERT_TRUE(xmlStrEqual(beforeTop, BAD_CAST "xspan"));
/* Target is already the top element; no intermediate pops expected */
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xspan");
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, BAD_CAST "xspan"));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
/* Test: in HTML5 mode (HTML_PARSE_HTML5), the function must return immediately and do nothing */
void test_htmlAutoCloseOnClose_early_return_in_html5_mode(void) {
EndEvents events;
EndEvents_init(&events);
/* Build a stack: ... <xdiv><xspan><xem> */
htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events);
int beforeNr = ctxt->nameNr;
const xmlChar *beforeTop = ctxt->name;
/* Enable HTML5 mode */
ctxt->options |= HTML_PARSE_HTML5;
test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv");
/* No changes expected */
TEST_ASSERT_EQUAL_INT(0, events.count);
TEST_ASSERT_EQUAL_INT(beforeNr, ctxt->nameNr);
TEST_ASSERT_TRUE(xmlStrEqual(ctxt->name, beforeTop));
htmlFreeParserCtxt(ctxt);
EndEvents_free(&events);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_htmlAutoCloseOnClose_closes_intermediate_elements_until_match);
RUN_TEST(test_htmlAutoCloseOnClose_no_action_if_target_not_found);
RUN_TEST(test_htmlAutoCloseOnClose_no_action_if_match_at_top);
RUN_TEST(test_htmlAutoCloseOnClose_early_return_in_html5_mode);
return UNITY_END();
}