#include "unity/unity.h" #include #include #include #include #include /* Wrapper provided in module to access the static function */ void test_htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlAttrPtr cur); /* Accumulator for xmlOutputBuffer IO */ typedef struct { char *data; size_t len; size_t cap; } Accum; static void Accum_init(Accum *a) { a->data = NULL; a->len = 0; a->cap = 0; } static void Accum_free(Accum *a) { free(a->data); a->data = NULL; a->len = 0; a->cap = 0; } static int acc_write(void *context, const char *buffer, int len) { Accum *a = (Accum *)context; if (len <= 0) return 0; size_t need = a->len + (size_t)len + 1; /* +1 for NUL */ if (need > a->cap) { size_t newcap = a->cap ? a->cap : 64; while (newcap < need) newcap *= 2; char *nd = (char *)realloc(a->data, newcap); if (nd == NULL) return -1; a->data = nd; a->cap = newcap; } memcpy(a->data + a->len, buffer, (size_t)len); a->len += (size_t)len; a->data[a->len] = '\0'; return len; } static int acc_close(void *context) { (void)context; return 0; } static xmlOutputBufferPtr makeOutputBuffer(Accum *a) { Accum_init(a); xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(acc_write, acc_close, a, NULL); return buf; } void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } /* Helper to create a simple document and root node */ static void make_doc_and_root(const char *rootName, xmlDocPtr *pdoc, xmlNodePtr *proot) { xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); TEST_ASSERT_NOT_NULL_MESSAGE(doc, "xmlNewDoc failed"); xmlNodePtr root = xmlNewNode(NULL, BAD_CAST rootName); TEST_ASSERT_NOT_NULL_MESSAGE(root, "xmlNewNode failed"); xmlDocSetRootElement(doc, root); *pdoc = doc; *proot = root; } /* 1) Non-URI attribute with characters requiring HTML escaping */ void test_htmlAttrDumpOutput_basic_escape_text(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("div", &doc, &node); xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "title", BAD_CAST "Tom & \"Jerry\""); TEST_ASSERT_NOT_NULL(attr); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); /* Don't use buf after close */ xmlOutputBufferClose(buf); const char *expected = " title=\"Tom & "Jerry"\""; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 2) Boolean attribute (lowercase) should serialize without ="..." */ void test_htmlAttrDumpOutput_boolean_selected_lowercase(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("option", &doc, &node); xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "selected", BAD_CAST "selected"); TEST_ASSERT_NOT_NULL(attr); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " selected"; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 3) Boolean attribute (uppercase name) should also serialize without ="..." */ void test_htmlAttrDumpOutput_boolean_selected_uppercase(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("option", &doc, &node); xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "SELECTED", BAD_CAST "true"); TEST_ASSERT_NOT_NULL(attr); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " SELECTED"; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 4) Attribute with NULL value (no children) should serialize without ="..." */ void test_htmlAttrDumpOutput_null_value_no_children(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("div", &doc, &node); xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "data", NULL); TEST_ASSERT_NOT_NULL(attr); TEST_ASSERT_NULL(attr->children); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " data"; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 5) Namespaced attribute: prefix should be printed and value escaped */ void test_htmlAttrDumpOutput_namespaced_attr_with_escape(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("div", &doc, &node); xmlNsPtr ns = xmlNewNs(node, BAD_CAST "http://example.com/ns", BAD_CAST "p"); TEST_ASSERT_NOT_NULL(ns); xmlAttrPtr attr = xmlNewNsProp(node, ns, BAD_CAST "id", BAD_CAST "alpha&beta"); TEST_ASSERT_NOT_NULL(attr); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " p:id=\"alpha&beta\""; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 6) URI attribute (href): leading spaces preserved, spaces encoded as %20; '&' and '\"' escaped */ void test_htmlAttrDumpOutput_uri_href_serialization(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("a", &doc, &node); xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "href", BAD_CAST " Ab c&\"Z"); TEST_ASSERT_NOT_NULL(attr); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " href=\" Ab%20c&"Z\""; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } /* 7) Attribute value containing an entity reference node should serialize as &name; */ void test_htmlAttrDumpOutput_entity_ref_in_value(void) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; make_doc_and_root("div", &doc, &node); /* Start with an attribute without children, then build a mixed content value */ xmlAttrPtr attr = xmlNewProp(node, BAD_CAST "data", NULL); TEST_ASSERT_NOT_NULL(attr); xmlNodePtr t1 = xmlNewText(BAD_CAST "X"); xmlNodePtr ref = xmlNewReference(doc, BAD_CAST "copy"); /* © */ xmlNodePtr t2 = xmlNewText(BAD_CAST "Y"); TEST_ASSERT_NOT_NULL(t1); TEST_ASSERT_NOT_NULL(ref); TEST_ASSERT_NOT_NULL(t2); /* Attach children to the attribute */ xmlAddChild((xmlNodePtr)attr, t1); xmlAddChild((xmlNodePtr)attr, ref); xmlAddChild((xmlNodePtr)attr, t2); Accum acc; xmlOutputBufferPtr buf = makeOutputBuffer(&acc); TEST_ASSERT_NOT_NULL(buf); test_htmlAttrDumpOutput(buf, attr); xmlOutputBufferFlush(buf); xmlOutputBufferClose(buf); const char *expected = " data=\"X©Y\""; TEST_ASSERT_NOT_NULL(acc.data); TEST_ASSERT_EQUAL_STRING(expected, acc.data); Accum_free(&acc); xmlFreeDoc(doc); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_htmlAttrDumpOutput_basic_escape_text); RUN_TEST(test_htmlAttrDumpOutput_boolean_selected_lowercase); RUN_TEST(test_htmlAttrDumpOutput_boolean_selected_uppercase); RUN_TEST(test_htmlAttrDumpOutput_null_value_no_children); RUN_TEST(test_htmlAttrDumpOutput_namespaced_attr_with_escape); RUN_TEST(test_htmlAttrDumpOutput_uri_href_serialization); RUN_TEST(test_htmlAttrDumpOutput_entity_ref_in_value); return UNITY_END(); }