| | #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> |
| |
|
| | |
| | void test_htmlAutoCloseOnClose(htmlParserCtxtPtr ctxt, const xmlChar * newtag); |
| |
|
| | |
| | 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; |
| | } |
| |
|
| | |
| | static void recordingEndElement(void *userData, const xmlChar *name) { |
| | EndEvents *ev = (EndEvents *)userData; |
| | if (ev && name) { |
| | EndEvents_push(ev, name); |
| | } |
| | } |
| |
|
| | |
| | static xmlSAXHandler gSAX; |
| |
|
| | |
| | static htmlParserCtxtPtr build_ctx_with_stack(const char *htmlChunk, EndEvents *events) { |
| | memset(&gSAX, 0, sizeof(gSAX)); |
| | gSAX.endElement = recordingEndElement; |
| |
|
| | 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"); |
| |
|
| | |
| | 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) { |
| | |
| | } |
| |
|
| | void tearDown(void) { |
| | |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnClose_closes_intermediate_elements_until_match(void) { |
| | EndEvents events; |
| | EndEvents_init(&events); |
| |
|
| | |
| | htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events); |
| |
|
| | TEST_ASSERT_NOT_NULL(ctxt->name); |
| | int beforeNr = ctxt->nameNr; |
| |
|
| | |
| | test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv"); |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnClose_no_action_if_target_not_found(void) { |
| | EndEvents events; |
| | EndEvents_init(&events); |
| |
|
| | |
| | htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events); |
| |
|
| | int beforeNr = ctxt->nameNr; |
| | const xmlChar *beforeTop = ctxt->name; |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnClose_no_action_if_match_at_top(void) { |
| | EndEvents events; |
| | EndEvents_init(&events); |
| |
|
| | |
| | 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")); |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnClose_early_return_in_html5_mode(void) { |
| | EndEvents events; |
| | EndEvents_init(&events); |
| |
|
| | |
| | htmlParserCtxtPtr ctxt = build_ctx_with_stack("<xdiv><xspan><xem>", &events); |
| |
|
| | int beforeNr = ctxt->nameNr; |
| | const xmlChar *beforeTop = ctxt->name; |
| |
|
| | |
| | ctxt->options |= HTML_PARSE_HTML5; |
| |
|
| | test_htmlAutoCloseOnClose(ctxt, BAD_CAST "xdiv"); |
| |
|
| | |
| | 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(); |
| | } |