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