| | #include "unity/unity.h" |
| | #include <libxml/HTMLparser.h> |
| | #include <libxml/parser.h> |
| | #include <string.h> |
| | #include <stdlib.h> |
| | #include <ctype.h> |
| |
|
| | |
| | void test_htmlAutoCloseOnEnd(htmlParserCtxtPtr ctxt); |
| |
|
| | |
| | typedef struct { |
| | int call_count; |
| | char names[64][64]; |
| | } EndRec; |
| |
|
| | static xmlSAXHandler g_sax; |
| |
|
| | static void on_end_element(void *userData, const xmlChar *name) { |
| | EndRec *rec = (EndRec *)userData; |
| | if (rec == NULL || name == NULL) |
| | return; |
| | if (rec->call_count < 64) { |
| | strncpy(rec->names[rec->call_count], (const char *)name, sizeof(rec->names[0]) - 1); |
| | rec->names[rec->call_count][sizeof(rec->names[0]) - 1] = '\0'; |
| | rec->call_count++; |
| | } |
| | } |
| |
|
| | static int ci_equal(const char *a, const char *b) { |
| | if (a == NULL || b == NULL) return 0; |
| | while (*a && *b) { |
| | unsigned char ca = (unsigned char)*a; |
| | unsigned char cb = (unsigned char)*b; |
| | if (tolower(ca) != tolower(cb)) return 0; |
| | a++; b++; |
| | } |
| | return *a == *b; |
| | } |
| |
|
| | |
| | static htmlParserCtxtPtr create_push_ctxt(EndRec *rec) { |
| | return htmlCreatePushParserCtxt(&g_sax, rec, NULL, 0, NULL, XML_CHAR_ENCODING_NONE); |
| | } |
| |
|
| | |
| | static void feed_chunk(htmlParserCtxtPtr ctxt, const char *data) { |
| | htmlParseChunk(ctxt, data, (int)strlen(data), 0); |
| | } |
| |
|
| | void setUp(void) { |
| | |
| | memset(&g_sax, 0, sizeof(g_sax)); |
| | g_sax.endElement = on_end_element; |
| | xmlInitParser(); |
| | } |
| |
|
| | void tearDown(void) { |
| | xmlCleanupParser(); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnEnd_no_open_elements_noop(void) { |
| | EndRec rec = {0}; |
| | htmlParserCtxtPtr ctxt = create_push_ctxt(&rec); |
| | TEST_ASSERT_NOT_NULL(ctxt); |
| |
|
| | |
| | TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr); |
| |
|
| | test_htmlAutoCloseOnEnd(ctxt); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr); |
| | TEST_ASSERT_EQUAL_INT(0, rec.call_count); |
| |
|
| | htmlFreeParserCtxt(ctxt); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnEnd_closes_stack_and_calls_endElement_reverse_order(void) { |
| | EndRec rec = {0}; |
| | htmlParserCtxtPtr ctxt = create_push_ctxt(&rec); |
| | TEST_ASSERT_NOT_NULL(ctxt); |
| |
|
| | |
| | feed_chunk(ctxt, "<div><span>"); |
| |
|
| | |
| | TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected at least one open element on the stack"); |
| |
|
| | |
| | char top_before[64] = {0}; |
| | if (ctxt->name != NULL) { |
| | strncpy(top_before, (const char *)ctxt->name, sizeof(top_before) - 1); |
| | } |
| |
|
| | |
| | rec.call_count = 0; |
| |
|
| | int initial_nameNr = ctxt->nameNr; |
| | test_htmlAutoCloseOnEnd(ctxt); |
| |
|
| | |
| | TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr); |
| |
|
| | |
| | TEST_ASSERT_EQUAL_INT(initial_nameNr, rec.call_count); |
| |
|
| | |
| | if (top_before[0] != '\0') { |
| | TEST_ASSERT_TRUE_MESSAGE(ci_equal(rec.names[0], top_before), |
| | "First endElement should be for the previous top-of-stack element"); |
| | } |
| |
|
| | htmlFreeParserCtxt(ctxt); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnEnd_returns_early_with_HTML5_option(void) { |
| | EndRec rec = {0}; |
| | htmlParserCtxtPtr ctxt = create_push_ctxt(&rec); |
| | TEST_ASSERT_NOT_NULL(ctxt); |
| |
|
| | feed_chunk(ctxt, "<div><span>"); |
| |
|
| | TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected open elements before testing HTML5 early return"); |
| |
|
| | int before_nameNr = ctxt->nameNr; |
| | ctxt->options |= HTML_PARSE_HTML5; |
| |
|
| | test_htmlAutoCloseOnEnd(ctxt); |
| |
|
| | |
| | TEST_ASSERT_EQUAL_INT(before_nameNr, ctxt->nameNr); |
| | TEST_ASSERT_EQUAL_INT(0, rec.call_count); |
| |
|
| | htmlFreeParserCtxt(ctxt); |
| | } |
| |
|
| | |
| | void test_htmlAutoCloseOnEnd_handles_null_sax_handler(void) { |
| | EndRec rec = {0}; |
| | htmlParserCtxtPtr ctxt = create_push_ctxt(&rec); |
| | TEST_ASSERT_NOT_NULL(ctxt); |
| |
|
| | feed_chunk(ctxt, "<div>"); |
| |
|
| | TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr > 0, "Expected open elements before null SAX test"); |
| |
|
| | int initial_nameNr = ctxt->nameNr; |
| |
|
| | |
| | ctxt->sax = NULL; |
| |
|
| | test_htmlAutoCloseOnEnd(ctxt); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, ctxt->nameNr); |
| | TEST_ASSERT_EQUAL_INT(0, rec.call_count); |
| |
|
| | (void)initial_nameNr; |
| |
|
| | htmlFreeParserCtxt(ctxt); |
| | } |
| |
|
| | int main(void) { |
| | UNITY_BEGIN(); |
| | RUN_TEST(test_htmlAutoCloseOnEnd_no_open_elements_noop); |
| | RUN_TEST(test_htmlAutoCloseOnEnd_closes_stack_and_calls_endElement_reverse_order); |
| | RUN_TEST(test_htmlAutoCloseOnEnd_returns_early_with_HTML5_option); |
| | RUN_TEST(test_htmlAutoCloseOnEnd_handles_null_sax_handler); |
| | return UNITY_END(); |
| | } |