|
|
#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(); |
|
|
} |