#include "unity/unity.h" #include #include #include /* External wrapper for the static function under test */ extern void test_htmlCharDataSAXCallback(htmlParserCtxtPtr ctxt, const xmlChar *buf, int size, int mode); typedef struct { int chars_calls; int ign_calls; int cdata_calls; char chars_text[512]; char ign_text[512]; char cdata_text[512]; } Capture; static void reset_capture(Capture *cap) { if (!cap) return; memset(cap, 0, sizeof(*cap)); } static void cb_characters(void *userData, const xmlChar *ch, int len) { Capture *cap = (Capture *)userData; if (!cap || !ch || len <= 0) return; int copy = len; if (copy > (int)(sizeof(cap->chars_text) - 1 - (int)strlen(cap->chars_text))) copy = (int)(sizeof(cap->chars_text) - 1 - strlen(cap->chars_text)); strncat(cap->chars_text, (const char *)ch, copy); cap->chars_calls++; } static void cb_ignorableWhitespace(void *userData, const xmlChar *ch, int len) { Capture *cap = (Capture *)userData; if (!cap || !ch || len <= 0) return; int copy = len; if (copy > (int)(sizeof(cap->ign_text) - 1 - (int)strlen(cap->ign_text))) copy = (int)(sizeof(cap->ign_text) - 1 - strlen(cap->ign_text)); strncat(cap->ign_text, (const char *)ch, copy); cap->ign_calls++; } static void cb_cdataBlock(void *userData, const xmlChar *ch, int len) { Capture *cap = (Capture *)userData; if (!cap || !ch || len <= 0) return; int copy = len; if (copy > (int)(sizeof(cap->cdata_text) - 1 - (int)strlen(cap->cdata_text))) copy = (int)(sizeof(cap->cdata_text) - 1 - strlen(cap->cdata_text)); strncat(cap->cdata_text, (const char *)ch, copy); cap->cdata_calls++; } static void prepare_sax(xmlSAXHandler *sax, xmlCharactersSAXFunc chars, xmlIgnorableWhitespaceSAXFunc ign, xmlCDataBlockSAXFunc cdata) { memset(sax, 0, sizeof(*sax)); sax->characters = chars; sax->ignorableWhitespace = ign; sax->cdataBlock = cdata; } void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } /* Test: No SAX handler => no output */ void test_htmlCharDataSAXCallback_null_sax(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); Capture cap; reset_capture(&cap); ctxt->sax = NULL; /* Critical for this test */ ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 0; ctxt->name = (const xmlChar *)"div"; const char *data = "abc"; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), 0); TEST_ASSERT_EQUAL_INT(0, cap.chars_calls); TEST_ASSERT_EQUAL_INT(0, cap.ign_calls); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } /* Test: disableSAX => no output even with handlers */ void test_htmlCharDataSAXCallback_disabled_sax(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, cb_cdataBlock); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 1; ctxt->keepBlanks = 0; ctxt->name = (const xmlChar *)"div"; const char *data = "abc"; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), 0); TEST_ASSERT_EQUAL_INT(0, cap.chars_calls); TEST_ASSERT_EQUAL_INT(0, cap.ign_calls); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } /* Test: In , leading whitespace goes to ignorableWhitespace when keepBlanks == 0, rest to characters */ void test_htmlCharDataSAXCallback_html_leading_ws_keepBlanks_false(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, cb_cdataBlock); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 0; ctxt->name = (const xmlChar *)"html"; const char *data = " \t\nabc"; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), 0); TEST_ASSERT_EQUAL_INT(1, cap.ign_calls); TEST_ASSERT_EQUAL_STRING(" \t\n", cap.ign_text); TEST_ASSERT_EQUAL_INT(1, cap.chars_calls); TEST_ASSERT_EQUAL_STRING("abc", cap.chars_text); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } /* Test: In , leading whitespace is also delivered as characters when keepBlanks == 1, then remaining as characters */ void test_htmlCharDataSAXCallback_head_leading_ws_keepBlanks_true(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, cb_cdataBlock); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 1; ctxt->name = (const xmlChar *)"head"; const char *data = " abc"; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), 0); TEST_ASSERT_EQUAL_INT(2, cap.chars_calls); TEST_ASSERT_EQUAL_STRING(" abc", cap.chars_text); TEST_ASSERT_EQUAL_INT(0, cap.ign_calls); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } /* Test: Only whitespace in with keepBlanks == 0 results only in ignorableWhitespace, then early return */ void test_htmlCharDataSAXCallback_html_only_ws_keepBlanks_false(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, cb_cdataBlock); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 0; ctxt->name = (const xmlChar *)"html"; const char *data = " "; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), 0); TEST_ASSERT_EQUAL_INT(1, cap.ign_calls); TEST_ASSERT_EQUAL_STRING(" ", cap.ign_text); TEST_ASSERT_EQUAL_INT(0, cap.chars_calls); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } /* Test: Nonzero non-RCDATA mode with cdataBlock present uses cdataBlock only */ void test_htmlCharDataSAXCallback_cdata_mode_nonzero(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, cb_cdataBlock); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 0; ctxt->name = (const xmlChar *)"div"; const char *data = " CDATA "; /* Pick a mode value very unlikely to equal DATA_RCDATA */ int mode = 9999; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), mode); TEST_ASSERT_EQUAL_INT(0, cap.ign_calls); TEST_ASSERT_EQUAL_INT(0, cap.chars_calls); TEST_ASSERT_EQUAL_INT(1, cap.cdata_calls); TEST_ASSERT_EQUAL_STRING(" CDATA ", cap.cdata_text); htmlFreeParserCtxt(ctxt); } /* Test: cdataBlock is NULL with nonzero mode falls back to characters path */ void test_htmlCharDataSAXCallback_nonzero_mode_no_cdataBlock_falls_back(void) { htmlParserCtxtPtr ctxt = htmlNewParserCtxt(); TEST_ASSERT_NOT_NULL(ctxt); xmlSAXHandler sax; Capture cap; reset_capture(&cap); /* Set cdataBlock to NULL to force the non-CDATA branch */ prepare_sax(&sax, cb_characters, cb_ignorableWhitespace, NULL); ctxt->sax = &sax; ctxt->userData = ∩ ctxt->disableSAX = 0; ctxt->keepBlanks = 1; /* Avoid areBlanks checks */ ctxt->name = (const xmlChar *)"div"; const char *data = "abc"; int mode = 9999; test_htmlCharDataSAXCallback(ctxt, (const xmlChar *)data, (int)strlen(data), mode); TEST_ASSERT_EQUAL_INT(1, cap.chars_calls); TEST_ASSERT_EQUAL_STRING("abc", cap.chars_text); TEST_ASSERT_EQUAL_INT(0, cap.ign_calls); TEST_ASSERT_EQUAL_INT(0, cap.cdata_calls); htmlFreeParserCtxt(ctxt); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_htmlCharDataSAXCallback_null_sax); RUN_TEST(test_htmlCharDataSAXCallback_disabled_sax); RUN_TEST(test_htmlCharDataSAXCallback_html_leading_ws_keepBlanks_false); RUN_TEST(test_htmlCharDataSAXCallback_head_leading_ws_keepBlanks_true); RUN_TEST(test_htmlCharDataSAXCallback_html_only_ws_keepBlanks_false); RUN_TEST(test_htmlCharDataSAXCallback_cdata_mode_nonzero); RUN_TEST(test_htmlCharDataSAXCallback_nonzero_mode_no_cdataBlock_falls_back); return UNITY_END(); }