diff options
Diffstat (limited to 'libs/xml.c')
| -rw-r--r-- | libs/xml.c/.github/workflows/ci.yaml | 24 | ||||
| -rw-r--r-- | libs/xml.c/.gitignore | 2 | ||||
| -rw-r--r-- | libs/xml.c/.mc/rebuild.sh | 21 | ||||
| -rw-r--r-- | libs/xml.c/.mc/ubuntu-22.04.yaml | 8 | ||||
| -rw-r--r-- | libs/xml.c/CMakeLists.txt | 51 | ||||
| -rw-r--r-- | libs/xml.c/LICENSE | 21 | ||||
| -rw-r--r-- | libs/xml.c/README.md | 126 | ||||
| -rw-r--r-- | libs/xml.c/src/xml.c | 1131 | ||||
| -rw-r--r-- | libs/xml.c/src/xml.h | 196 | ||||
| -rw-r--r-- | libs/xml.c/test/CMakeLists.txt | 128 | ||||
| -rw-r--r-- | libs/xml.c/test/example.c | 96 | ||||
| -rw-r--r-- | libs/xml.c/test/test-attributes.xml | 1 | ||||
| -rw-r--r-- | libs/xml.c/test/test-huitre39.c | 181 | ||||
| -rw-r--r-- | libs/xml.c/test/test-xml-c.c | 265 | ||||
| -rw-r--r-- | libs/xml.c/test/test-xml-cpp.cpp | 222 | ||||
| -rw-r--r-- | libs/xml.c/test/test.xml | 7 |
16 files changed, 2480 insertions, 0 deletions
diff --git a/libs/xml.c/.github/workflows/ci.yaml b/libs/xml.c/.github/workflows/ci.yaml new file mode 100644 index 0000000..2f9a4a9 --- /dev/null +++ b/libs/xml.c/.github/workflows/ci.yaml @@ -0,0 +1,24 @@ +name: CI + +# Controls when the action will run. Triggers the workflow on push or pull +# request events but only for the main branch +on: + push: + branches: + - master + pull_request: + branches: [ master ] + schedule: + - cron: '0 0 15 * *' + + +jobs: + ci: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Build and Test + run: npx --package mini-cross@0.15.2 mc --no-tty ubuntu-22.04 .mc/rebuild.sh + diff --git a/libs/xml.c/.gitignore b/libs/xml.c/.gitignore new file mode 100644 index 0000000..bdc5af0 --- /dev/null +++ b/libs/xml.c/.gitignore @@ -0,0 +1,2 @@ +*~ +build diff --git a/libs/xml.c/.mc/rebuild.sh b/libs/xml.c/.mc/rebuild.sh new file mode 100644 index 0000000..67443c3 --- /dev/null +++ b/libs/xml.c/.mc/rebuild.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + + +# @see https://stackoverflow.com/a/1482133 +DIRECTORY_OF_SCRIPT=`dirname "$(readlink -f "$0")"` + +SOURCE_DIRECTORY="${DIRECTORY_OF_SCRIPT}/.." +BUILD_DIRECTORY="${DIRECTORY_OF_SCRIPT}/../build" + + +if [ -d "${BUILD_DIRECTORY}" ]; then + rm -rf "${BUILD_DIRECTORY}" +fi +mkdir "${BUILD_DIRECTORY}" + +(cd "${BUILD_DIRECTORY}" && cmake -DCMAKE_BUILD_TYPE=Release ..) +make --directory "${BUILD_DIRECTORY}" --silent +make --directory "${BUILD_DIRECTORY}" --silent test + diff --git a/libs/xml.c/.mc/ubuntu-22.04.yaml b/libs/xml.c/.mc/ubuntu-22.04.yaml new file mode 100644 index 0000000..762305c --- /dev/null +++ b/libs/xml.c/.mc/ubuntu-22.04.yaml @@ -0,0 +1,8 @@ +--- +base: ubuntu:22.04 +install: + - cmake + - g++ + - gcc + - valgrind +--- diff --git a/libs/xml.c/CMakeLists.txt b/libs/xml.c/CMakeLists.txt new file mode 100644 index 0000000..698dc78 --- /dev/null +++ b/libs/xml.c/CMakeLists.txt @@ -0,0 +1,51 @@ +# Project setup +project(xml C CXX) +set(VERSION_MAJOR "0") +set(VERSION_MINOR "2") +set(VERSION_PATCH "0") +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + + +# Define main library target +add_library(xml STATIC "") + + +# Compiler setup +target_compile_options( + xml + PRIVATE + -std=c11 +) + + +# Options +option(XML_PARSER_VERBOSE "Enable to be told everything the xml parser does" OFF) + +if(XML_PARSER_VERBOSE) + target_compile_definitions( + xml + PRIVATE + XML_PARSER_VERBOSE + ) +endif(XML_PARSER_VERBOSE) + + +# Sources +target_sources( + xml + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/src/xml.c" +) + + +target_include_directories( + xml + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/src/" +) + + +# Build unit cases +enable_testing() +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/test") + diff --git a/libs/xml.c/LICENSE b/libs/xml.c/LICENSE new file mode 100644 index 0000000..f6c1ad0 --- /dev/null +++ b/libs/xml.c/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2012 ooxi/xml.c + https://github.com/ooxi/xml.c + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is + not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + diff --git a/libs/xml.c/README.md b/libs/xml.c/README.md new file mode 100644 index 0000000..b255bd2 --- /dev/null +++ b/libs/xml.c/README.md @@ -0,0 +1,126 @@ +xml.c +===== + +Similar to the [GLib Markup parser](http://developer.gnome.org/glib/2.34/glib-Simple-XML-Subset-Parser.html), +which also just parses an xml subset, [xml.c](https://github.com/ooxi/xml.c) is +a simple, small and self contained xml parser in one file. Ideal for embedding +into other projects without the need for big external dependencies. + +[](https://github.com/ooxi/xml.c/actions) + + +Downloads +--------- + +All releases are based on master, so the preferred way of using xml.c is adding +the repository as [git submodule](http://git-scm.com/book/en/Git-Tools-Submodules). + +If you prefer formal releases, check out the [release tags](https://github.com/ooxi/xml.c/tags). + + +Building xml.c +-------------- + +Since xml.c uses [CMake](http://www.cmake.org/), building the library is fairly +easy + + $ git clone https://github.com/ooxi/xml.c.git xml.c + $ mkdir xml.c/build; cd xml.c/build + $ cmake -DCMAKE_BUILD_TYPE=Release .. + $ make && make test + +If you need a debug build, specify `CMAKE_BUILD_TYPE` as `Debug` and rebuild. + + +Usage +----- + +This example is also [included in the repository](https://github.com/ooxi/xml.c/blob/master/test/example.c) +and will be build by default. Most of the code is C boilerplate, the important +functions are `xml_parse_document`, `xml_document_root`, `xml_node_name`, +`xml_node_content` and `xml_node_child` / `xml_node_children`. + +```c +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <xml.h> + + + +int main(int argc, char** argv) { + + /* XML source, could be read from disk + */ + uint8_t* source = "" + "<Root>" + "<Hello>World</Hello>" + "<This>" + "<Is>:-)</Is>" + "<An>:-O</An>" + "<Example>:-D</Example>" + "</This>" + "</Root>" + ; + + + /* Parse the document + * + * Watch out: Remember not to free the source until you have freed the + * document itself. If you have to free the source before, supply a + * copy to xml_parse_document which can be freed together with the + * document (`free_buffer' argument to `xml_document_free') + */ + struct xml_document* document = xml_parse_document(source, strlen(source)); + + /* You _have_ to check the result of `xml_parse_document', if it's 0 + * then the source could not be parsed. If you think this is a bug in + * xml.c, then use a debug build (cmake -DCMAKE_BUILD_TYPE=Debug) which + * will verbosely tell you about the parsing process + */ + if (!document) { + printf("Could parse document\n"); + exit(EXIT_FAILURE); + } + struct xml_node* root = xml_document_root(document); + + + /* Say Hello World :-) + */ + struct xml_node* root_hello = xml_node_child(root, 0); + struct xml_string* hello = xml_node_name(root_hello); + struct xml_string* world = xml_node_content(root_hello); + + /* Watch out: `xml_string_copy' will not 0-terminate your buffers! (but + * `calloc' will :-) + */ + uint8_t* hello_0 = calloc(xml_string_length(hello) + 1, sizeof(uint8_t)); + uint8_t* world_0 = calloc(xml_string_length(world) + 1, sizeof(uint8_t)); + xml_string_copy(hello, hello_0, xml_string_length(hello)); + xml_string_copy(world, world_0, xml_string_length(world)); + + printf("%s %s\n", hello_0, world_0); + free(hello_0); + free(world_0); + + + /* Extract amount of Root/This children + */ + struct xml_node* root_this = xml_node_child(root, 1); + printf("Root/This has %lu children\n", (unsigned long)xml_node_children(root_this)); + + + /* Remember to free the document or you'll risk a memory leak + */ + xml_document_free(document, false); +} +``` + +Another usage example can be found in the [unit case](https://github.com/ooxi/xml.c/blob/master/test/test-xml.c). + + +License +------- + +[libpng/zlib](https://github.com/ooxi/xml.c/blob/master/LICENSE) (BSD) + diff --git a/libs/xml.c/src/xml.c b/libs/xml.c/src/xml.c new file mode 100644 index 0000000..4c4e6f2 --- /dev/null +++ b/libs/xml.c/src/xml.c @@ -0,0 +1,1131 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ +#define _CRT_SECURE_NO_WARNINGS +#include "xml.h" + +#ifdef XML_PARSER_VERBOSE +#include <alloca.h> +#endif + +#include <ctype.h> + +#ifndef __MACH__ +#include <malloc.h> +#endif + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + + + + + +/* + * public domain strtok_r() by Charlie Gordon + * + * from comp.lang.c 9/14/2007 + * + * http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684 + * + * (Declaration that it's public domain): + * http://groups.google.com/group/comp.lang.c/msg/7c7b39328fefab9c + */ +static char* xml_strtok_r(char *str, const char *delim, char **nextp) { + char *ret; + + if (str == NULL) { + str = *nextp; + } + + str += strspn(str, delim); + + if (*str == '\0') { + return NULL; + } + + ret = str; + + str += strcspn(str, delim); + + if (*str) { + *str++ = '\0'; + } + + *nextp = str; + + return ret; +} + + + + + + +/** + * [OPAQUE API] + * + * UTF-8 text + */ +struct xml_string { + uint8_t const* buffer; + size_t length; +}; + +/** + * [OPAQUE API] + * + * An xml_attribute may contain text content. + */ +struct xml_attribute { + struct xml_string* name; + struct xml_string* content; +}; + +/** + * [OPAQUE API] + * + * An xml_node will always contain a tag name, a 0-terminated list of attributes + * and a 0-terminated list of children. Moreover it may contain text content. + */ +struct xml_node { + struct xml_string* name; + struct xml_string* content; + struct xml_attribute** attributes; + struct xml_node** children; +}; + +/** + * [OPAQUE API] + * + * An xml_document simply contains the root node and the underlying buffer + */ +struct xml_document { + struct { + uint8_t* buffer; + size_t length; + } buffer; + + struct xml_node* root; +}; + + + + + +/** + * [PRIVATE] + * + * Parser context + */ +struct xml_parser { + uint8_t* buffer; + size_t position; + size_t length; +}; + +/** + * [PRIVATE] + * + * Character offsets + */ +enum xml_parser_offset { + NO_CHARACTER = -1, + CURRENT_CHARACTER = 0, + NEXT_CHARACTER = 1, +}; + + + + + +/** + * [PRIVATE] + * + * @return Number of attributes in 0-terminated array + */ +static size_t get_zero_terminated_array_attributes(struct xml_attribute** attributes) { + size_t elements = 0; + + while (attributes[elements]) { + ++elements; + } + + return elements; +} + + + +/** + * [PRIVATE] + * + * @return Number of nodes in 0-terminated array + */ +static size_t get_zero_terminated_array_nodes(struct xml_node** nodes) { + size_t elements = 0; + + while (nodes[elements]) { + ++elements; + } + + return elements; +} + + + +/** + * [PRIVATE] + * + * @warning No UTF conversions will be attempted + * + * @return true iff a == b + */ +static _Bool xml_string_equals(struct xml_string* a, struct xml_string* b) { + + if (a->length != b->length) { + return false; + } + + size_t i = 0; for (; i < a->length; ++i) { + if (a->buffer[i] != b->buffer[i]) { + return false; + } + } + + return true; +} + + + +/** + * [PRIVATE] + */ +static uint8_t* xml_string_clone(struct xml_string* s) { + if (!s) { + return 0; + } + + uint8_t* clone = calloc(s->length + 1, sizeof(uint8_t)); + + xml_string_copy(s, clone, s->length); + clone[s->length] = 0; + + return clone; +} + + + +/** + * [PRIVATE] + * + * Frees the resources allocated by the string + * + * @warning `buffer` must _not_ be freed, since it is a reference to the + * document's buffer + */ +static void xml_string_free(struct xml_string* string) { + free(string); +} + + + +/** + * [PRIVATE] + * + * Frees the resources allocated by the attribute + */ +static void xml_attribute_free(struct xml_attribute* attribute) { + if(attribute->name) { + xml_string_free(attribute->name); + } + if(attribute->content) { + xml_string_free(attribute->content); + } + free(attribute); +} + +/** + * [PRIVATE] + * + * Frees the resources allocated by the node + */ +static void xml_node_free(struct xml_node* node) { + xml_string_free(node->name); + + if (node->content) { + xml_string_free(node->content); + } + + struct xml_attribute** at = node->attributes; + while(*at) { + xml_attribute_free(*at); + ++at; + } + free(node->attributes); + + struct xml_node** it = node->children; + while (*it) { + xml_node_free(*it); + ++it; + } + free(node->children); + + free(node); +} + + + +/** + * [PRIVATE] + * + * Echos the parsers call stack for debugging purposes + */ +#ifdef XML_PARSER_VERBOSE +static void xml_parser_info(struct xml_parser* parser, char const* message) { + fprintf(stdout, "xml_parser_info %s\n", message); +} +#else +#define xml_parser_info(parser, message) {} +#endif + + + +/** + * [PRIVATE] + * + * Echos an error regarding the parser's source to the console + */ +static void xml_parser_error(struct xml_parser* parser, enum xml_parser_offset offset, char const* message) { + int row = 0; + int column = 0; + + //#define min(X,Y) ((X) < (Y) ? (X) : (Y)) + //#define max(X,Y) ((X) > (Y) ? (X) : (Y)) + size_t character = max(0, min(parser->length, parser->position + offset)); + //#undef min + //#undef max + + size_t position = 0; for (; position < character; ++position) { + column++; + + if ('\n' == parser->buffer[position]) { + row++; + column = 0; + } + } + + if (NO_CHARACTER != offset) { + fprintf(stderr, "xml_parser_error at %i:%i (is %c): %s\n", + row + 1, column, parser->buffer[character], message + ); + } else { + fprintf(stderr, "xml_parser_error at %i:%i: %s\n", + row + 1, column, message + ); + } +} + + + +/** + * [PRIVATE] + * + * Returns the n-th not-whitespace byte in parser and 0 if such a byte does not + * exist + */ +static uint8_t xml_parser_peek(struct xml_parser* parser, size_t n) { + size_t position = parser->position; + + while (position < parser->length) { + if (!isspace(parser->buffer[position])) { + if (n == 0) { + return parser->buffer[position]; + } else { + --n; + } + } + + position++; + } + + return 0; +} + + + +/** + * [PRIVATE] + * + * Moves the parser's position n bytes. If the new position would be out of + * bounds, it will be converted to the bounds itself + */ +static void xml_parser_consume(struct xml_parser* parser, size_t n) { + + /* Debug information + */ + #ifdef XML_PARSER_VERBOSE + #define min(X,Y) ((X) < (Y) ? (X) : (Y)) + char* consumed = alloca((n + 1) * sizeof(char)); + memcpy(consumed, &parser->buffer[parser->position], min(n, parser->length - parser->position)); + consumed[n] = 0; + #undef min + + size_t message_buffer_length = 512; + char* message_buffer = alloca(512 * sizeof(char)); + snprintf(message_buffer, message_buffer_length, "Consuming %li bytes \"%s\"", (long)n, consumed); + message_buffer[message_buffer_length - 1] = 0; + + xml_parser_info(parser, message_buffer); + #endif + + + /* Move the position forward + */ + parser->position += n; + + /* Don't go too far + * + * @warning Valid because parser->length must be greater than 0 + */ + if (parser->position >= parser->length) { + parser->position = parser->length - 1; + } +} + + + +/** + * [PRIVATE] + * + * Skips to the next non-whitespace character + */ +static void xml_skip_whitespace(struct xml_parser* parser) { + xml_parser_info(parser, "whitespace"); + + while (isspace(parser->buffer[parser->position])) { + if (parser->position + 1 >= parser->length) { + return; + } else { + parser->position++; + } + } +} + + + +/** + * [PRIVATE] + * + * Finds and creates all attributes on the given node. + * + * @author Blake Felt + * @see https://github.com/Molorius + */ +static struct xml_attribute** xml_find_attributes(struct xml_parser* parser, struct xml_string* tag_open) { + (void)parser; + xml_parser_info(parser, "find_attributes"); + char* tmp; + char* rest = NULL; + char* token; + char* str_name; + char* str_content; + const unsigned char* start_name; + const unsigned char* start_content; + size_t old_elements; + size_t new_elements; + struct xml_attribute* new_attribute; + struct xml_attribute** attributes; + int position; + + attributes = calloc(1, sizeof(struct xml_attribute*)); + attributes[0] = 0; + + tmp = (char*) xml_string_clone(tag_open); + + token = xml_strtok_r(tmp, " ", &rest); // skip the first value + if(token == NULL) { + goto cleanup; + } + tag_open->length = strlen(token); + + for(token=xml_strtok_r(NULL," ", &rest); token!=NULL; token=xml_strtok_r(NULL," ", &rest)) { + str_name = malloc(strlen(token)+1); + str_content = malloc(strlen(token)+1); + // %s=\"%s\" wasn't working for some reason, ugly hack to make it work + if(sscanf(token, "%[^=]=\"%[^\"]", str_name, str_content) != 2) { + if(sscanf(token, "%[^=]=\'%[^\']", str_name, str_content) != 2) { + free(str_name); + free(str_content); + continue; + } + } + position = (int)(token-tmp); + start_name = &tag_open->buffer[position]; + start_content = &tag_open->buffer[position + strlen(str_name) + 2]; + + new_attribute = malloc(sizeof(struct xml_attribute)); + new_attribute->name = malloc(sizeof(struct xml_string)); + new_attribute->name->buffer = (unsigned char*)start_name; + new_attribute->name->length = strlen(str_name); + new_attribute->content = malloc(sizeof(struct xml_string)); + new_attribute->content->buffer = (unsigned char*)start_content; + new_attribute->content->length = strlen(str_content); + + old_elements = get_zero_terminated_array_attributes(attributes); + new_elements = old_elements + 1; + attributes = realloc(attributes, (new_elements+1)*sizeof(struct xml_attribute*)); + + attributes[new_elements-1] = new_attribute; + attributes[new_elements] = 0; + + + free(str_name); + free(str_content); + } + +cleanup: + free(tmp); + return attributes; +} + + + +/** + * [PRIVATE] + * + * Parses the name out of the an XML tag's ending + * + * ---( Example )--- + * tag_name> + * --- + */ +static struct xml_string* xml_parse_tag_end(struct xml_parser* parser) { + xml_parser_info(parser, "tag_end"); + size_t start = parser->position; + size_t length = 0; + + /* Parse until `>' or a whitespace is reached + */ + while (start + length < parser->length) { + uint8_t current = xml_parser_peek(parser, CURRENT_CHARACTER); + + if (('>' == current) || isspace(current)) { + break; + } else { + xml_parser_consume(parser, 1); + length++; + } + } + + /* Consume `>' + */ + if ('>' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_tag_end::expected tag end"); + return 0; + } + xml_parser_consume(parser, 1); + + /* Return parsed tag name + */ + struct xml_string* name = malloc(sizeof(struct xml_string)); + name->buffer = &parser->buffer[start]; + name->length = length; + return name; +} + + + +/** + * [PRIVATE] + * + * Parses an opening XML tag without attributes + * + * ---( Example )--- + * <tag_name> + * --- + */ +static struct xml_string* xml_parse_tag_open(struct xml_parser* parser) { + xml_parser_info(parser, "tag_open"); + xml_skip_whitespace(parser); + + /* Consume `<' + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_tag_open::expected opening tag"); + return 0; + } + xml_parser_consume(parser, 1); + + /* Consume tag name + */ + return xml_parse_tag_end(parser); +} + + + +/** + * [PRIVATE] + * + * Parses an closing XML tag without attributes + * + * ---( Example )--- + * </tag_name> + * --- + */ +static struct xml_string* xml_parse_tag_close(struct xml_parser* parser) { + xml_parser_info(parser, "tag_close"); + xml_skip_whitespace(parser); + + /* Consume `</' + */ + if ( ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) + || ('/' != xml_parser_peek(parser, NEXT_CHARACTER))) { + + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_tag_close::expected closing tag `<'"); + } + if ('/' != xml_parser_peek(parser, NEXT_CHARACTER)) { + xml_parser_error(parser, NEXT_CHARACTER, "xml_parse_tag_close::expected closing tag `/'"); + } + + return 0; + } + xml_parser_consume(parser, 2); + + /* Consume tag name + */ + return xml_parse_tag_end(parser); +} + + + +/** + * [PRIVATE] + * + * Parses a tag's content + * + * ---( Example )--- + * this is + * a + * tag {} content + * --- + * + * @warning CDATA etc. is _not_ and will never be supported + */ +static struct xml_string* xml_parse_content(struct xml_parser* parser) { + xml_parser_info(parser, "content"); + + /* Whitespace will be ignored + */ + xml_skip_whitespace(parser); + + size_t start = parser->position; + size_t length = 0; + + /* Consume until `<' is reached + */ + while (start + length < parser->length) { + uint8_t current = xml_parser_peek(parser, CURRENT_CHARACTER); + + if ('<' == current) { + break; + } else { + xml_parser_consume(parser, 1); + length++; + } + } + + /* Next character must be an `<' or we have reached end of file + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + xml_parser_error(parser, CURRENT_CHARACTER, "xml_parse_content::expected <"); + return 0; + } + + /* Ignore tailing whitespace + */ + while ((length > 0) && isspace(parser->buffer[start + length - 1])) { + length--; + } + + /* Return text + */ + struct xml_string* content = malloc(sizeof(struct xml_string)); + content->buffer = &parser->buffer[start]; + content->length = length; + return content; +} + + + +/** + * [PRIVATE] + * + * Parses an XML fragment node + * + * ---( Example without children )--- + * <Node>Text</Node> + * --- + * + * ---( Example with children )--- + * <Parent> + * <Child>Text</Child> + * <Child>Text</Child> + * <Test>Content</Test> + * </Parent> + * --- + */ +static struct xml_node* xml_parse_node(struct xml_parser* parser) { + xml_parser_info(parser, "node"); + + /* Setup variables + */ + struct xml_string* tag_open = 0; + struct xml_string* tag_close = 0; + struct xml_string* content = 0; + + size_t original_length; + struct xml_attribute** attributes; + + struct xml_node** children = calloc(1, sizeof(struct xml_node*)); + children[0] = 0; + + + /* Parse open tag + */ + tag_open = xml_parse_tag_open(parser); + if (!tag_open) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag_open"); + goto exit_failure; + } + + original_length = tag_open->length; + attributes = xml_find_attributes(parser, tag_open); + + /* If tag ends with `/' it's self closing, skip content lookup */ + if (tag_open->length > 0 && '/' == tag_open->buffer[original_length - 1]) { + /* Drop `/' + */ + goto node_creation; + } + + /* If the content does not start with '<', a text content is assumed + */ + if ('<' != xml_parser_peek(parser, CURRENT_CHARACTER)) { + content = xml_parse_content(parser); + + if (!content) { + xml_parser_error(parser, 0, "xml_parse_node::content"); + goto exit_failure; + } + + + /* Otherwise children are to be expected + */ + } else while ('/' != xml_parser_peek(parser, NEXT_CHARACTER)) { + + /* Parse child node + */ + struct xml_node* child = xml_parse_node(parser); + if (!child) { + xml_parser_error(parser, NEXT_CHARACTER, "xml_parse_node::child"); + goto exit_failure; + } + + /* Grow child array :) + */ + size_t old_elements = get_zero_terminated_array_nodes(children); + size_t new_elements = old_elements + 1; + children = realloc(children, (new_elements + 1) * sizeof(struct xml_node*)); + + /* Save child + */ + children[new_elements - 1] = child; + children[new_elements] = 0; + } + + + /* Parse close tag + */ + tag_close = xml_parse_tag_close(parser); + if (!tag_close) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag_close"); + goto exit_failure; + } + + + /* Close tag has to match open tag + */ + if (!xml_string_equals(tag_open, tag_close)) { + xml_parser_error(parser, NO_CHARACTER, "xml_parse_node::tag missmatch"); + goto exit_failure; + } + + + /* Return parsed node + */ + xml_string_free(tag_close); + +node_creation:; + struct xml_node* node = malloc(sizeof(struct xml_node)); + node->name = tag_open; + node->content = content; + node->attributes = attributes; + node->children = children; + return node; + + + /* A failure occured, so free all allocalted resources + */ +exit_failure: + if (tag_open) { + xml_string_free(tag_open); + } + if (tag_close) { + xml_string_free(tag_close); + } + if (content) { + xml_string_free(content); + } + + struct xml_node** it = children; + while (*it) { + xml_node_free(*it); + ++it; + } + free(children); + + return 0; +} + + + + + +/** + * [PUBLIC API] + */ +struct xml_document* xml_parse_document(uint8_t* buffer, size_t length) { + + /* Initialize parser + */ + struct xml_parser parser = { + .buffer = buffer, + .position = 0, + .length = length + }; + + /* An empty buffer can never contain a valid document + */ + if (!length) { + xml_parser_error(&parser, NO_CHARACTER, "xml_parse_document::length equals zero"); + return 0; + } + + /* Parse the root node + */ + struct xml_node* root = xml_parse_node(&parser); + if (!root) { + xml_parser_error(&parser, NO_CHARACTER, "xml_parse_document::parsing document failed"); + return 0; + } + + /* Return parsed document + */ + struct xml_document* document = malloc(sizeof(struct xml_document)); + document->buffer.buffer = buffer; + document->buffer.length = length; + document->root = root; + + return document; +} + + + +/** + * [PUBLIC API] + */ +struct xml_document* xml_open_document(FILE* source) { + + /* Prepare buffer + */ + size_t const read_chunk = 1; // TODO 4096; + + size_t document_length = 0; + size_t buffer_size = 1; // TODO 4069 + uint8_t* buffer = malloc(buffer_size * sizeof(uint8_t)); + + /* Read hole file into buffer + */ + while (!feof(source)) { + + /* Reallocate buffer + */ + if (buffer_size - document_length < read_chunk) { + buffer = realloc(buffer, buffer_size + 2 * read_chunk); + buffer_size += 2 * read_chunk; + } + + size_t read = fread( + &buffer[document_length], + sizeof(uint8_t), read_chunk, + source + ); + + document_length += read; + } + fclose(source); + + /* Try to parse buffer + */ + struct xml_document* document = xml_parse_document(buffer, document_length); + + if (!document) { + free(buffer); + return 0; + } + return document; +} + + + +/** + * [PUBLIC API] + */ +void xml_document_free(struct xml_document* document, bool free_buffer) { + xml_node_free(document->root); + + if (free_buffer) { + free(document->buffer.buffer); + } + free(document); +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_document_root(struct xml_document* document) { + return document->root; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_name(struct xml_node* node) { + return node->name; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_content(struct xml_node* node) { + return node->content; +} + + + +/** + * [PUBLIC API] + * + * @warning O(n) + */ +size_t xml_node_children(struct xml_node* node) { + return get_zero_terminated_array_nodes(node->children); +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_node_child(struct xml_node* node, size_t child) { + if (child >= xml_node_children(node)) { + return 0; + } + + return node->children[child]; +} + + + +/** + * [PUBLIC API] + */ +size_t xml_node_attributes(struct xml_node* node) { + return get_zero_terminated_array_attributes(node->attributes); +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_attribute_name(struct xml_node* node, size_t attribute) { + if(attribute >= xml_node_attributes(node)) { + return 0; + } + + return node->attributes[attribute]->name; +} + + + +/** + * [PUBLIC API] + */ +struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attribute) { + if(attribute >= xml_node_attributes(node)) { + return 0; + } + + return node->attributes[attribute]->content; +} + + + +/** + * [PUBLIC API] + */ +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name, ...) { + + /* Find children, 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 = (const uint8_t *)child_name, + .length = strlen((const char *)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 { + va_end(arguments); + return 0; + } + } + } + + /* No child with that name found + */ + if (!next) { + va_end(arguments); + 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] + */ +size_t xml_string_length(struct xml_string* string) { + if (!string) { + return 0; + } + return string->length; +} + + + +/** + * [PUBLIC API] + */ +void xml_string_copy(struct xml_string* string, uint8_t* buffer, size_t length) { + if (!string) { + return; + } + + //#define min(X,Y) ((X) < (Y) ? (X) : (Y)) + length = min(length, string->length); + //#undef min + + memcpy(buffer, string->buffer, length); +} + diff --git a/libs/xml.c/src/xml.h b/libs/xml.c/src/xml.h new file mode 100644 index 0000000..688a4be --- /dev/null +++ b/libs/xml.c/src/xml.h @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ +#ifndef HEADER_XML +#define HEADER_XML + + +/** + * Includes + */ +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Opaque structure holding the parsed xml document + */ +struct xml_document; +struct xml_node; +struct xml_attribute; + +/** + * Internal character sequence representation + */ +struct xml_string; + + + +/** + * Tries to parse the XML fragment in buffer + * + * @param buffer Chunk to parse + * @param length Size of the buffer + * + * @warning `buffer` will be referenced by the document, you may not free it + * until you free the xml_document + * @warning You have to call xml_document_free after you finished using the + * document + * + * @return The parsed xml fragment iff parsing was successful, 0 otherwise + */ +struct xml_document* xml_parse_document(uint8_t* buffer, size_t length); + + + +/** + * Tries to read an XML document from disk + * + * @param source File that will be read into an xml document. Will be closed + * + * @warning You have to call xml_document_free with free_buffer = true after you + * finished using the document + * + * @return The parsed xml fragment iff parsing was successful, 0 otherwise + */ +struct xml_document* xml_open_document(FILE* source); + + + +/** + * Frees all resources associated with the document. All xml_node and xml_string + * references obtained through the document will be invalidated + * + * @param document xml_document to free + * @param free_buffer iff true the internal buffer supplied via xml_parse_buffer + * will be freed with the `free` system call + */ +void xml_document_free(struct xml_document* document, bool free_buffer); + + +/** + * @return xml_node representing the document root + */ +struct xml_node* xml_document_root(struct xml_document* document); + + + +/** + * @return The xml_node's tag name + */ +struct xml_string* xml_node_name(struct xml_node* node); + + + +/** + * @return The xml_node's string content (if available, otherwise NULL) + */ +struct xml_string* xml_node_content(struct xml_node* node); + + + +/** + * @return Number of child nodes + */ +size_t xml_node_children(struct xml_node* node); + + + +/** + * @return The n-th child or 0 if out of range + */ +struct xml_node* xml_node_child(struct xml_node* node, size_t child); + + + +/** + * @return Number of attribute nodes + */ +size_t xml_node_attributes(struct xml_node* node); + + + +/** + * @return the n-th attribute name or 0 if out of range + */ +struct xml_string* xml_node_attribute_name(struct xml_node* node, size_t attribute); + + + +/** + * @return the n-th attribute content or 0 if out of range + */ +struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attribute); + + + +/** + * @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 + */ +size_t xml_string_length(struct xml_string* string); + + + +/** + * Copies the string into the supplied buffer + * + * @warning String will not be 0-terminated + * @warning Will write at most length bytes, even if the string is longer + */ +void xml_string_copy(struct xml_string* string, uint8_t* buffer, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/libs/xml.c/test/CMakeLists.txt b/libs/xml.c/test/CMakeLists.txt new file mode 100644 index 0000000..09097d0 --- /dev/null +++ b/libs/xml.c/test/CMakeLists.txt @@ -0,0 +1,128 @@ +# xml.c / test +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + + + +# Example +add_executable( + "${PROJECT_NAME}-example" + "${CMAKE_CURRENT_LIST_DIR}/example.c" +) + +target_compile_options( + "${PROJECT_NAME}-example" + PRIVATE + -std=c11 +) + +target_link_libraries( + "${PROJECT_NAME}-example" + PRIVATE + xml +) + +add_test( + NAME "${PROJECT_NAME}-example" + COMMAND "${PROJECT_NAME}-example" +) + + + +# Test cases +FILE( COPY "${CMAKE_CURRENT_LIST_DIR}/test.xml" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" +) + +FILE( COPY "${CMAKE_CURRENT_LIST_DIR}/test-attributes.xml" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" +) + + + +# Test (C) +add_executable( + "${PROJECT_NAME}-test-c" + "${CMAKE_CURRENT_LIST_DIR}/test-xml-c.c" +) + +target_compile_options( + "${PROJECT_NAME}-test-c" + PRIVATE + -std=c11 +) + +target_link_libraries( + "${PROJECT_NAME}-test-c" + PRIVATE + xml +) + + +add_test( + NAME "${PROJECT_NAME}-test-c" + COMMAND "${PROJECT_NAME}-test-c" +) + +add_test( + NAME "${PROJECT_NAME}-test-c-valgrind" + COMMAND valgrind --tool=memcheck --leak-check=full --track-origins=yes -v "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-test-c" +) + + + +# Test (C++) +add_executable( + "${PROJECT_NAME}-test-cpp" + "${CMAKE_CURRENT_LIST_DIR}/test-xml-cpp.cpp" +) + +target_compile_options( + "${PROJECT_NAME}-test-cpp" + PRIVATE + -std=c++11 +) + +target_link_libraries( + "${PROJECT_NAME}-test-cpp" + PRIVATE + xml +) + + +add_test( + NAME "${PROJECT_NAME}-test-cpp" + COMMAND "${PROJECT_NAME}-test-cpp" +) + + +add_test( + NAME "${PROJECT_NAME}-test-cpp-valgrind" + COMMAND valgrind --tool=memcheck --leak-check=full --track-origins=yes -v "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-test-cpp" +) + + + +# Test huitre39 +add_executable( + "${PROJECT_NAME}-test-huitre39" + "${CMAKE_CURRENT_LIST_DIR}/test-huitre39.c" +) + +target_compile_options( + "${PROJECT_NAME}-test-huitre39" + PRIVATE + -std=c11 +) + +target_link_libraries( + "${PROJECT_NAME}-test-huitre39" + PRIVATE + xml +) + + +add_test( + NAME "${PROJECT_NAME}-test-huitre39" + COMMAND "${PROJECT_NAME}-test-huitre39" +) + diff --git a/libs/xml.c/test/example.c b/libs/xml.c/test/example.c new file mode 100644 index 0000000..ebf605c --- /dev/null +++ b/libs/xml.c/test/example.c @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <xml.h> + + + +int main(int argc, char** argv) { + + /* XML source, could be read from disk + */ + uint8_t* source = "" + "<Root>" + "<Hello>World</Hello>" + "<This>" + "<Is>:-)</Is>" + "<An>:-O</An>" + "<Example>:-D</Example>" + "</This>" + "</Root>" + ; + + + /* Parse the document + * + * Watch out: Remember not to free the source until you have freed the + * document itself. If you have to free the source before, supply a + * copy to xml_parse_document which can be freed together with the + * document (`free_buffer' argument to `xml_document_free') + */ + struct xml_document* document = xml_parse_document(source, strlen(source)); + + /* You _have_ to check the result of `xml_parse_document', if it's 0 + * then the source could not be parsed. If you think this is a bug in + * xml.c, than use a debug build (cmake -DCMAKE_BUILD_TYPE=Debug) which + * will verbosely tell you about the parsing process + */ + if (!document) { + printf("Could parse document\n"); + exit(EXIT_FAILURE); + } + struct xml_node* root = xml_document_root(document); + + + /* Say Hello World :-) + */ + struct xml_node* root_hello = xml_node_child(root, 0); + struct xml_string* hello = xml_node_name(root_hello); + struct xml_string* world = xml_node_content(root_hello); + + /* Watch out: `xml_string_copy' will not 0-terminate your buffers! (but + * `calloc' will :-) + */ + uint8_t* hello_0 = calloc(xml_string_length(hello) + 1, sizeof(uint8_t)); + uint8_t* world_0 = calloc(xml_string_length(world) + 1, sizeof(uint8_t)); + xml_string_copy(hello, hello_0, xml_string_length(hello)); + xml_string_copy(world, world_0, xml_string_length(world)); + + printf("%s %s\n", hello_0, world_0); + free(hello_0); + free(world_0); + + + /* Extract amount of Root/This children + */ + struct xml_node* root_this = xml_node_child(root, 1); + printf("Root/This has %lu children\n", (unsigned long)xml_node_children(root_this)); + + + /* Remember to free the document or you'll risk a memory leak + */ + xml_document_free(document, false); +} + diff --git a/libs/xml.c/test/test-attributes.xml b/libs/xml.c/test/test-attributes.xml new file mode 100644 index 0000000..7832c02 --- /dev/null +++ b/libs/xml.c/test/test-attributes.xml @@ -0,0 +1 @@ +<Test value="2" value_2="Hello"></Test> diff --git a/libs/xml.c/test/test-huitre39.c b/libs/xml.c/test/test-huitre39.c new file mode 100644 index 0000000..43b9ae7 --- /dev/null +++ b/libs/xml.c/test/test-huitre39.c @@ -0,0 +1,181 @@ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "./xml.h" + + + + + +/** + * Will halt the program iff assertion fails + */ +static void _assert_that(_Bool condition, char const* message, char const* func, char const* file, int line) { + if (!condition) { + fprintf(stderr, "Assertion failed: %s, in %s (%s:%i)\n", message, func, file, line); + exit(EXIT_FAILURE); + } +} + +#define assert_that(condition, message) \ + _assert_that(condition, message, __func__, __FILE__, __LINE__) + + + + + +/** + * Behaves similar to `getElementsByTagName`, however returns just the first and + * not all elements with a given tag name + * + * @param base Node in tree which should be the start of te recursive search + * @param 0-terminated tag name, case sensitive + * + * @return First node below `base` iff found, otherwise 0 + * @warning Depth-First search! + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName + */ +static struct xml_node* get_node_by_name(struct xml_node* base, uint8_t* name) { + + + /* Check whether `base` already has the tag name we are searching for + */ + size_t const name_length = strlen(name); + + uint8_t* base_name = xml_easy_name(base); + size_t const base_name_length = strlen(base_name); + + + /* Length of `name` and `base_name` do match, we should now do a real + * comparison + */ + if (name_length == base_name_length) { + int const rs = memcmp(name, base_name, name_length); + + /* Names match! We have found an element which fullfills our + * search criteria + */ + if (!rs) { + free(base_name); + return base; + } + } + + + /* Unfortunately, `base` is not the element we are looking for :-( + */ + free(base_name); + + + /* Let's take a look at the children of `base` + */ + size_t const number_of_children = xml_node_children(base); + + + /* No children → No luck with base + */ + if (!number_of_children) { + return 0; + } + + + /* Recursivly look through all children + */ + size_t child = 0; for (; child < number_of_children; ++child) { + struct xml_node* child_node = xml_node_child(base, child); + + /* Maybe this child does contain the element we are looking for? + */ + struct xml_node* search_result = get_node_by_name( + child_node, name + ); + + /* We are lucky! + */ + if (search_result) { + return search_result; + } + } + + + /* No luck :-( + */ + return 0; +} + + + + + +int main(int argc, char** argv) { + + /* XML source, could be read from disk + */ + uint8_t* source = "" + "<Root>" + "<Hello>World</Hello>" + + "<Functions>" + "<Function>" + "<as>testas one</as>" + "<os>testos</os>" + "</Function>" + + "<Function>" + "<is>testis</is>" + "<us>testus</us>" + "<ls>testls</ls>" + "</Function>" + + "<Function>" + "<mn>testmn</mn>" + "<as>testas two</as>" + "</Function>" + "</Functions>" + "</Root>" + ; + + struct xml_document* document = xml_parse_document(source, strlen(source)); + + if (!document) { + printf("Could parse document\n"); + exit(EXIT_FAILURE); + } + struct xml_node* root = xml_document_root(document); + + + + /* We expect to find Root / Functions / Function#1 / us + */ + struct xml_node* us = get_node_by_name(root, "us"); + assert_that(us, "Did not find element by tag name `us'"); + + uint8_t* us_content = xml_easy_content(us); + assert_that(us_content, "`us' should have content"); + assert_that(!strcmp(us_content, "testus"), "Unexpected content for node `us'"); + free(us_content); + + + /* We expect to find Root / Functions / Function#0 / as + */ + struct xml_node* as = get_node_by_name(root, "as"); + assert_that(as, "Did not find element by tag name `as'"); + + uint8_t* as_content = xml_easy_content(as); + assert_that(as_content, "`as' should have content"); + assert_that(!strcmp(as_content, "testas one"), "Unexpected content for first `as' node"); + free(as_content); + + + /* We do not expect do find a node with tag name `does_not_exist' + */ + struct xml_node* does_not_exist = get_node_by_name(root, "does_not_exist"); + assert_that(!does_not_exist, "Found node that should not exist"); + + + + xml_document_free(document, false); +} + diff --git a/libs/xml.c/test/test-xml-c.c b/libs/xml.c/test/test-xml-c.c new file mode 100644 index 0000000..ee54bac --- /dev/null +++ b/libs/xml.c/test/test-xml-c.c @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ +#include <alloca.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <xml.h> + + + + + +/** + * Will halt the program iff assertion fails + */ +static void _assert_that(_Bool condition, char const* message, char const* func, char const* file, int line) { + if (!condition) { + fprintf(stderr, "Assertion failed: %s, in %s (%s:%i)\n", message, func, file, line); + exit(EXIT_FAILURE); + } +} + +#define assert_that(condition, message) \ + _assert_that(condition, message, __func__, __FILE__, __LINE__) + + + +/** + * @return true iff xml string equals the c string + */ +static _Bool string_equals(struct xml_string* a, char const* b) { + size_t a_length = xml_string_length(a); + size_t b_length = strlen(b); + + uint8_t* a_buffer = alloca((a_length + 1) * sizeof(uint8_t)); + xml_string_copy(a, a_buffer, a_length); + a_buffer[a_length] = 0; + + if (a_length != b_length) { + fprintf(stderr, "string_equals: %s#%i <> %s#%i\n", a_buffer, (int)a_length, b, (int)b_length); + return false; + } + + size_t i = 0; for (; i < a_length; ++i) { + if (a_buffer[i] != b[i]) { + fprintf(stderr, "string_equals: %s <> %s\n", a_buffer, b); + return false; + } + } + + return true; +} + + + +/** + * Converts a static character array to an uint8_t data source + */ +#define SOURCE(source, content) \ + uint8_t* source = calloc(strlen(content) + 1, sizeof(uint8_t)); \ + { char const* content_string = content; \ + memcpy(source, content_string, strlen(content) + 1); \ + } + + + +/** + * Tries to parse a simple document containing only one tag + */ +static void test_xml_parse_document_0() { + SOURCE(source, "<Hello>World</Hello>"); +// uint8_t* source = malloc((1 + strlen("<Hello>World</Hello>")) * sizeof(uint8_t)); +// { char const* content_string = "<Hello>World</Hello>"; +// memcpy(source, content_string, strlen("<Hello>World</Hello>") + 1); +// } + + 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), "Hello"), "root node name must be `Hello'"); + assert_that(string_equals(xml_node_content(root), "World"), "root node content must be `World'"); + + xml_document_free(document, true); +} + +/** + * Tries to parse a document containing multiple tags + */ +static void test_xml_parse_document_1() { + SOURCE(source, "" + "<Parent>\n" + "\t<Child>\n" + "\t\tFirst content\n" + "\t</Child>\n" + "\t<Child>\n" + "\t\tSecond content\n" + "\t</Child>\n" + "</Parent>\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(2 == xml_node_children(root), "root must have two children"); + + struct xml_node* first_child = xml_node_child(root, 0); + struct xml_node* second_child = xml_node_child(root, 1); + assert_that(first_child && second_child, "Failed retrieving the children of root"); + + struct xml_node* third_child = xml_node_child(root, 2); + assert_that(!third_child, "root has a third child where non should be"); + + assert_that(string_equals(xml_node_name(first_child), "Child"), "first_child node name must be `Child'"); + assert_that(string_equals(xml_node_content(first_child), "First content"), "first_child node content must be `First content'"); + assert_that(string_equals(xml_node_name(second_child), "Child"), "second_child node name must be `Child'"); + assert_that(string_equals(xml_node_content(second_child), "Second content"), "second_child node content must be `tSecond content'"); + + xml_document_free(document, true); +} + + + +/** + * Tests the eas functionality + */ +static void test_xml_parse_document_2() { + SOURCE(source, "" + "<Parent>\n" + "\t<Child>\n" + "\t\tFirst content\n" + "\t</Child>\n" + "\t<This><Is>\n" + "<A><Test>Content A</Test></A>\n" + "<B><Test>Content B</Test></B>\n" + "\t</Is></This>\n" + "\t<Child>\n" + "\t\tSecond content\n" + "\t</Child>\n" + "</Parent>\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); +} + + + +/** + * Tests the xml_open_document functionality + */ +static void test_xml_parse_document_3() { + #define FILE_NAME "test.xml" + FILE* handle = fopen(FILE_NAME, "rb"); + assert_that(handle, "Cannot open " FILE_NAME); + + struct xml_document* document = xml_open_document(handle); + assert_that(document, "Cannot parse " FILE_NAME); + + struct xml_node* element = xml_easy_child( + xml_document_root(document), "Element", "With", 0 + ); + assert_that(element, "Cannot find Document/Element/With"); + assert_that(string_equals(xml_node_content(element), "Child"), "Content of Document/Element/With must be `Child'"); + + xml_document_free(document, true); + #undef FILE_NAME +} + + + +/** + * Test parsing of attributes + * + * @author Isty001 + * @see https://github.com/Isty001/ + */ +static void test_xml_parse_attributes() { + #define FILE_NAME "test-attributes.xml" + FILE* handle = fopen(FILE_NAME, "rb"); + assert_that(handle, "Cannot open " FILE_NAME); + + struct xml_document* document = xml_open_document(handle); + assert_that(document, "Cannot parse " FILE_NAME); + + struct xml_node* element = xml_easy_child( + xml_document_root(document), 0 + ); + + assert_that(element, "Cannot find Document/Element/With"); + assert_that(2 == xml_node_attributes(element), "Should have 2 attributes"); + + assert_that(string_equals(xml_node_attribute_name(element, 0), "value"), "Content of Document/Element/With must be `Child'"); + assert_that(string_equals(xml_node_attribute_content(element, 0), "2"), "First attribute's content should be \"2\""); + + assert_that(string_equals(xml_node_attribute_name(element, 1), "value_2"), "Content of Document/Element/With must be `Child'"); + assert_that(string_equals(xml_node_attribute_content(element, 1), "Hello"), "Second attribute's content should be Hello"); + + xml_document_free(document, true); + #undef FILE_NAME +} + + + +/** + * Console interface + */ +int main(int argc, char** argv) { + test_xml_parse_document_0(); + test_xml_parse_document_1(); + test_xml_parse_document_2(); + test_xml_parse_document_3(); + test_xml_parse_attributes(); + + fprintf(stdout, "All tests passed :-)\n"); + exit(EXIT_SUCCESS); +} diff --git a/libs/xml.c/test/test-xml-cpp.cpp b/libs/xml.c/test/test-xml-cpp.cpp new file mode 100644 index 0000000..513d14b --- /dev/null +++ b/libs/xml.c/test/test-xml-cpp.cpp @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2012 ooxi/xml.c + * https://github.com/ooxi/xml.c + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include <iostream> +#include <cstdlib> +#include <cstdio> +#include <xml.h> + +/** + * Will halt the program iff assertion fails + */ +static void _assert_that(bool condition, const char* message, + const char* func, const char* file, int line) { + if (!condition) { + std::cerr << "Assertion failed: " << message << ", in " << func << " (" + << file << ":" << line << ")\n"; + exit(EXIT_FAILURE); + } +} + +#define assert_that(condition, message) \ + _assert_that(condition, message, __func__, __FILE__, __LINE__) + +/** + * @return true iff xml string equals the c string + */ +static bool string_equals(struct xml_string* a, const char* b) { + size_t a_length = xml_string_length(a); + size_t b_length = strlen(b); + uint8_t* a_buffer = new uint8_t[((a_length + 1) * sizeof(uint8_t))]; + xml_string_copy(a, a_buffer, a_length); + a_buffer[a_length] = 0; + if (a_length != b_length) { + std::cerr << "string_equals: " << a_buffer << "#" << a_length << " <> " + << b << "#" << b_length << "\n"; + delete[] a_buffer; + return false; + } + size_t i = 0; for (; i < a_length; ++i) { + if (a_buffer[i] != b[i]) { + std::cerr << "string_equals: " << a_buffer << " <> " << b << "\n"; + delete[] a_buffer; + return false; + } + } + delete[] a_buffer; + return true; +} + + +/** + * Converts a static character array to an uint8_t data source which can be + * freed + */ +#define SOURCE(source, content) \ + uint8_t* source = (uint8_t*)calloc(strlen(content) + 1, sizeof(uint8_t)); \ + memcpy(source, (content), strlen(content) + 1); \ + + +/** + * Tries to parse a simple document containing only one tag + */ +static void test_xml_parse_document_0() { + SOURCE(source, "<Hello>World</Hello>"); + // uint8_t* source = malloc((1 + strlen("<Hello>World</Hello>")) * + // sizeof(uint8_t)); + // { + // const char* content_string = "<Hello>World</Hello>"; + // memcpy(source, content_string, strlen("<Hello>World</Hello>") + 1); + // } + struct xml_document* document = xml_parse_document(source, + strlen((const char *)source)); + assert_that(document, "Could not parse document"); + struct xml_node* root = xml_document_root(document); + assert_that(string_equals(xml_node_name(root), "Hello"), + "root node name must be `Hello'"); + assert_that(string_equals(xml_node_content(root), "World"), + "root node content must be `World'"); + xml_document_free(document, true); +} + +/** + * Tries to parse a document containing multiple tags + */ +static void test_xml_parse_document_1() { + SOURCE(source, "" + "<Parent>\n" + "\t<Child>\n" + "\t\tFirst content\n" + "\t</Child>\n" + "\t<Child>\n" + "\t\tSecond content\n" + "\t</Child>\n" + "</Parent>\n" + ); + struct xml_document* document = xml_parse_document(source, + strlen((const char *)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(2 == xml_node_children(root), "root must have two children"); + struct xml_node* first_child = xml_node_child(root, 0); + struct xml_node* second_child = xml_node_child(root, 1); + assert_that(first_child && second_child, + "Failed retrieving the children of root"); + struct xml_node* third_child = xml_node_child(root, 2); + assert_that(!third_child, "root has a third child where non should be"); + assert_that(string_equals(xml_node_name(first_child), "Child"), + "first_child node name must be `Child'"); + assert_that(string_equals(xml_node_content(first_child), "First content"), + "first_child node content must be `First content'"); + assert_that(string_equals(xml_node_name(second_child), "Child"), + "second_child node name must be `Child'"); + assert_that(string_equals(xml_node_content(second_child), "Second content"), + "second_child node content must be `tSecond content'"); + xml_document_free(document, true); +} + +/** + * Tests the eas functionality + */ +static void test_xml_parse_document_2() { + SOURCE(source, "" + "<Parent>\n" + "\t<Child>\n" + "\t\tFirst content\n" + "\t</Child>\n" + "\t<This><Is>\n" + "<A><Test>Content A</Test></A>\n" + "<B><Test>Content B</Test></B>\n" + "\t</Is></This>\n" + "\t<Child>\n" + "\t\tSecond content\n" + "\t</Child>\n" + "</Parent>\n" + ); + struct xml_document* document = xml_parse_document(source, + strlen((const char *)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, (uint8_t *)"This", + (uint8_t *)"Is", (uint8_t *)"A", (uint8_t *)"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, (uint8_t *)"This", + (uint8_t *)"Is", (uint8_t *)"B", (uint8_t *)"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, (uint8_t *)"This", + (uint8_t *)"Is", (uint8_t *)"C", (uint8_t *)"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, (uint8_t *)"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, (uint8_t *)"This", + (uint8_t *)"Is", 0)); + assert_that(!strcmp((const char*)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((const char*)content_a, "Content A"), + "Content of Parent/This/Is/A/Test must be `Content A'"); + free(content_a); + xml_document_free(document, true); +} + +/** + * Tests the xml_open_document functionality + */ +static void test_xml_parse_document_3() { + #define FILE_NAME "test.xml" + FILE* handle = fopen(FILE_NAME, "rb"); + assert_that(handle, "Cannot open " FILE_NAME); + struct xml_document* document = xml_open_document(handle); + assert_that(document, "Cannot parse " FILE_NAME); + struct xml_node* element = xml_easy_child(xml_document_root(document), + (uint8_t *)"Element", (uint8_t *)"With", 0); + assert_that(element, "Cannot find Document/Element/With"); + assert_that(string_equals(xml_node_content(element), "Child"), + "Content of Document/Element/With must be `Child'"); + xml_document_free(document, true); + #undef FILE_NAME +} + +int main(int argc, char **argv) { + test_xml_parse_document_0(); + test_xml_parse_document_1(); + test_xml_parse_document_2(); + test_xml_parse_document_3(); + std::cout << "All tests passed :-)\n"; + exit(EXIT_SUCCESS); +} + diff --git a/libs/xml.c/test/test.xml b/libs/xml.c/test/test.xml new file mode 100644 index 0000000..a0e1ab8 --- /dev/null +++ b/libs/xml.c/test/test.xml @@ -0,0 +1,7 @@ +<Document> + <Prefix></Prefix> + <Element> + <With>Child</With> + </Element> +</Document> + |
