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