| #include "unity/unity.h" |
| #include <libxml/HTMLparser.h> |
|
|
| #include <string.h> |
| #include <stdlib.h> |
|
|
| |
| void test_htmlStartCharData(htmlParserCtxtPtr ctxt); |
|
|
| |
| typedef struct { |
| int start_count; |
| int end_count; |
| xmlChar* start_names[16]; |
| xmlChar* end_names[16]; |
| } TestSAXRec; |
|
|
| static void recordStartElement(void *ctx, const xmlChar *name, const xmlChar **atts) { |
| (void)atts; |
| TestSAXRec *rec = (TestSAXRec *)ctx; |
| if (rec == NULL) return; |
| if (rec->start_count < (int)(sizeof(rec->start_names)/sizeof(rec->start_names[0]))) { |
| rec->start_names[rec->start_count] = xmlStrdup(name); |
| } |
| rec->start_count++; |
| } |
|
|
| static void recordEndElement(void *ctx, const xmlChar *name) { |
| TestSAXRec *rec = (TestSAXRec *)ctx; |
| if (rec == NULL) return; |
| if (rec->end_count < (int)(sizeof(rec->end_names)/sizeof(rec->end_names[0]))) { |
| rec->end_names[rec->end_count] = xmlStrdup(name); |
| } |
| rec->end_count++; |
| } |
|
|
| static void freeRec(TestSAXRec *rec) { |
| if (!rec) return; |
| for (int i = 0; i < rec->start_count; i++) { |
| if (rec->start_names[i]) xmlFree(rec->start_names[i]); |
| rec->start_names[i] = NULL; |
| } |
| for (int i = 0; i < rec->end_count; i++) { |
| if (rec->end_names[i]) xmlFree(rec->end_names[i]); |
| rec->end_names[i] = NULL; |
| } |
| rec->start_count = 0; |
| rec->end_count = 0; |
| } |
|
|
| static void initSAX(xmlSAXHandler *sax) { |
| memset(sax, 0, sizeof(*sax)); |
| sax->startElement = recordStartElement; |
| sax->endElement = recordEndElement; |
| } |
|
|
| void setUp(void) { |
| xmlInitParser(); |
| } |
|
|
| void tearDown(void) { |
| xmlCleanupParser(); |
| } |
|
|
| |
| static htmlParserCtxtPtr create_ctxt_with_rec(TestSAXRec *rec) { |
| htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); |
| TEST_ASSERT_NOT_NULL_MESSAGE(ctxt, "htmlNewParserCtxt failed"); |
| static xmlSAXHandler sax; |
| initSAX(&sax); |
| ctxt->sax = &sax; |
| ctxt->userData = rec; |
| |
| ctxt->name = NULL; |
| ctxt->nameNr = 0; |
| |
| ctxt->options = 0; |
| return ctxt; |
| } |
|
|
| |
| void test_htmlStartCharData_returns_early_with_HTML5_option(void) { |
| TestSAXRec rec; |
| memset(&rec, 0, sizeof(rec)); |
| htmlParserCtxtPtr ctxt = create_ctxt_with_rec(&rec); |
| ctxt->options |= HTML_PARSE_HTML5; |
|
|
| test_htmlStartCharData(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rec.start_count); |
| TEST_ASSERT_EQUAL_INT(0, rec.end_count); |
|
|
| freeRec(&rec); |
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlStartCharData_returns_early_with_NOIMPLIED_option(void) { |
| TestSAXRec rec; |
| memset(&rec, 0, sizeof(rec)); |
| htmlParserCtxtPtr ctxt = create_ctxt_with_rec(&rec); |
| ctxt->options |= HTML_PARSE_NOIMPLIED; |
|
|
| test_htmlStartCharData(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rec.start_count); |
| TEST_ASSERT_EQUAL_INT(0, rec.end_count); |
|
|
| freeRec(&rec); |
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlStartCharData_implies_html_and_body_when_stack_empty(void) { |
| TestSAXRec rec; |
| memset(&rec, 0, sizeof(rec)); |
| htmlParserCtxtPtr ctxt = create_ctxt_with_rec(&rec); |
| |
| ctxt->options = 0; |
| |
| ctxt->name = NULL; |
| ctxt->nameNr = 0; |
|
|
| test_htmlStartCharData(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(2, rec.start_count); |
| TEST_ASSERT_EQUAL_INT(0, rec.end_count); |
| TEST_ASSERT_NOT_NULL(rec.start_names[0]); |
| TEST_ASSERT_NOT_NULL(rec.start_names[1]); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "html", rec.start_names[0])); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "body", rec.start_names[1])); |
| TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr >= 2, "Expected at least 2 elements on the stack"); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "body", ctxt->name)); |
|
|
| freeRec(&rec); |
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlStartCharData_implies_body_when_only_html_present(void) { |
| TestSAXRec rec; |
| memset(&rec, 0, sizeof(rec)); |
| htmlParserCtxtPtr ctxt = create_ctxt_with_rec(&rec); |
| ctxt->options = 0; |
|
|
| |
| |
| if (ctxt->nameTab == NULL || ctxt->nameMax < 2) { |
| |
| ctxt->nameMax = 8; |
| ctxt->nameTab = (const xmlChar **) xmlMalloc(ctxt->nameMax * sizeof(const xmlChar *)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(ctxt->nameTab, "Failed to allocate nameTab"); |
| } |
| ctxt->nameNr = 1; |
| ctxt->nameTab[0] = BAD_CAST "html"; |
| ctxt->name = ctxt->nameTab[0]; |
|
|
| test_htmlStartCharData(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(1, rec.start_count); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "body", rec.start_names[0])); |
| TEST_ASSERT_TRUE_MESSAGE(ctxt->nameNr >= 2, "Expected body to be pushed on the stack"); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "body", ctxt->name)); |
|
|
| freeRec(&rec); |
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| |
| void test_htmlStartCharData_no_duplication_when_body_present(void) { |
| TestSAXRec rec; |
| memset(&rec, 0, sizeof(rec)); |
| htmlParserCtxtPtr ctxt = create_ctxt_with_rec(&rec); |
| ctxt->options = 0; |
|
|
| |
| if (ctxt->nameTab == NULL || ctxt->nameMax < 2) { |
| ctxt->nameMax = 8; |
| ctxt->nameTab = (const xmlChar **) xmlMalloc(ctxt->nameMax * sizeof(const xmlChar *)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(ctxt->nameTab, "Failed to allocate nameTab"); |
| } |
| ctxt->nameNr = 2; |
| ctxt->nameTab[0] = BAD_CAST "html"; |
| ctxt->nameTab[1] = BAD_CAST "body"; |
| ctxt->name = ctxt->nameTab[1]; |
|
|
| test_htmlStartCharData(ctxt); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rec.start_count); |
| TEST_ASSERT_EQUAL_INT(0, rec.end_count); |
| TEST_ASSERT_EQUAL_INT(2, ctxt->nameNr); |
| TEST_ASSERT_TRUE(xmlStrEqual(BAD_CAST "body", ctxt->name)); |
|
|
| freeRec(&rec); |
| htmlFreeParserCtxt(ctxt); |
| } |
|
|
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_htmlStartCharData_returns_early_with_HTML5_option); |
| RUN_TEST(test_htmlStartCharData_returns_early_with_NOIMPLIED_option); |
| RUN_TEST(test_htmlStartCharData_implies_html_and_body_when_stack_empty); |
| RUN_TEST(test_htmlStartCharData_implies_body_when_only_html_present); |
| RUN_TEST(test_htmlStartCharData_no_duplication_when_body_present); |
| return UNITY_END(); |
| } |