diff --git a/CMakeLists.txt b/CMakeLists.txt index 47678b3..77ff54c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ PROJECT(xml) SET(VERSION_MAJOR "0") SET(VERSION_MINOR "1") -SET(VERSION_PATCH "2") +SET(VERSION_PATCH "3") CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR) diff --git a/src/xml.c b/src/xml.c index 59cca48..ee13669 100644 --- a/src/xml.c +++ b/src/xml.c @@ -22,6 +22,7 @@ */ #include #include +#include #include #include #include @@ -37,7 +38,7 @@ * UTF-8 text */ struct xml_string { - uint8_t* buffer; + uint8_t const* buffer; size_t length; }; @@ -59,7 +60,11 @@ struct xml_node { * An xml_document simply contains the root node and the underlying buffer */ struct xml_document { - struct xml_string buffer; + struct { + uint8_t* buffer; + size_t length; + } buffer; + struct xml_node* root; }; @@ -134,6 +139,20 @@ static _Bool xml_string_equals(struct xml_string* a, struct xml_string* b) { +/** + * [PRIVATE] + */ +static uint8_t* xml_string_clone(struct xml_string* s) { + uint8_t* clone = calloc(s->length + 1, sizeof(uint8_t)); + + xml_string_copy(s, clone, s->length); + clone[s->length] = 0; + + return clone; +} + + + /** * [PRIVATE] * @@ -715,6 +734,96 @@ struct xml_node* xml_node_child(struct xml_node* node, size_t child) { +/** + * [PUBLIC API] + */ +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name, ...) { + + /* Find childrens, one by one + */ + struct xml_node* current = node; + + va_list arguments; + va_start(arguments, child_name); + + + /* Descent to current.child + */ + while (child_name) { + + /* Convert child_name to xml_string for easy comparison + */ + struct xml_string cn = { + .buffer = child_name, + .length = strlen(child_name) + }; + + /* Interate through all children + */ + struct xml_node* next = 0; + + size_t i = 0; for (; i < xml_node_children(current); ++i) { + struct xml_node* child = xml_node_child(current, i); + + if (xml_string_equals(xml_node_name(child), &cn)) { + if (!next) { + next = child; + + /* Two children with the same name + */ + } else { + return 0; + } + } + } + + /* No child with that name found + */ + if (!next) { + return 0; + } + current = next; + + /* Find name of next child + */ + child_name = va_arg(arguments, uint8_t const*); + } + va_end(arguments); + + + /* Return current element + */ + return current; +} + + + +/** + * [PUBLIC API] + */ +uint8_t* xml_easy_name(struct xml_node* node) { + if (!node) { + return 0; + } + + return xml_string_clone(xml_node_name(node)); +} + + + +/** + * [PUBLIC API] + */ +uint8_t* xml_easy_content(struct xml_node* node) { + if (!node) { + return 0; + } + + return xml_string_clone(xml_node_content(node)); +} + + + /** * [PUBLIC API] */ diff --git a/src/xml.h b/src/xml.h index 448056e..39643ee 100644 --- a/src/xml.h +++ b/src/xml.h @@ -107,6 +107,31 @@ struct xml_node* xml_node_child(struct xml_node* node, size_t child); +/** + * @return The node described by the path or 0 if child cannot be found + * @warning Each element on the way must be unique + * @warning Last argument must be 0 + */ +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child, ...); + + + +/** + * @return 0-terminated copy of node name + * @warning User must free the result + */ +uint8_t* xml_easy_name(struct xml_node* node); + + + +/** + * @return 0-terminated copy of node content + * @warning User must free the result + */ +uint8_t* xml_easy_content(struct xml_node* node); + + + /** * @return Length of the string */ diff --git a/test/test-xml.c b/test/test-xml.c index de08c6d..6c0479a 100644 --- a/test/test-xml.c +++ b/test/test-xml.c @@ -141,6 +141,58 @@ static void test_xml_parse_document_1() { +/** + * Tests the eas functionality + */ +static void test_xml_parse_document_2() { + SOURCE(source, "" + "\n" + "\t\n" + "\t\tFirst content\n" + "\t\n" + "\t\n" + "Content A\n" + "Content B\n" + "\t\n" + "\t\n" + "\t\tSecond content\n" + "\t\n" + "\n" + ); + struct xml_document* document = xml_parse_document(source, strlen(source)); + assert_that(document, "Could not parse document"); + + struct xml_node* root = xml_document_root(document); + assert_that(string_equals(xml_node_name(root), "Parent"), "root node name must be `Parent'"); + assert_that(3 == xml_node_children(root), "root must have two children"); + + struct xml_node* test_a = xml_easy_child(root, "This", "Is", "A", "Test", 0); + assert_that(test_a, "Cannot find Parent/This/Is/A/Test"); + assert_that(string_equals(xml_node_content(test_a), "Content A"), "Content of Parent/This/Is/A/Test must be `Content A'"); + + struct xml_node* test_b = xml_easy_child(root, "This", "Is", "B", "Test", 0); + assert_that(test_b, "Cannot find Parent/This/Is/B/Test"); + assert_that(string_equals(xml_node_content(test_b), "Content B"), "Content of Parent/This/Is/B/Test must be `Content B'"); + + struct xml_node* test_c = xml_easy_child(root, "This", "Is", "C", "Test", 0); + assert_that(!test_c, "Must not find Parent/This/Is/C/Test because no such path exists"); + + struct xml_node* must_be_null = xml_easy_child(root, "Child"); + assert_that(!must_be_null, "Parent/Child cannot be a valid expression, because there are two children named `Child' in `Parent'"); + + uint8_t* name_is = xml_easy_name(xml_easy_child(root, "This", "Is", 0)); + assert_that(!strcmp(name_is, "Is"), "Name of Parent/This/Is must be `Is'"); + free(name_is); + + uint8_t* content_a = xml_easy_content(test_a); + assert_that(!strcmp(content_a, "Content A"), "Content of Parent/This/Is/A/Test must be `Content A'"); + free(content_a); + + xml_document_free(document, true); +} + + + /** @@ -149,6 +201,7 @@ static void test_xml_parse_document_1() { int main(int argc, char** argv) { test_xml_parse_document_0(); test_xml_parse_document_1(); + test_xml_parse_document_2(); fprintf(stdout, "All tests passed :-)\n"); exit(EXIT_SUCCESS);