├── .gitignore ├── include ├── rapidxml.hpp ├── rapidxml_print.hpp └── flxml │ ├── generator.h │ ├── utils.h │ ├── wrappers.h │ ├── iterators.h │ ├── predicates.h │ ├── print.h │ └── tables.h ├── conanfile.py ├── test ├── sonar-project.properties ├── conanfile.py ├── CMakeLists.txt └── src │ ├── low-level-parse.cpp │ ├── manipulations.cpp │ ├── perf.cpp │ ├── iterators.cpp │ ├── main.cc │ ├── xpath.cpp │ ├── round-trips.cpp │ └── parse-simple.cpp ├── .github └── workflows │ └── gtest.yml ├── license.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /cmake-build-debug/ 2 | /gtest-build/ 3 | -------------------------------------------------------------------------------- /include/rapidxml.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 4/19/25. 3 | // 4 | 5 | #ifndef RAPIDXML_HPP 6 | #define RAPIDXML_HPP 7 | 8 | #include 9 | 10 | namespace rapidxml = flxml; 11 | 12 | #endif //RAPIDXML_HPP 13 | -------------------------------------------------------------------------------- /include/rapidxml_print.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 4/19/25. 3 | // 4 | 5 | #ifndef RAPIDXML_PRINT_HPP 6 | #define RAPIDXML_PRINT_HPP 7 | 8 | #include 9 | 10 | namespace rapidxml = flxml; 11 | 12 | #endif //RAPIDXML_PRINT_HPP 13 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.tools.files import copy 3 | 4 | class FLXML(ConanFile): 5 | name = "flxml" 6 | exports_sources = "include/*" 7 | no_copy_source = True 8 | 9 | def package(self): 10 | copy(self, "include/*.hpp", self.source_folder, self.package_folder) 11 | copy(self, "include/*.h", self.source_folder, self.package_folder) 12 | 13 | def package_info(self): 14 | self.cpp_info.includedirs = ['include'] 15 | self.cpp_info.libdirs = [] 16 | self.cpp_info.bindirs = [] 17 | -------------------------------------------------------------------------------- /test/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=dwd-github_rapidxml 2 | sonar.organization=dwd-github 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=RapidXML 6 | #sonar.projectVersion=1.0 7 | 8 | 9 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 10 | #sonar.sources=. 11 | 12 | # Encoding of the source code. Default is default system encoding 13 | #sonar.sourceEncoding=UTF-8 14 | 15 | 16 | sonar.exclusions=**/deps/**, **/_deps/**, **/sentry-native/**, **/*.html, **/googletest/** 17 | sonar.sources=. 18 | sonar.coverage.exclusions=**/deps/**,**/_deps/**,**/sentry-native/**,**/*.html,**/googletest/**,**/test/** -------------------------------------------------------------------------------- /test/conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.tools.cmake import CMakeToolchain, CMake, CMakeDeps 3 | 4 | class FLXML(ConanFile): 5 | name = "flxml-test" 6 | settings = "os", "compiler", "build_type", "arch" 7 | test_type = "explicit" 8 | 9 | def configure(self): 10 | self.options["sentry-native"].backend = "inproc" 11 | 12 | def requirements(self): 13 | self.requires(f'flxml/{self.version}') 14 | self.requires("sentry-native/0.7.11") 15 | self.requires("gtest/1.12.1") 16 | 17 | def generate(self): 18 | deps = CMakeDeps(self) 19 | deps.generate() 20 | tc = CMakeToolchain(self) 21 | tc.user_presets_path = False 22 | tc.generate() 23 | 24 | def build(self): 25 | cmake = CMake(self) 26 | cmake.configure() 27 | cmake.build() 28 | 29 | def test(self): 30 | if not self.conf.get("tools.build:skip_test"): 31 | self.run("./rapidxml-test", env="conanrun") 32 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(rapidxml) 3 | 4 | # Include the Conan toolchain 5 | include(${CMAKE_CURRENT_SOURCE_DIR}/conan_toolchain.cmake) 6 | 7 | # GoogleTest requires at least C++14 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | option(RAPIDXML_PERF_TESTS "Enable (very slow) performance tests" OFF) 11 | option(RAPIDXML_SENTRY "Use Sentry (for tests only)" ON) 12 | 13 | find_package(GTest) 14 | find_package(flxml CONFIG REQUIRED) 15 | 16 | if (RAPIDXML_SENTRY) 17 | set(SENTRY_BACKEND inproc) 18 | find_package(sentry) 19 | endif(RAPIDXML_SENTRY) 20 | 21 | enable_testing() 22 | add_executable(rapidxml-test 23 | src/parse-simple.cpp 24 | src/manipulations.cpp 25 | src/round-trips.cpp 26 | src/low-level-parse.cpp 27 | src/perf.cpp 28 | src/iterators.cpp 29 | src/xpath.cpp 30 | src/main.cc 31 | ) 32 | target_link_libraries(rapidxml-test PRIVATE 33 | GTest::gtest 34 | flxml::flxml 35 | ) 36 | if(RAPIDXML_SENTRY) 37 | target_link_libraries(rapidxml-test PRIVATE sentry-native::sentry-native) 38 | target_compile_definitions(rapidxml-test PRIVATE DWD_GTEST_SENTRY=1) 39 | endif() 40 | target_include_directories(rapidxml-test 41 | PUBLIC 42 | ${CMAKE_CURRENT_SOURCE_DIR} 43 | ) 44 | if (RAPIDXML_PERF_TESTS) 45 | message("Running performance tests") 46 | file(DOWNLOAD https://www.w3.org/TR/xml/REC-xml-20081126.xml ${CMAKE_CURRENT_BINARY_DIR}/REC-xml-20081126.xml) 47 | target_compile_definitions(rapidxml-test PRIVATE RAPIDXML_TESTING=1 RAPIDXML_PERF_TESTS=1) 48 | else() 49 | message("Will skip performance tests") 50 | target_compile_definitions(rapidxml-test PRIVATE RAPIDXML_TESTING=1) 51 | endif() 52 | 53 | include(GoogleTest) 54 | gtest_discover_tests(rapidxml-test) 55 | -------------------------------------------------------------------------------- /include/flxml/generator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 29/07/2024. 3 | // 4 | 5 | #ifndef RAPIDXML_RAPIDXML_GENERATOR_HPP 6 | #define RAPIDXML_RAPIDXML_GENERATOR_HPP 7 | 8 | #include 9 | #include 10 | 11 | namespace flxml { 12 | template 13 | class generator { 14 | public: 15 | using value_pointer = std::remove_reference::type *; 16 | struct handle_type; 17 | struct promise_type { 18 | value_pointer value; 19 | 20 | std::suspend_always yield_value(T & v) { 21 | value = &v; 22 | return {}; 23 | } 24 | 25 | std::suspend_never initial_suspend() { 26 | return {}; 27 | } 28 | 29 | std::suspend_always final_suspend() noexcept { 30 | return {}; // Change this to std::suspend_always 31 | } 32 | 33 | void return_void() {} 34 | 35 | void unhandled_exception() { 36 | std::terminate(); 37 | } 38 | 39 | generator get_return_object() { 40 | return generator{handle_type{handle_type::from_promise(*this)}}; 41 | } 42 | }; 43 | 44 | struct handle_type : std::coroutine_handle { 45 | explicit handle_type(std::coroutine_handle && h) : std::coroutine_handle(std::move(h)) {} 46 | 47 | T &operator*() { 48 | return *(this->promise().value); 49 | } 50 | 51 | void operator++() { 52 | this->resume(); 53 | } 54 | 55 | bool operator!=(std::default_sentinel_t) const { 56 | return !this->done(); 57 | } 58 | }; 59 | 60 | explicit generator(handle_type h) : m_handle(h) {} 61 | 62 | ~generator() { 63 | if (m_handle) 64 | m_handle.destroy(); 65 | } 66 | 67 | handle_type begin() { 68 | return m_handle; 69 | } 70 | 71 | std::default_sentinel_t end() { 72 | return std::default_sentinel; 73 | } 74 | 75 | private: 76 | handle_type m_handle{}; 77 | }; 78 | } 79 | 80 | #endif //RAPIDXML_RAPIDXML_GENERATOR_HPP 81 | -------------------------------------------------------------------------------- /.github/workflows/gtest.yml: -------------------------------------------------------------------------------- 1 | name: gtest 2 | 3 | on: 4 | - push 5 | - pull_request 6 | - release 7 | 8 | jobs: 9 | gtest: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Figure out version 17 | id: tag 18 | run: | 19 | TAG=$(git describe --tags --abbrev=0) 20 | COMMITS_SINCE_TAG=$(git rev-list ${TAG}..HEAD --count) 21 | if [ "${COMMITS_SINCE_TAG}" -eq 0 ]; then 22 | echo "VERSION=${TAG}" >> $GITHUB_ENV 23 | else 24 | echo "VERSION="$(git describe --tags --abbrev=8) >> $GITHUB_ENV 25 | fi 26 | - name: Cache Conan2 dependencies 27 | uses: actions/cache@v3 28 | with: 29 | path: ~/.conan2 30 | key: ${{ runner.os }}-conan2-${{ hashFiles('**/conanfile.py') }} 31 | restore-keys: | 32 | ${{ runner.os }}-conan2- 33 | - name: Set up Python 3.8 for gcovr 34 | uses: actions/setup-python@v4 35 | - name: SonarQube install 36 | uses: SonarSource/sonarcloud-github-c-cpp@v3 37 | - name: Install Conan 38 | run: pip install conan 39 | - name: Configure Conan Profile 40 | run: | 41 | conan profile detect -e 42 | conan remote add conan-nexus https://nexus.cridland.io/repository/dwd-conan --force 43 | conan remote login conan-nexus ci --password ${{ secrets.NEXUS_PASSWORD }} 44 | - name: Conan Deps 45 | run: conan install . --output-folder=gh-build -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 46 | - name: Create package 47 | run: conan create . --version=${{ env.VERSION }} 48 | - name: Conan deps for tests 49 | run: cd test && conan install . --output-folder=. -s build_type=Debug -s compiler.cppstd=gnu23 -b missing --version=${{ env.VERSION }} 50 | - name: CMake tests 51 | run: cd test && cmake -B gh-build -DCMAKE_BUILD_TYPE=Debug 52 | - name: Build Wrapper 53 | run: cd test && build-wrapper-linux-x86-64 --out-dir sonar-out cmake --build gh-build 54 | - name: Sonar Scanner 55 | run: cd test && sonar-scanner --define sonar.cfamily.compile-commands=sonar-out/compile_commands.json 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 59 | - name: Run Tests 60 | run: cd test/gh-build && ./rapidxml-test 61 | - name: Upload 62 | run: conan upload -r conan-nexus --confirm 'flxml/*' 63 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Use of this software is granted under one of the following two licenses, 2 | to be chosen freely by the user. 3 | 4 | 1. Boost Software License - Version 1.0 - August 17th, 2003 5 | =============================================================================== 6 | 7 | Copyright (c) 2006, 2007 Marcin Kalicinski 8 | 9 | Permission is hereby granted, free of charge, to any person or organization 10 | obtaining a copy of the software and accompanying documentation covered by 11 | this license (the "Software") to use, reproduce, display, distribute, 12 | execute, and transmit the Software, and to prepare derivative works of the 13 | Software, and to permit third-parties to whom the Software is furnished to 14 | do so, all subject to the following: 15 | 16 | The copyright notices in the Software and this entire statement, including 17 | the above license grant, this restriction and the following disclaimer, 18 | must be included in all copies of the Software, in whole or in part, and 19 | all derivative works of the Software, unless such copies or derivative 20 | works are solely in the form of machine-executable object code generated by 21 | a source language processor. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 26 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 27 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 28 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 | DEALINGS IN THE SOFTWARE. 30 | 31 | 2. The MIT License 32 | =============================================================================== 33 | 34 | Copyright (c) 2006, 2007 Marcin Kalicinski 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy 37 | of this software and associated documentation files (the "Software"), to deal 38 | in the Software without restriction, including without limitation the rights 39 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 40 | of the Software, and to permit persons to whom the Software is furnished to do so, 41 | subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 49 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 52 | IN THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /test/src/low-level-parse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 05/07/2024. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | TEST(Constants, Empty) { 9 | flxml::xml_document<> doc; 10 | auto empty = doc.nullstr(); 11 | EXPECT_EQ(empty, ""); 12 | EXPECT_EQ(empty.size(), 0); 13 | } 14 | 15 | TEST(Predicates, Skip) { 16 | std::string test_data{""}; 17 | auto start = test_data.c_str(); 18 | auto end = ++start; 19 | flxml::xml_document<>::skip::element_name_pred,0>(end); 20 | EXPECT_EQ(*end, '/'); 21 | std::string_view sv({start, end}); 22 | EXPECT_EQ(sv, "simple"); 23 | } 24 | 25 | TEST(PredicateBuffer, Skip) { 26 | std::string test_data{""}; 27 | auto start = flxml::buffer_ptr(test_data); 28 | auto end = ++start; 29 | flxml::xml_document<>::skip::element_name_pred,0>(end); 30 | EXPECT_EQ(*end, '/'); 31 | std::string_view sv({start, end}); 32 | EXPECT_EQ(sv, "simple"); 33 | } 34 | 35 | TEST(Predicates, SkipAndExpand) { 36 | std::string test_data{"&hello;<"}; 37 | char * start = const_cast(test_data.c_str()); 38 | auto end = flxml::xml_document<>::skip_and_expand_character_refs< 39 | flxml::xml_document<>::text_pred, 40 | flxml::xml_document<>::text_pure_with_ws_pred, 41 | flxml::parse_no_entity_translation>(start); 42 | EXPECT_EQ(*end, '<'); 43 | } 44 | 45 | TEST(Predicates, SkipAndExpandShort) { 46 | std::string test_data{"&hello;"}; 47 | char * start = const_cast(test_data.c_str()); 48 | auto end = flxml::xml_document<>::skip_and_expand_character_refs< 49 | flxml::xml_document<>::text_pred, 50 | flxml::xml_document<>::text_pure_with_ws_pred, 51 | flxml::parse_no_entity_translation>(start); 52 | EXPECT_EQ(*end, '\0'); 53 | } 54 | 55 | TEST(Predicates, SkipAndExpandShorter) { 56 | std::string test_data{"&hell"}; 57 | char * start = const_cast(test_data.c_str()); 58 | auto end = flxml::xml_document<>::skip_and_expand_character_refs< 59 | flxml::xml_document<>::text_pred, 60 | flxml::xml_document<>::text_pure_with_ws_pred, 61 | flxml::parse_no_entity_translation>(start); 62 | EXPECT_EQ(*end, '\0'); 63 | } 64 | 65 | TEST(ParseFns, ParseBom) { 66 | std::string test_data{"\xEF\xBB\xBF"}; 67 | char *start = const_cast(test_data.c_str()); 68 | flxml::xml_document<> doc; 69 | doc.parse_bom<0>(start); 70 | EXPECT_EQ(*start, '<'); 71 | } 72 | 73 | TEST(ParseFns, ParseBomShort) { 74 | std::string test_data{"\xEF\xBB\xBF"}; 75 | char *start = const_cast(test_data.c_str()); 76 | flxml::xml_document<> doc; 77 | doc.parse_bom<0>(start); 78 | EXPECT_EQ(*start, '\0'); 79 | } 80 | 81 | TEST(ParseFns, ParseBomShorter) { 82 | std::string test_data{"\xEF\xBB"}; 83 | char *start = const_cast(test_data.c_str()); 84 | flxml::xml_document<> doc; 85 | doc.parse_bom<0>(start); 86 | EXPECT_EQ(*start, '\xEF'); 87 | } 88 | -------------------------------------------------------------------------------- /test/src/manipulations.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 01/07/24. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace { 11 | auto print(flxml::xml_document<> & doc) { 12 | std::string output; 13 | flxml::print(std::back_inserter(output), *doc.first_node()); 14 | return output; 15 | } 16 | } 17 | 18 | TEST(Create, Node) { 19 | flxml::xml_document<> doc; 20 | auto node = doc.allocate_node(flxml::node_element, "fish", "cakes"); 21 | doc.append_node(node); 22 | 23 | EXPECT_EQ( 24 | print(doc), 25 | "cakes\n" 26 | ); 27 | } 28 | 29 | TEST(Create, NodeEmpty) { 30 | flxml::xml_document<> doc; 31 | auto node = doc.allocate_node(flxml::node_element, "fish"); 32 | doc.append_node(node); 33 | 34 | EXPECT_EQ( 35 | print(doc), 36 | "\n" 37 | ); 38 | } 39 | 40 | TEST(Create, Node2) { 41 | flxml::xml_document<> doc; 42 | auto node = doc.allocate_node(flxml::node_element, "fish", "cakes"); 43 | doc.append_node(node); 44 | 45 | EXPECT_EQ( 46 | print(doc), 47 | "cakes\n" 48 | ); 49 | } 50 | 51 | static std::string s = "tuna"; 52 | 53 | std::string const & fn() { 54 | return s; 55 | } 56 | 57 | TEST(Create, NodeAttr) { 58 | flxml::xml_document<> doc; 59 | auto node = doc.allocate_node(flxml::node_element, "fish", "cakes"); 60 | auto haddock = doc.allocate_attribute("id", "haddock"); 61 | node->append_attribute(haddock); 62 | doc.append_node(node); 63 | 64 | EXPECT_EQ( 65 | print(doc), 66 | "cakes\n" 67 | ); 68 | 69 | const std::string & s2 = fn(); 70 | const flxml::xml_attribute<>::view_type & sv{s2}; 71 | 72 | auto tuna = doc.allocate_attribute("not-id", fn()); 73 | // These check that the same buffer is being used throughout, instead of creating temporaries. 74 | EXPECT_EQ(s.data(), s2.data()); 75 | EXPECT_EQ(s.data(), sv.data()); 76 | EXPECT_EQ(s.data(), tuna->value().data()); 77 | node->append_attribute(tuna); 78 | EXPECT_EQ(haddock->next_attribute(), tuna); 79 | EXPECT_EQ(tuna->parent(), node); 80 | EXPECT_EQ( 81 | print(doc), 82 | "cakes\n" 83 | ); 84 | node->remove_attribute(tuna); 85 | EXPECT_EQ(haddock->next_attribute(), nullptr); 86 | EXPECT_EQ(tuna->parent(), nullptr); 87 | EXPECT_EQ( 88 | print(doc), 89 | "cakes\n" 90 | ); 91 | node->prepend_attribute(tuna); 92 | EXPECT_EQ( 93 | print(doc), 94 | "cakes\n" 95 | ); 96 | node->value("pie"); 97 | EXPECT_EQ( 98 | print(doc), 99 | "pie\n" 100 | ); 101 | node->remove_all_attributes(); 102 | EXPECT_EQ( 103 | print(doc), 104 | "pie\n" 105 | ); 106 | auto child = node->append_element({"urn:xmpp:fish:0", "shark"}, fn()); 107 | EXPECT_EQ(s.data(), child->value().data()); 108 | EXPECT_EQ( 109 | print(doc), 110 | "\n\ttuna\n\n" 111 | ); 112 | child->append_element({"urn:xmpp:fish:0", "species"}, "tiger"); 113 | EXPECT_EQ( 114 | print(doc), 115 | "\n\t\n\t\ttiger\n\t\n\n" 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /include/flxml/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RAPIDXML_UTILS_HPP_INCLUDED 2 | #define RAPIDXML_UTILS_HPP_INCLUDED 3 | 4 | // Copyright (C) 2006, 2009 Marcin Kalicinski 5 | // Version 1.13 6 | // Revision $DateTime: 2009/05/13 01:46:17 $ 7 | //! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful 8 | //! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective. 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace flxml 17 | { 18 | 19 | //! Represents data loaded from a file 20 | template 21 | class file 22 | { 23 | 24 | public: 25 | 26 | //! Loads file into the memory. Data will be automatically destroyed by the destructor. 27 | //! \param filename Filename to load. 28 | file(const char *filename) 29 | { 30 | using namespace std; 31 | 32 | // Open stream 33 | basic_ifstream stream(filename, ios::binary); 34 | if (!stream) 35 | throw runtime_error(string("cannot open file ") + filename); 36 | stream.unsetf(ios::skipws); 37 | 38 | // Determine stream size 39 | stream.seekg(0, ios::end); 40 | size_t size = stream.tellg(); 41 | stream.seekg(0); 42 | 43 | // Load data and add terminating 0 44 | m_data.resize(size + 1); 45 | stream.read(&m_data.front(), static_cast(size)); 46 | m_data[size] = 0; 47 | } 48 | 49 | //! Loads file into the memory. Data will be automatically destroyed by the destructor 50 | //! \param stream Stream to load from 51 | file(std::basic_istream &stream) 52 | { 53 | using namespace std; 54 | 55 | // Load data and add terminating 0 56 | stream.unsetf(ios::skipws); 57 | m_data.assign(istreambuf_iterator(stream), istreambuf_iterator()); 58 | if (stream.fail() || stream.bad()) 59 | throw runtime_error("error reading stream"); 60 | m_data.push_back(0); 61 | } 62 | 63 | //! Gets file data. 64 | //! \return Pointer to data of file. 65 | Ch *data() 66 | { 67 | return &m_data.front(); 68 | } 69 | 70 | //! Gets file data. 71 | //! \return Pointer to data of file. 72 | const Ch *data() const 73 | { 74 | return &m_data.front(); 75 | } 76 | 77 | //! Gets file data size. 78 | //! \return Size of file data, in characters. 79 | std::size_t size() const 80 | { 81 | return m_data.size(); 82 | } 83 | 84 | private: 85 | 86 | std::vector m_data; // File data 87 | 88 | }; 89 | 90 | //! Counts children of node. Time complexity is O(n). 91 | //! \return Number of children of node 92 | template 93 | inline std::size_t count_children(xml_node *node) 94 | { 95 | xml_node *child = node->first_node(); 96 | std::size_t count = 0; 97 | while (child) 98 | { 99 | ++count; 100 | child = child->next_sibling(); 101 | } 102 | return count; 103 | } 104 | 105 | //! Counts attributes of node. Time complexity is O(n). 106 | //! \return Number of attributes of node 107 | template 108 | inline std::size_t count_attributes(xml_node *node) 109 | { 110 | xml_attribute *attr = node->first_attribute(); 111 | std::size_t count = 0; 112 | while (attr) 113 | { 114 | ++count; 115 | attr = attr->next_attribute(); 116 | } 117 | return count; 118 | } 119 | 120 | } 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /test/src/perf.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 07/07/2024. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include "flxml/print.h" 11 | #include "flxml/iterators.h" 12 | 13 | const auto xml_sample_file = "REC-xml-20081126.xml"; 14 | 15 | #ifdef RAPIDXML_PERF_TESTS 16 | #define PERF_TEST() (void)0 17 | #else 18 | #define PERF_TEST() GTEST_SKIP() << "Skipping performance test" 19 | #endif 20 | 21 | TEST(Perf, Parse) { 22 | using std::chrono::high_resolution_clock; 23 | using std::chrono::duration_cast; 24 | using std::chrono::microseconds; 25 | 26 | PERF_TEST(); 27 | flxml::file source(xml_sample_file); 28 | 29 | std::vector timings; 30 | for (auto i = 0; i != 1000; ++i) { 31 | flxml::xml_document<> doc; 32 | auto t1 = high_resolution_clock::now(); 33 | doc.parse(source.data()); 34 | auto t2 = high_resolution_clock::now(); 35 | auto ms_int = duration_cast(t2 - t1); 36 | timings.push_back(ms_int.count()); 37 | } 38 | auto total = 0ULL; 39 | for (auto t : timings) { 40 | total += t / 1000; 41 | } 42 | std::cout << "Execution time: " << total << " us\n"; 43 | } 44 | 45 | TEST(Perf, Parse2) { 46 | using std::chrono::high_resolution_clock; 47 | using std::chrono::duration_cast; 48 | using std::chrono::microseconds; 49 | 50 | PERF_TEST(); 51 | flxml::file source(xml_sample_file); 52 | 53 | std::vector timings; 54 | for (auto i = 0; i != 1000; ++i) { 55 | flxml::xml_document<> doc; 56 | std::string_view sv{source.data(), source.size() - 1}; // Drop the NUL. 57 | auto t1 = high_resolution_clock::now(); 58 | doc.parse(sv); 59 | auto t2 = high_resolution_clock::now(); 60 | auto ms_int = duration_cast(t2 - t1); 61 | timings.push_back(ms_int.count()); 62 | } 63 | auto total = 0ULL; 64 | for (auto t : timings) { 65 | total += t / 1000; 66 | } 67 | std::cout << "Execution time: " << total << " us\n"; 68 | } 69 | 70 | TEST(Perf, PrintClean) { 71 | using std::chrono::high_resolution_clock; 72 | using std::chrono::duration_cast; 73 | using std::chrono::microseconds; 74 | 75 | PERF_TEST(); 76 | flxml::file source(xml_sample_file); 77 | 78 | std::vector timings; 79 | flxml::xml_document<> doc; 80 | doc.parse(source.data()); 81 | for (auto i = 0; i != 1000; ++i) { 82 | std::string output; 83 | auto t1 = high_resolution_clock::now(); 84 | flxml::print(std::back_inserter(output), doc); 85 | auto t2 = high_resolution_clock::now(); 86 | auto ms_int = duration_cast(t2 - t1); 87 | timings.push_back(ms_int.count()); 88 | } 89 | auto total = 0ULL; 90 | for (auto t : timings) { 91 | total += t / 1000; 92 | } 93 | std::cout << "Execution time: " << total << " us\n"; 94 | } 95 | 96 | TEST(Perf, PrintDirty) { 97 | using std::chrono::high_resolution_clock; 98 | using std::chrono::duration_cast; 99 | using std::chrono::microseconds; 100 | 101 | PERF_TEST(); 102 | flxml::file source(xml_sample_file); 103 | 104 | std::vector timings; 105 | flxml::xml_document<> doc_o; 106 | doc_o.parse(source.data()); 107 | flxml::xml_document<> doc; 108 | for (auto & child : flxml::children(doc_o)) { 109 | doc.append_node(doc.clone_node(&child, true)); 110 | } 111 | for (auto i = 0; i != 1000; ++i) { 112 | std::string output; 113 | auto t1 = high_resolution_clock::now(); 114 | flxml::print(std::back_inserter(output), doc); 115 | auto t2 = high_resolution_clock::now(); 116 | auto ms_int = duration_cast(t2 - t1); 117 | timings.push_back(ms_int.count()); 118 | } 119 | auto total = 0ULL; 120 | for (auto t : timings) { 121 | total += t / 1000; 122 | } 123 | std::cout << "Execution time: " << total << " us\n"; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /test/src/iterators.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 10/07/2024. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | TEST(Iterators, Nodes) { 12 | std::string xml = ""; 13 | flxml::xml_document<> doc; 14 | doc.parse(xml); 15 | int i = 0; 16 | for (auto & child : doc.first_node()->children()) { 17 | ++i; 18 | switch(i) { 19 | case 1: 20 | EXPECT_EQ(child.name(), "one"); 21 | break; 22 | case 2: 23 | EXPECT_EQ(child.name(), "two"); 24 | break; 25 | case 3: 26 | EXPECT_EQ(child.name(), "three"); 27 | break; 28 | } 29 | } 30 | EXPECT_EQ(i, 3); 31 | } 32 | 33 | TEST(Iterators, Attributes) { 34 | std::string xml = R"()"; 35 | flxml::xml_document<> doc; 36 | doc.parse(xml); 37 | int i = 0; 38 | for (auto & child : doc.first_node()->attributes()) { 39 | ++i; 40 | switch(i) { 41 | case 1: 42 | EXPECT_EQ(child.name(), "one"); 43 | break; 44 | case 2: 45 | EXPECT_EQ(child.name(), "two"); 46 | break; 47 | case 3: 48 | EXPECT_EQ(child.name(), "three"); 49 | break; 50 | } 51 | } 52 | EXPECT_EQ(i, 3); 53 | } 54 | 55 | TEST(Predicates, Nodes) { 56 | std::string xml = ""; 57 | flxml::xml_document<> doc; 58 | doc.parse(xml); 59 | auto r = doc.first_node()->children(); 60 | for (auto const & child : r | std::ranges::views::filter([](auto const & n) { return n.name() == "two"; })) { 61 | EXPECT_EQ(child.name(), "two"); 62 | } 63 | auto c = std::ranges::count_if(r, [](auto const & n) { return n.name() == "two"; }); 64 | EXPECT_EQ(c, 1); 65 | auto match = std::ranges::find_if(r, [](auto const & n) { return n.name() == "two"; }); 66 | EXPECT_EQ(match->name(), "two"); 67 | } 68 | 69 | TEST(Predicates, AllNodes) { 70 | std::string xml = ""; 71 | flxml::xml_document<> doc; 72 | doc.parse(xml); 73 | auto it = flxml::descendant_iterator<>(doc.first_node()); 74 | EXPECT_EQ(it->name(), "one"); 75 | ++it; 76 | EXPECT_EQ(it->name(), "two"); 77 | ++it; 78 | EXPECT_EQ(it->name(), "three"); 79 | ++it; 80 | EXPECT_EQ(it->name(), "four"); 81 | ++it; 82 | EXPECT_EQ(it->name(), "five"); 83 | ++it; 84 | EXPECT_EQ(it->name(), "six"); 85 | ++it; 86 | EXPECT_FALSE(it.valid()); 87 | } 88 | 89 | TEST(Predicates, AllNodesRev) { 90 | std::string xml = ""; 91 | flxml::xml_document<> doc; 92 | doc.parse(xml); 93 | auto it = flxml::descendant_iterator<>(doc.first_node()); 94 | EXPECT_EQ(it->name(), "one"); 95 | ++it; 96 | EXPECT_EQ(it->name(), "two"); 97 | ++it; 98 | EXPECT_EQ(it->name(), "three"); 99 | ++it; 100 | EXPECT_EQ(it->name(), "four"); 101 | ++it; 102 | EXPECT_EQ(it->name(), "five"); 103 | ++it; 104 | EXPECT_EQ(it->name(), "six"); 105 | --it; 106 | EXPECT_EQ(it->name(), "five"); 107 | --it; 108 | EXPECT_EQ(it->name(), "four"); 109 | --it; 110 | EXPECT_EQ(it->name(), "three"); 111 | --it; 112 | EXPECT_EQ(it->name(), "two"); 113 | --it; 114 | EXPECT_EQ(it->name(), "one"); 115 | } 116 | 117 | TEST(Predicates, Attributes) { 118 | std::string xml = R"()"; 119 | flxml::xml_document<> doc; 120 | doc.parse(xml); 121 | auto r = doc.first_node()->attributes(); 122 | for (auto const & child : r | std::ranges::views::filter([](auto const & n) { return n.name() == "two"; })) { 123 | EXPECT_EQ(child.name(), "two"); 124 | } 125 | auto c = std::ranges::count_if(r, [](auto const & n) { return n.name() == "two"; }); 126 | EXPECT_EQ(c, 1); 127 | auto match = std::ranges::find_if(r, [](auto const & n) { return n.name() == "two"; }); 128 | EXPECT_EQ(match->name(), "two"); 129 | auto match2 = std::ranges::find_if(doc.first_node()->attributes(), [](auto const & n) { return n.name() == "two"; }); 130 | EXPECT_EQ(match2->name(), "two"); 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FLXML 2 | ## Or -- RapidXML, Dave's Version 3 | 4 | Hey! This is a fork of RapidXML, an ancient C++ library for parsing XML quickly and flexibly. To distinguish, this version is called "FLXML", for "Fast/Light XML". Hey, it's a name. 5 | 6 | There's a lot of forks of this around, and I (Dave Cridland) didn't write the vast majority of this library - instead, it was written by someone called Marcin Kalicinski, and his copyright is dated 2009. 7 | 8 | ## Version 2, Breaking Changes 9 | 10 | This is version 2.x. You might not want this, since it introduces a number of breaking changes from rapidxml. The rapidxml-like library is available, with breaking changes, by including `rapidxml.hpp` as before, within the `rapidxml` namespace - however this is an alias to the `flxml` namespace defined in `flxml.h`. 11 | 12 | It has breaking changes, the largest of which are: 13 | * No more case insensitive option. Really, nobody should be using XML case insensitively anyway, but it was too difficult to keep around, sorry. 14 | * Instead of passing around potentially unterminated character pointers with optional lengths, we now use std::basic_string_view 15 | * There is no need for string termination, now, so the parse function never terminates, and that option has vanished. 16 | * Return values that were previously bare pointers are now a safe wrapped pointer which ordinarily will check/throw for nullptr. 17 | * append/prepend/insert_node now also have an append/prepend/insert_element shorthand, which will allow an XML namespace to be included if wanted. 18 | * Parsing data can be done from a container as well as a NUL-terminated buffer. A NUL-terminated buffer remains slightly faster, and will be used if possible (for example, if you pass ina std::basic_string, it'll call c_str() on it and do that). 19 | 20 | Not breaking, but kind of nice: 21 | * The parse buffer is now treated as const, and will never be mutated. This incurs a slight performance penalty for handling long text values that have an encoded entity late in the string. 22 | * The iterators library is now included by default, and updated to m_handle most idiomatic modern C++ operations. 23 | 24 | Internal changes: 25 | * There is no longer a internal::measure or internal::compare; these just use the std::char_traits functions as used by the string_views. 26 | * Reserialization (that is, using the rapidxml::print family on a tree that is mostly or entirely from parsing) is now much faster, and will optimize itself to use simple buffer copies where the data is unchanged from parsing. 27 | * Alignment of the allocator uses C++11's alignof/std::align, and so should be more portable. 28 | 29 | New features: 30 | * Instead of the `doc->allocate_node` / `node->append_node` dance, you can now `node->append_element(name, value)`, where `name` can be either a `string` (or `string_view`, etc) or a tuple like {xmlns, local_name}, which will set an xmlns attribute if needed. 31 | * There's a xpathish thing going on in `flxml/predicates.h`, which lets you search for (or iterate through) elements using a trivial subset of XPath. 32 | * You can get access to containerish things in rapidxml_iterators by methods on nodes/documents, as `node.children()`, `node.attributes()` and a new `node.descendants()`. 33 | 34 | ### Fun 35 | 36 | The rapidxml_iterators library is now included in `flxml.h`, and you can do amusing things like: 37 | 38 | ```c++ 39 | for (auto & child : node.children()) { 40 | if (child.name() == "potato") scream_for(joy); 41 | } 42 | ``` 43 | 44 | More in [test/iterators.cpp](./test/iterators.cpp) 45 | 46 | Of course, in this case it might be simpler to: 47 | 48 | ```c++ 49 | auto xpath = flxml::xpath::parse("/potato"); 50 | for (auto & child : xp->all(node)) { 51 | scream_for(joy); 52 | } 53 | ``` 54 | 55 | More of that in [test/xpath.cpp](./test/xpath.cpp) 56 | 57 | For those of us who lose track of the buffer sometimes, clone_node() now takes an optional second argument of "true" if you want to also clone the strings. Otherwise, nodes will use string_views which reference the original parsed buffer. 58 | 59 | ### Gotchas 60 | 61 | The functions like find_node and name(...) that took a Ch * and optional length now take only a 62 | std::basic_string_view. Typical usage passed in 0, NULL, or nullptr for unwanted values; this will now segfault on C++20 63 | and earlier - use C++23 ideally, but you can pass in {} instead. This should probably be a 64 | std::optional> instead. 65 | 66 | ## Changes to the original 67 | 68 | I needed a library for fast XMPP processing (reading, processing, and reserializing), and this mostly fit the bill. However, not entirely, so this version adds: 69 | 70 | * XML Namespace support 71 | * An additional parse mode flag for doing shallow parsing. 72 | * An additional parse mode flag for extracting just one (child) element. 73 | 74 | ## Tests 75 | 76 | The other thing this fork added was a file of simple tests, which I've recently rewritten into GoogleTest. 77 | 78 | The original makes reference to an expansive test suite, but this was not included in the open source release. I'll expand these tests as and when I need to. 79 | 80 | The tests use a driver which can optionally use Sentry for performance/error tracking; to enable, use the CMake option RAPIDXML_SENTRY, and clone the [sentry-native](https://github.com/getsentry/sentry-native) repository into the root, and when running `rapidxml-test`, set SENTRY_DSN in the environment. 81 | 82 | The tests are in a different Conan package, to keep things light and simple. 83 | 84 | ## Pull Requests 85 | 86 | Pull request are very welcome, but do ensure you're happy with the licensing first. -------------------------------------------------------------------------------- /test/src/main.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 30/07/2024. 3 | // 4 | 5 | #include "gtest/gtest.h" 6 | #ifdef DWD_GTEST_SENTRY 7 | #include 8 | 9 | class EventListener : public ::testing::TestEventListener { 10 | sentry_transaction_context_t *tx_ctx = nullptr; 11 | sentry_transaction_t *tx = nullptr; 12 | sentry_span_t *main_span = nullptr; 13 | sentry_span_t *suite_span = nullptr; 14 | sentry_span_t *test_span = nullptr; 15 | std::string const & m_progname; 16 | public: 17 | EventListener(std::string const & progname) : m_progname(progname) {} 18 | ~EventListener() override = default; 19 | 20 | // Override this to define how to set up the environment. 21 | void OnTestProgramStart(const ::testing::UnitTest & u) override { 22 | sentry_options_t *options = sentry_options_new(); 23 | sentry_options_set_traces_sample_rate(options, 1.0); 24 | sentry_init(options); 25 | } 26 | void OnTestProgramEnd(const ::testing::UnitTest &) override { 27 | sentry_shutdown(); 28 | } 29 | 30 | void OnTestStart(::testing::TestInfo const & test_info) override { 31 | const char * testName = test_info.name(); 32 | std::string tname = test_info.test_suite_name(); 33 | tname += "."; 34 | tname += testName; 35 | test_span = sentry_span_start_child( 36 | suite_span, 37 | "test", 38 | tname.c_str() 39 | ); 40 | } 41 | 42 | // Override this to define how to tear down the environment. 43 | void OnTestEnd(const ::testing::TestInfo & ti) override { 44 | if (ti.result()->Failed()) { 45 | sentry_span_set_status(test_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 46 | } 47 | sentry_span_finish(test_span); // Mark the span as finished 48 | } 49 | 50 | void OnTestIterationStart(const testing::UnitTest &unit_test, int iteration) override { 51 | tx_ctx = sentry_transaction_context_new( 52 | m_progname.c_str(), 53 | "googletest" 54 | ); 55 | tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); 56 | main_span = sentry_transaction_start_child( 57 | tx, 58 | "googletest", 59 | m_progname.c_str() 60 | ); 61 | } 62 | 63 | void OnEnvironmentsSetUpStart(const testing::UnitTest &unit_test) override { 64 | 65 | } 66 | 67 | void OnEnvironmentsSetUpEnd(const testing::UnitTest &unit_test) override { 68 | 69 | } 70 | 71 | void OnTestSuiteStart(const testing::TestSuite &suite) override { 72 | suite_span = sentry_span_start_child( 73 | main_span, 74 | "test.suite", 75 | suite.name() 76 | ); 77 | TestEventListener::OnTestSuiteStart(suite); 78 | } 79 | 80 | void OnTestCaseStart(const testing::TestCase &aCase) override { 81 | TestEventListener::OnTestCaseStart(aCase); 82 | } 83 | 84 | void OnTestDisabled(const testing::TestInfo &info) override { 85 | TestEventListener::OnTestDisabled(info); 86 | } 87 | 88 | void OnTestPartResult(const testing::TestPartResult &test_part_result) override { 89 | sentry_set_span(test_span); 90 | auto val = sentry_value_new_breadcrumb("test", test_part_result.message()); 91 | sentry_add_breadcrumb(val); 92 | if (test_part_result.failed()) { 93 | auto ev = sentry_value_new_event(); 94 | auto exc = sentry_value_new_exception("GoogleTest", test_part_result.message()); 95 | sentry_value_set_stacktrace(exc, nullptr, 0); 96 | sentry_event_add_exception(ev, exc); 97 | sentry_capture_event(ev); 98 | } 99 | } 100 | 101 | void OnTestSuiteEnd(const testing::TestSuite &suite) override { 102 | TestEventListener::OnTestSuiteEnd(suite); 103 | if (suite.failed_test_count() > 0) { 104 | sentry_span_set_status(suite_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 105 | } 106 | sentry_span_finish(suite_span); // Mark the span as finished 107 | } 108 | 109 | void OnTestCaseEnd(const testing::TestCase &aCase) override { 110 | TestEventListener::OnTestCaseEnd(aCase); 111 | } 112 | 113 | void OnEnvironmentsTearDownStart(const testing::UnitTest &unit_test) override { 114 | 115 | } 116 | 117 | void OnEnvironmentsTearDownEnd(const testing::UnitTest &unit_test) override { 118 | 119 | } 120 | 121 | void OnTestIterationEnd(const testing::UnitTest &unit_test, int iteration) override { 122 | if (unit_test.failed_test_count() > 0) { 123 | sentry_span_set_status(main_span, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 124 | sentry_transaction_set_status(tx, sentry_span_status_t::SENTRY_SPAN_STATUS_INTERNAL_ERROR); 125 | } 126 | sentry_span_finish(main_span); // Mark the span as finished 127 | sentry_transaction_finish(tx); 128 | } 129 | }; 130 | #endif 131 | 132 | int main(int argc, char ** argv) { 133 | std::string progname(argv[0]); 134 | auto slash = progname.find_last_of("/\\"); 135 | if (slash != std::string::npos) { 136 | progname = progname.substr(slash + 1); 137 | } 138 | ::testing::InitGoogleTest(&argc, argv); 139 | auto & listeners = ::testing::UnitTest::GetInstance()->listeners(); 140 | #ifdef DWD_GTEST_SENTRY 141 | listeners.Append(new EventListener(progname)); 142 | #endif 143 | auto ret = RUN_ALL_TESTS(); 144 | return ret; 145 | } 146 | -------------------------------------------------------------------------------- /include/flxml/wrappers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 10/07/2024. 3 | // 4 | 5 | #ifndef RAPIDXML_RAPIDXML_WRAPPERS_HPP 6 | #define RAPIDXML_RAPIDXML_WRAPPERS_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace flxml { 13 | // Most of rapidxml was written to use a NUL-terminated Ch * for parsing. 14 | // This utility struct wraps a buffer to provide something that 15 | // looks mostly like a pointer, deferencing to NUL when it hits the end. 16 | // It's also a forward_iterator, so it'll work with the rage type constructors for string{_view} etc. 17 | template 18 | struct buffer_ptr { 19 | // Iterator magic typedefs 20 | using iterator_category = std::contiguous_iterator_tag; 21 | using difference_type = T::difference_type; 22 | using value_type = T::value_type; 23 | using pointer = T::const_pointer; 24 | using reference = T::const_reference; 25 | 26 | using real_it = T::const_iterator; 27 | real_it it; 28 | real_it end_it; 29 | static constexpr value_type end_char = value_type(0); 30 | 31 | explicit buffer_ptr(T const & buf) : it(buf.cbegin()), end_it(buf.cend()) {} 32 | buffer_ptr(buffer_ptr const & other) : it(other.it), end_it(other.end_it) {} 33 | buffer_ptr() = default; 34 | buffer_ptr & operator = (buffer_ptr const & other) { 35 | it = other.it; 36 | return *this; 37 | } 38 | reference validated_it(typename T::const_iterator const &it) const { 39 | if (it == end_it) return end_char; 40 | return *it; 41 | } 42 | reference operator[](int i) const { 43 | real_it it2 = it + i; 44 | if (it2 >= end_it) return end_char; 45 | return *it2; 46 | } 47 | pointer operator -> () const { 48 | if (it >= end_it) return &end_char; 49 | return &*it; 50 | } 51 | 52 | auto operator <=> (buffer_ptr other) const { 53 | return it <=> other.it; 54 | } 55 | auto operator < (buffer_ptr other) const { 56 | return it < other.it; 57 | } 58 | auto operator > (buffer_ptr other) const { 59 | return it > other.it; 60 | } 61 | auto operator >= (buffer_ptr other) const { 62 | return it >= other.it; 63 | } 64 | auto operator <= (buffer_ptr other) const { 65 | return it <= other.it; 66 | } 67 | 68 | buffer_ptr & operator ++() { 69 | ++it; 70 | return *this; 71 | } 72 | buffer_ptr operator ++(int) { 73 | auto old = *this; 74 | ++it; 75 | return old; 76 | } 77 | 78 | buffer_ptr & operator --() { 79 | --it; 80 | return *this; 81 | } 82 | buffer_ptr operator --(int) { 83 | auto old = *this; 84 | --it; 85 | return old; 86 | } 87 | 88 | reference operator *() const { 89 | return validated_it(it); 90 | } 91 | 92 | bool operator == (buffer_ptr const & other) const { 93 | return it == other.it; 94 | } 95 | 96 | auto operator + (difference_type n) const { 97 | buffer_ptr other(*this); 98 | other.it += n; 99 | return other; 100 | } 101 | buffer_ptr & operator += (difference_type i) { 102 | it += i; 103 | return *this; 104 | } 105 | 106 | auto operator - (difference_type n) const { 107 | buffer_ptr other(*this); 108 | other.it -= n; 109 | return other; 110 | } 111 | buffer_ptr & operator -= (difference_type i) { 112 | it -= i; 113 | return *this; 114 | } 115 | 116 | difference_type operator - (buffer_ptr const & other) const { 117 | return it - other.it; 118 | } 119 | 120 | pointer ptr() { 121 | return &*it; 122 | } 123 | }; 124 | 125 | template 126 | static auto operator + (int n, buffer_ptr it) { 127 | it.it += n; 128 | return it; 129 | } 130 | 131 | class no_such_node : std::runtime_error { 132 | public: 133 | no_such_node() : std::runtime_error("No such node") {} 134 | }; 135 | 136 | template 137 | class optional_ptr { 138 | T * m_ptr; 139 | 140 | void assert_value() const { 141 | if (m_ptr == nullptr) { 142 | throw no_such_node(); 143 | } 144 | } 145 | public: 146 | optional_ptr(std::nullptr_t) : m_ptr(nullptr) {} 147 | optional_ptr() : m_ptr(nullptr) {} 148 | optional_ptr(T * ptr) : m_ptr(ptr) {} 149 | 150 | bool has_value() const { 151 | return m_ptr != nullptr; 152 | } 153 | 154 | T & value() { 155 | assert_value(); 156 | return *m_ptr; 157 | } 158 | T * get() { 159 | assert_value(); 160 | return m_ptr; 161 | } 162 | T * operator -> () { 163 | return get(); 164 | } 165 | T & operator * () { 166 | return value(); 167 | } 168 | T * ptr_unsafe() { 169 | return m_ptr; 170 | } 171 | 172 | T const & value() const { 173 | assert_value(); 174 | return *m_ptr; 175 | } 176 | T const * get() const { 177 | assert_value(); 178 | return m_ptr; 179 | } 180 | T const * operator -> () const { 181 | return get(); 182 | } 183 | T const & operator * () const { 184 | return value(); 185 | } 186 | T const * ptr_unsafe() const { 187 | return m_ptr; 188 | } 189 | 190 | bool operator ! () const { 191 | return m_ptr == nullptr; 192 | } 193 | operator bool() const { 194 | return m_ptr != nullptr; 195 | } 196 | 197 | bool operator == (T * t) const { 198 | return m_ptr == t; 199 | } 200 | bool operator == (optional_ptr const & t) const { 201 | return m_ptr == t.m_ptr; 202 | } 203 | }; 204 | } 205 | 206 | #endif //RAPIDXML_RAPIDXML_WRAPPERS_HPP 207 | -------------------------------------------------------------------------------- /test/src/xpath.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 29/07/2024. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | TEST(XPath, parse) { 9 | std::string xpath_string = "//"; 10 | std::string_view sv{xpath_string}; 11 | auto xp = flxml::xpath<>::parse(sv); 12 | EXPECT_EQ(sv.length(), 0); 13 | EXPECT_NE(xp.get(), nullptr); 14 | EXPECT_EQ(xp->chain().size(), 1); 15 | } 16 | 17 | TEST(XPath, parse2) { 18 | std::string xpath_string = "//child"; 19 | std::string_view sv{xpath_string}; 20 | auto xp = flxml::xpath<>::parse(sv); 21 | EXPECT_EQ(sv.length(), 0); 22 | EXPECT_NE(xp.get(), nullptr); 23 | EXPECT_EQ(xp->chain().size(), 2); 24 | } 25 | 26 | TEST(XPath, parse1) { 27 | std::string xpath_string = "/child"; 28 | std::string_view sv{xpath_string}; 29 | auto xp = flxml::xpath<>::parse(sv); 30 | EXPECT_EQ(sv.length(), 0); 31 | EXPECT_NE(xp.get(), nullptr); 32 | EXPECT_EQ(xp->chain().size(), 2); 33 | } 34 | 35 | TEST(XPath, parse3) { 36 | std::string xpath_string = "//child[another/element]/something"; 37 | std::string_view sv{xpath_string}; 38 | auto xp = flxml::xpath<>::parse(sv); 39 | EXPECT_EQ(sv.length(), 0); 40 | EXPECT_NE(xp.get(), nullptr); 41 | EXPECT_EQ(xp->chain().size(), 4); 42 | ASSERT_EQ(xp->chain()[1]->contexts().size(), 1); 43 | EXPECT_EQ(xp->chain()[1]->contexts().begin()->get()->chain().size(), 4); 44 | } 45 | 46 | TEST(XPath, parse4) { 47 | std::string xpath_string = ""; 48 | std::string_view sv{xpath_string}; 49 | EXPECT_THROW( 50 | flxml::xpath<>::parse(sv), 51 | std::runtime_error 52 | ); 53 | } 54 | 55 | 56 | TEST(XPath, parse_attr) { 57 | std::string xpath_string = "//child[@foo='bar']/something"; 58 | std::string_view sv{xpath_string}; 59 | auto xp = flxml::xpath<>::parse(sv); 60 | EXPECT_EQ(sv.length(), 0); 61 | EXPECT_NE(xp.get(), nullptr); 62 | EXPECT_EQ(xp->chain().size(), 4); 63 | ASSERT_EQ(xp->chain()[1]->contexts().size(), 1); 64 | EXPECT_EQ(xp->chain()[1]->contexts().begin()->get()->chain().size(), 1); 65 | } 66 | 67 | TEST(XPath, parse_text) { 68 | std::string xpath_string = "//child[text()='bar']/something"; 69 | std::string_view sv{xpath_string}; 70 | auto xp = flxml::xpath<>::parse(sv); 71 | EXPECT_EQ(sv.length(), 0); 72 | EXPECT_NE(xp.get(), nullptr); 73 | EXPECT_EQ(xp->chain().size(), 4); 74 | ASSERT_EQ(xp->chain()[1]->contexts().size(), 1); 75 | EXPECT_EQ(xp->chain()[1]->contexts().begin()->get()->chain().size(), 1); 76 | } 77 | 78 | TEST(XPathFirst, simple_all) { 79 | flxml::xml_document<> doc; 80 | doc.parse(""); 81 | std::string xpath = "//"; 82 | std::string_view sv{xpath}; 83 | auto xp = flxml::xpath<>::parse(sv); 84 | auto r = xp->first(doc); 85 | ASSERT_TRUE(r); 86 | EXPECT_EQ(r->type(), flxml::node_type::node_document); 87 | } 88 | 89 | TEST(XPathFirst, simple_any) { 90 | flxml::xml_document<> doc; 91 | doc.parse(""); 92 | std::string xpath = "//child"; 93 | std::string_view sv{xpath}; 94 | auto xp = flxml::xpath<>::parse(sv); 95 | auto r = xp->first(doc); 96 | ASSERT_TRUE(r); 97 | EXPECT_EQ(r->name(), "child"); 98 | } 99 | 100 | TEST(XPathFirst, simple_sub) { 101 | flxml::xml_document<> doc; 102 | doc.parse(""); 103 | std::string xpath = "//[child]"; 104 | std::string_view sv{xpath}; 105 | auto xp = flxml::xpath<>::parse(sv); 106 | auto r = xp->first(doc); 107 | ASSERT_TRUE(r); 108 | EXPECT_EQ(r->name(), "simple"); 109 | } 110 | 111 | TEST(XPathFirst, simple_attr) { 112 | flxml::xml_document<> doc; 113 | doc.parse("foobar"); 114 | std::string xpath = "//child[@attr='val2']"; 115 | std::string_view sv{xpath}; 116 | auto xp = flxml::xpath<>::parse(sv); 117 | auto r = xp->first(doc); 118 | ASSERT_TRUE(r); 119 | EXPECT_EQ(r->name(), "child"); 120 | EXPECT_EQ(r->value(), "bar"); 121 | } 122 | 123 | TEST(XPathFirst, simple_text) { 124 | flxml::xml_document<> doc; 125 | doc.parse("foobar"); 126 | auto xp = flxml::xpath<>::parse("//child[text()='bar']"); 127 | auto r = xp->first(doc); 128 | ASSERT_TRUE(r); 129 | EXPECT_EQ(r->name(), "child"); 130 | EXPECT_EQ(r->value(), "bar"); 131 | } 132 | 133 | TEST(XPathNS, simple_text) { 134 | flxml::xml_document<> doc; 135 | doc.parse("foobar"); 136 | auto xp = flxml::xpath<>::parse("//child[text()='bar']"); 137 | auto r = xp->first(doc); 138 | ASSERT_TRUE(r); 139 | EXPECT_EQ(r->name(), "child"); 140 | EXPECT_EQ(r->value(), "bar"); 141 | } 142 | 143 | TEST(XPathNS, xmlns_text) { 144 | flxml::xml_document<> doc; 145 | doc.parse("foobar"); 146 | std::map xmlns = { 147 | {"x1", "p2"}, 148 | {"x2", "p1"} 149 | }; 150 | auto xp = flxml::xpath<>::parse(xmlns,"//x1:child[text()='bar']"); 151 | auto r = xp->first(doc); 152 | ASSERT_TRUE(r); 153 | EXPECT_EQ(r->name(), "child"); 154 | EXPECT_EQ(r->value(), "bar"); 155 | } 156 | 157 | TEST(XPathNS, xmlns_both) { 158 | flxml::xml_document<> doc; 159 | doc.parse("foobar"); 160 | std::map xmlns = { 161 | {"x1", "p2"}, 162 | {"x2", "p1"} 163 | }; 164 | auto xp = flxml::xpath<>::parse(xmlns,"//x1:child[text()='bar'][@attr='val2']"); 165 | auto r = xp->first(doc); 166 | ASSERT_TRUE(r); 167 | EXPECT_EQ(r->name(), "child"); 168 | EXPECT_EQ(r->value(), "bar"); 169 | } 170 | 171 | TEST(XPathNS, xmlns_text_miss) { 172 | flxml::xml_document<> doc; 173 | doc.parse("foobar"); 174 | std::map xmlns = { 175 | {"x1", "p2"}, 176 | {"x2", "p1"} 177 | }; 178 | auto xp = flxml::xpath<>::parse(xmlns,"//x2:child[text()='bar']"); 179 | auto r = xp->first(doc); 180 | ASSERT_FALSE(r); 181 | } 182 | -------------------------------------------------------------------------------- /test/src/round-trips.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 04/07/2024. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace { 10 | auto print(flxml::xml_document<> & doc) { 11 | std::string output; 12 | flxml::print(std::back_inserter(output), doc, flxml::print_no_indenting); 13 | return output; 14 | } 15 | } 16 | 17 | TEST(RoundTrip, Simple) { 18 | const char input[] = ""; 19 | std::vector buffer{input, input + sizeof(input)}; 20 | flxml::xml_document<> doc; 21 | doc.parse(buffer.data()); 22 | auto output = print(doc); 23 | // Have we parsed correctly? 24 | EXPECT_EQ(input, output); 25 | // Have we mutated the underlying buffer? 26 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 27 | } 28 | 29 | TEST(RoundTrip, SimpleMod) { 30 | const char input[] = ""; 31 | std::vector buffer{input, input + sizeof(input)}; 32 | flxml::xml_document<> doc; 33 | doc.parse(buffer.data()); 34 | auto output = print(doc); 35 | // Have we parsed correctly? 36 | EXPECT_EQ(input, output); 37 | // Have we mutated the underlying buffer? 38 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 39 | auto that = doc.first_node()->first_node(); 40 | doc.first_node()->remove_node(that); 41 | auto output2 = print(doc); 42 | EXPECT_EQ(output2, ""); 43 | std::string xmlns = "that"; 44 | std::string name = "this"; 45 | auto check = doc.first_node()->append_element(name, "the other"); 46 | doc.first_node()->append_element(name, "another'"); 47 | EXPECT_EQ(name, "this"); 48 | EXPECT_EQ(check->name(), name); 49 | EXPECT_EQ(check->name().data(), name.data()); 50 | doc.first_node()->append_element("odd", "the other"); 51 | doc.first_node()->append_element({xmlns, name}, "the other"); 52 | EXPECT_EQ(name, "this"); 53 | EXPECT_EQ(check->name(), name); 54 | EXPECT_EQ(check->name().data(), name.data()); 55 | doc.first_node()->append_element({"this", "that"}, "the other"); 56 | doc.first_node()->append_element(name, "last time"); 57 | EXPECT_EQ(name, "this"); 58 | EXPECT_EQ(check->name(), name); 59 | EXPECT_EQ(check->name().data(), name.data()); 60 | auto output3 = print(doc); 61 | EXPECT_EQ(name, "this"); 62 | EXPECT_EQ(check->name(), name); 63 | EXPECT_EQ(check->name().data(), name.data()); 64 | EXPECT_EQ(output3, "the otheranother'the otherthe otherthe otherlast time"); 65 | flxml::xml_document<> doc2; 66 | doc2.clone_node(doc.first_node(), true); 67 | auto output4 = print(doc); 68 | EXPECT_EQ(output3, output4); 69 | } 70 | 71 | TEST(RoundTrip, SimpleApos) { 72 | const char input[] = ""; 73 | std::vector buffer{input, input + sizeof(input)}; 74 | flxml::xml_document<> doc; 75 | doc.parse(buffer.data()); 76 | auto output = print(doc); 77 | // Have we parsed correctly? 78 | flxml::xml_document<> doc2; 79 | for (auto & child : flxml::children(doc)) { 80 | doc2.append_node(doc2.clone_node(&child, true)); 81 | } 82 | EXPECT_EQ(input, print(doc2)); 83 | EXPECT_EQ(input, output); 84 | // Have we mutated the underlying buffer? 85 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 86 | } 87 | 88 | TEST(RoundTrip, SimpleApos2) { 89 | const char input[] = ""; 90 | const char expected[] = ""; 91 | std::vector buffer{input, input + sizeof(input)}; 92 | flxml::xml_document<> doc; 93 | doc.parse(buffer.data()); 94 | auto output = print(doc); 95 | EXPECT_EQ(doc.first_node()->first_attribute()->value(), "'"); 96 | // Have we parsed correctly? 97 | flxml::xml_document<> doc2; 98 | for (auto & child : flxml::children(doc)) { 99 | doc2.append_node(doc2.clone_node(&child, true)); 100 | } 101 | EXPECT_EQ(expected, print(doc2)); 102 | EXPECT_EQ(expected, output); 103 | // Have we mutated the underlying buffer? 104 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 105 | } 106 | 107 | TEST(RoundTrip, SimpleLtBody) { 108 | const char input[] = "<"; 109 | const char expected[] = "<"; 110 | std::vector buffer{input, input + sizeof(input)}; 111 | flxml::xml_document<> doc; 112 | doc.parse(buffer.data()); 113 | auto output = print(doc); 114 | EXPECT_EQ(doc.first_node()->value(), "<"); 115 | EXPECT_EQ(doc.first_node()->first_attribute()->value(), "'"); 116 | // Have we parsed correctly? 117 | flxml::xml_document<> doc2; 118 | for (auto & child : flxml::children(doc)) { 119 | doc2.append_node(doc2.clone_node(&child, true)); 120 | } 121 | EXPECT_EQ(expected, print(doc2)); 122 | EXPECT_EQ(expected, output); 123 | // Have we mutated the underlying buffer? 124 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 125 | } 126 | 127 | TEST(RoundTrip, MutateBody) { 128 | const char input[] = "<"; 129 | const char expected[] = "<"; 130 | const char expected2[] = "new value"; 131 | std::vector buffer{input, input + sizeof(input)}; 132 | flxml::xml_document<> doc; 133 | doc.parse(buffer.data()); 134 | auto output = print(doc); 135 | EXPECT_EQ(expected, output); 136 | // Have we mutated the underlying buffer? 137 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 138 | doc.first_node()->value("new value"); 139 | EXPECT_EQ(doc.first_node()->value_raw(), ""); 140 | EXPECT_EQ(doc.first_node()->value(), "new value"); 141 | EXPECT_EQ(expected2, print(doc)); 142 | } 143 | 144 | TEST(RoundTrip, Everything) { 145 | const char input[] = ""; 146 | const char expected[] = ""; 147 | std::vector buffer{input, input + sizeof(input)}; 148 | flxml::xml_document<> doc; 149 | doc.parse(buffer.data()); 150 | auto output = print(doc); 151 | flxml::xml_document<> doc2; 152 | for (auto & child : flxml::children(doc)) { 153 | doc2.append_node(doc2.clone_node(&child, true)); 154 | } 155 | EXPECT_EQ(expected, print(doc2)); 156 | // Have we parsed correctly? 157 | EXPECT_EQ(expected, output); 158 | // Have we mutated the underlying buffer? 159 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size() - 1)); 160 | } 161 | 162 | TEST(RoundTrip, EverythingStream) { 163 | const char input[] = ""; 164 | const char expected[] = "\n\n\n\t\n\n\n"; 165 | std::vector buffer{input, input + sizeof(input) - 1}; 166 | flxml::xml_document<> doc; 167 | doc.parse(buffer); 168 | std::stringstream ss1; 169 | ss1 << doc; 170 | auto output = ss1.str(); 171 | flxml::xml_document<> doc2; 172 | for (auto & child : doc.children()) { 173 | doc2.append_node(doc2.clone_node(&child, true)); 174 | } 175 | std::stringstream ss2; 176 | ss2 << doc2; 177 | EXPECT_EQ(expected, ss2.str()); 178 | // Have we parsed correctly? 179 | EXPECT_EQ(expected, output); 180 | // Have we mutated the underlying buffer? 181 | EXPECT_EQ(input, std::string(buffer.data(), buffer.size())); 182 | } 183 | -------------------------------------------------------------------------------- /test/src/parse-simple.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 1/13/24. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | TEST(Parser, SingleElement) { 9 | char doc_text[] = ""; 10 | flxml::xml_document<> doc; 11 | doc.parse<0>(doc_text); 12 | 13 | auto node = doc.first_node(); 14 | EXPECT_NE(nullptr, node); 15 | EXPECT_FALSE(node->name().empty()); 16 | EXPECT_EQ("single-element", node->name()); 17 | doc.validate(); 18 | } 19 | 20 | TEST(Parser, DefaultElementNS) { 21 | char doc_text[] = ""; 22 | flxml::xml_document<> doc; 23 | doc.parse(doc_text); 24 | 25 | auto node = doc.first_node(); 26 | EXPECT_NE(nullptr, node); 27 | EXPECT_FALSE(node->name().empty()); 28 | EXPECT_EQ("element", node->name()); 29 | EXPECT_EQ(node->xmlns(), "this"); 30 | auto child = node->first_node(); 31 | EXPECT_EQ(child->name(), "child"); 32 | EXPECT_EQ(child->xmlns(), "this"); 33 | doc.validate(); 34 | auto no_node = child->next_sibling(); 35 | EXPECT_THROW(no_node->xmlns(), flxml::no_such_node); 36 | } 37 | 38 | TEST(Parser, UnboundPrefix) { 39 | flxml::xml_document<> doc; 40 | char doc_text[] = ""; 41 | doc.parse<0>(doc_text); 42 | 43 | auto node = doc.first_node(); 44 | EXPECT_EQ("single-element", node->name()); 45 | EXPECT_THROW( 46 | doc.validate(), 47 | flxml::element_xmlns_unbound 48 | ); 49 | } 50 | 51 | TEST(Parser, DuplicateAttribute) { 52 | flxml::xml_document<> doc; 53 | char doc_text[] = ""; 54 | doc.parse<0>(doc_text); 55 | 56 | auto node = doc.first_node(); 57 | EXPECT_EQ("single-element", node->name()); 58 | EXPECT_THROW( 59 | doc.validate(), 60 | flxml::duplicate_attribute 61 | ); 62 | } 63 | 64 | TEST(Parser, UnboundAttrPrefix) { 65 | flxml::xml_document<> doc; 66 | char doc_text[] = ""; 67 | doc.parse<0>(doc_text); 68 | 69 | auto node = doc.first_node(); 70 | EXPECT_EQ("single-element", node->name()); 71 | auto attr = node->first_attribute(); 72 | EXPECT_THROW( 73 | doc.validate(), 74 | flxml::attr_xmlns_unbound 75 | ); 76 | EXPECT_THROW( 77 | attr->xmlns(), 78 | flxml::attr_xmlns_unbound 79 | ); 80 | } 81 | 82 | 83 | TEST(Parser, DuplicateAttrPrefix) { 84 | flxml::xml_document<> doc; 85 | char doc_text[] = ""; 86 | doc.parse<0>(doc_text); 87 | 88 | auto node = doc.first_node(); 89 | assert(std::string("single-element") == node->name()); 90 | EXPECT_THROW( 91 | doc.validate(), 92 | flxml::duplicate_attribute 93 | ); 94 | } 95 | 96 | 97 | TEST(Parser, Xmlns) { 98 | flxml::xml_document<> doc; 99 | char doc_text[] = ""; 100 | doc.parse<0>(doc_text); 101 | 102 | auto node = doc.first_node(); 103 | EXPECT_EQ("single", node->name()); 104 | EXPECT_EQ("pfx", node->prefix()); 105 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 106 | doc.validate(); 107 | } 108 | 109 | TEST(Parser, ChildXmlns) { 110 | flxml::xml_document<> doc; 111 | char doc_text[] = ""; 112 | doc.parse<0>(doc_text); 113 | 114 | auto node = doc.first_node(); 115 | EXPECT_EQ("single", node->name()); 116 | auto child = node->first_node({}, "urn:potato"); 117 | ASSERT_NE(nullptr, child); 118 | EXPECT_EQ("child", child->name()); 119 | EXPECT_EQ("urn:potato", child->xmlns()); 120 | child = node->first_node(); 121 | EXPECT_EQ("firstchild", child->name()); 122 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 123 | child = child->next_sibling(); 124 | EXPECT_EQ("child", child->name()); 125 | EXPECT_EQ("urn:potato", child->xmlns()); 126 | child = child->next_sibling(); 127 | EXPECT_EQ("child", child->name()); 128 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 129 | child = node->first_node("child"); 130 | EXPECT_EQ("child", child->name()); 131 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 132 | child = node->first_node()->next_sibling({}, "urn:xmpp:example"); 133 | EXPECT_EQ("child", child->name()); 134 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 135 | child = node->first_node()->next_sibling("child"); 136 | // This will default to the same namespace as the first child ndoe. 137 | EXPECT_EQ("child", child->name()); 138 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 139 | auto attr = node->first_attribute(); 140 | EXPECT_EQ(attr->xmlns(), "http://www.w3.org/2000/xmlns/"); 141 | EXPECT_EQ(attr->local_name(), "pfx"); 142 | EXPECT_EQ(attr->name(), "xmlns:pfx"); 143 | EXPECT_EQ(attr->value(), "urn:xmpp:example"); 144 | attr = attr->next_attribute(); 145 | EXPECT_EQ(attr->xmlns(), ""); 146 | EXPECT_EQ(attr->local_name(), "foo"); 147 | EXPECT_EQ(attr->name(), "foo"); 148 | EXPECT_EQ(attr->value(), "bar"); 149 | doc.validate(); 150 | } 151 | 152 | TEST(Parser, HandleEOF){ 153 | flxml::xml_document<> doc; 154 | char doc_text[] = ""; 155 | EXPECT_THROW( 156 | doc.parse<0>(doc_text), 157 | flxml::eof_error 158 | ); 159 | } 160 | 161 | TEST(ParseOptions, Fastest) { 162 | flxml::xml_document<> doc; 163 | char doc_text[] = ""; 164 | doc.parse(doc_text); 165 | 166 | auto node = doc.first_node(); 167 | EXPECT_EQ("single", node->name()); 168 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 169 | auto child = node->first_node({}, "urn:potato"); 170 | ASSERT_NE(nullptr, child); 171 | EXPECT_EQ("child", child->name()); 172 | EXPECT_EQ("urn:potato", child->xmlns()); 173 | child = node->first_node(); 174 | EXPECT_EQ("firstchild", child->name()); 175 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 176 | child = node->first_node("child"); 177 | EXPECT_EQ("child", child->name()); 178 | EXPECT_EQ("urn:xmpp:example", child->xmlns()); 179 | doc.validate(); 180 | } 181 | 182 | TEST(ParseOptions, OpenOnly) { 183 | flxml::xml_document<> doc; 184 | char doc_text[] = ""; 185 | doc.parse(doc_text); 186 | 187 | auto node = doc.first_node(); 188 | EXPECT_EQ("single", node->name()); 189 | EXPECT_EQ("pfx", node->prefix()); 190 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 191 | doc.validate(); 192 | } 193 | 194 | TEST(ParseOptions, ParseOne) { 195 | flxml::xml_document<> doc; 196 | char doc_text[] = "Hello!"; 197 | const char * text = doc.parse(doc_text); 198 | 199 | { 200 | auto node = doc.first_node(); 201 | EXPECT_EQ("single", node->name()); 202 | EXPECT_EQ("pfx", node->prefix()); 203 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 204 | EXPECT_STREQ( 205 | "Hello!", 206 | text); 207 | } 208 | doc.validate(); 209 | unsigned counter = 0; 210 | while (*text) { 211 | flxml::xml_document<> subdoc; 212 | text = subdoc.parse(text, &doc); 213 | auto node = subdoc.first_node(); 214 | ASSERT_NE(nullptr, node); 215 | switch(++counter) { 216 | case 1: 217 | EXPECT_EQ("features", node->name()); 218 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 219 | break; 220 | case 2: 221 | EXPECT_EQ("message", node->name()); 222 | EXPECT_EQ("jabber:client", node->xmlns()); 223 | break; 224 | default: 225 | FAIL(); 226 | } 227 | subdoc.validate(); 228 | } 229 | } 230 | 231 | TEST(ParseOptions, OpenOnlyFastest) { 232 | flxml::xml_document<> doc; 233 | char doc_text[] = "Hello!"; 234 | const char * text = doc.parse(doc_text); 235 | 236 | { 237 | auto node = doc.first_node(); 238 | EXPECT_EQ("single", node->name()); 239 | EXPECT_EQ("pfx", node->prefix()); 240 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 241 | EXPECT_STREQ( 242 | "Hello!", 243 | text); 244 | } 245 | doc.validate(); 246 | unsigned counter = 0; 247 | while (*text) { 248 | flxml::xml_document<> subdoc; 249 | text = subdoc.parse(text, &doc); 250 | auto node = subdoc.first_node(); 251 | ASSERT_NE(nullptr, node); 252 | switch(++counter) { 253 | case 1: 254 | EXPECT_EQ("features", node->name()); 255 | EXPECT_EQ("urn:xmpp:example", node->xmlns()); 256 | break; 257 | case 2: 258 | EXPECT_EQ("message", node->name()); 259 | EXPECT_EQ("jabber:client", node->xmlns()); 260 | break; 261 | default: 262 | FAIL(); 263 | } 264 | subdoc.validate(); 265 | } 266 | } 267 | 268 | TEST(Parser_Emoji, Single) { 269 | std::string foo{"'"}; 270 | flxml::xml_document<> doc; 271 | doc.parse(foo); 272 | EXPECT_EQ("'", doc.first_node()->value()); 273 | } 274 | 275 | TEST(Parser_Emoji, SingleUni) { 276 | std::string foo{"Ӓ"}; 277 | flxml::xml_document<> doc; 278 | doc.parse(foo); 279 | EXPECT_EQ("\xD3\x92", doc.first_node()->value()); 280 | } 281 | 282 | TEST(Parser_Emoji, SingleEmoji) { 283 | std::string foo{"😀"}; 284 | flxml::xml_document<> doc; 285 | doc.parse(foo); 286 | EXPECT_EQ("\xF0\x9F\x98\x80", doc.first_node()->value()); 287 | EXPECT_EQ(4, doc.first_node()->value().size()); 288 | } 289 | 290 | TEST(Parser_Emoji, SingleEmojiReuse) { 291 | std::string bar("Sir I bear a rhyme excelling in mystic verse and magic spelling 😀"); 292 | flxml::xml_document<> doc; 293 | flxml::xml_document<> parent_doc; 294 | parent_doc.parse(""); 295 | doc.parse(bar, &parent_doc); 296 | EXPECT_EQ("Sir I bear a rhyme excelling in mystic verse and magic spelling \xF0\x9F\x98\x80", doc.first_node()->value()); 297 | auto doc_a = doc.first_node()->document(); 298 | doc.first_node()->value(doc_a->allocate_string("Sausages are the loneliest fruit, and are but one of the strange things I have witnessed in my long and interesting life.")); 299 | EXPECT_EQ("Sausages are the loneliest fruit, and are but one of the strange things I have witnessed in my long and interesting life.", doc.first_node()->value()); 300 | bar = "😀"; 301 | doc.parse(bar, &parent_doc); 302 | EXPECT_EQ("\xF0\x9F\x98\x80", doc.first_node()->value()); 303 | EXPECT_EQ(4, doc.first_node()->value().size()); 304 | } 305 | 306 | -------------------------------------------------------------------------------- /include/flxml/iterators.h: -------------------------------------------------------------------------------- 1 | #ifndef RAPIDXML_ITERATORS_HPP_INCLUDED 2 | #define RAPIDXML_ITERATORS_HPP_INCLUDED 3 | 4 | // Copyright (C) 2006, 2009 Marcin Kalicinski 5 | // Version 1.13 6 | // Revision $DateTime: 2009/05/13 01:46:17 $ 7 | //! \file rapidxml_iterators.hpp This file contains rapidxml iterators 8 | 9 | #include 10 | 11 | namespace flxml 12 | { 13 | //! Iterator of child nodes of xml_node 14 | template 15 | class node_iterator 16 | { 17 | public: 18 | using value_type = xml_node; 19 | using reference = xml_node &; 20 | using pointer = xml_node *; 21 | using iterator_category = std::bidirectional_iterator_tag; 22 | using difference_type = long; 23 | 24 | node_iterator() 25 | : m_node() 26 | { 27 | } 28 | 29 | explicit node_iterator(xml_node const &node) 30 | : m_node(node.first_node()) 31 | { 32 | } 33 | 34 | node_iterator(node_iterator && other) noexcept : m_node(other.m_node) {} 35 | node_iterator(node_iterator const & other) : m_node(other.m_node) {} 36 | 37 | reference operator *() const 38 | { 39 | return const_cast(*m_node); 40 | } 41 | 42 | pointer operator->() const 43 | { 44 | return const_cast(m_node.get()); 45 | } 46 | 47 | node_iterator& operator++() 48 | { 49 | m_node = m_node->next_sibling(); 50 | return *this; 51 | } 52 | 53 | node_iterator operator++(int) 54 | { 55 | node_iterator tmp = *this; 56 | ++(*this); 57 | return tmp; 58 | } 59 | 60 | node_iterator& operator--() 61 | { 62 | m_node = m_node->previous_sibling(); 63 | return *this; 64 | } 65 | 66 | node_iterator operator--(int) 67 | { 68 | node_iterator tmp = *this; 69 | --(*this); 70 | return tmp; 71 | } 72 | 73 | bool operator == (const node_iterator& rhs) const 74 | { 75 | return m_node == rhs.m_node; 76 | } 77 | 78 | bool operator != (const node_iterator& rhs) const 79 | { 80 | return m_node != rhs.m_node; 81 | } 82 | 83 | node_iterator & operator = (node_iterator && other) noexcept { 84 | m_node = other.m_node; 85 | return *this; 86 | } 87 | 88 | node_iterator & operator = (node_iterator const & other) { 89 | m_node = other.m_node; 90 | return *this; 91 | } 92 | 93 | bool valid() 94 | { 95 | return m_node.has_value(); 96 | } 97 | 98 | private: 99 | 100 | optional_ptr> m_node; 101 | 102 | }; 103 | 104 | //! Iterator of child nodes of xml_node 105 | template 106 | class descendant_iterator 107 | { 108 | public: 109 | using value_type = xml_node; 110 | using reference = xml_node &; 111 | using pointer = xml_node *; 112 | using iterator_category = std::bidirectional_iterator_tag; 113 | using difference_type = long; 114 | 115 | descendant_iterator() 116 | : m_parent(), m_node() 117 | { 118 | } 119 | 120 | explicit descendant_iterator(xml_node::ptr node) 121 | : m_parent(node), m_node(node->first_node()) 122 | { 123 | } 124 | 125 | descendant_iterator(descendant_iterator && other) noexcept : m_parent(other.m_parent), m_node(other.m_node) {} 126 | descendant_iterator(descendant_iterator const & other) : m_parent(other.m_parent), m_node(other.m_node) {} 127 | 128 | reference operator *() const 129 | { 130 | return const_cast(*m_node); 131 | } 132 | 133 | pointer operator->() const 134 | { 135 | return const_cast(m_node.get()); 136 | } 137 | 138 | descendant_iterator& operator++() 139 | { 140 | if (m_node->first_node()) { 141 | m_node = m_node->first_node(); 142 | } else if (m_node->next_sibling()) { 143 | m_node = m_node->next_sibling(); 144 | } else { 145 | // Run out of children, so move upward until we can find a sibling. 146 | while (true) { 147 | m_node = m_node->parent(); 148 | if (m_node == m_parent) { 149 | m_node = nullptr; 150 | break; 151 | } 152 | if (m_node->next_sibling()) { 153 | m_node = m_node->next_sibling(); 154 | break; 155 | } 156 | } 157 | } 158 | return *this; 159 | } 160 | 161 | descendant_iterator operator++(int) 162 | { 163 | node_iterator tmp = *this; 164 | ++(*this); 165 | return tmp; 166 | } 167 | 168 | descendant_iterator& operator--() 169 | { 170 | if (!m_node->previous_sibling()) { 171 | m_node = m_node->parent(); 172 | if (m_node == m_parent) { 173 | m_node = nullptr; 174 | } 175 | } else { 176 | m_node = m_node->previous_sibling(); 177 | while (m_node->last_node()) { 178 | m_node = m_node->last_node(); 179 | } 180 | } 181 | return *this; 182 | } 183 | 184 | descendant_iterator operator--(int) 185 | { 186 | node_iterator tmp = *this; 187 | --(*this); 188 | return tmp; 189 | } 190 | 191 | bool operator == (const descendant_iterator& rhs) const 192 | { 193 | return m_node == rhs.m_node; 194 | } 195 | 196 | bool operator != (const descendant_iterator& rhs) const 197 | { 198 | return m_node != rhs.m_node; 199 | } 200 | 201 | descendant_iterator & operator = (descendant_iterator && other) noexcept { 202 | m_parent = other.m_parent; 203 | m_node = other.m_node; 204 | return *this; 205 | } 206 | 207 | descendant_iterator & operator = (descendant_iterator const & other) { 208 | m_parent = other.m_parent; 209 | m_node = other.m_node; 210 | return *this; 211 | } 212 | 213 | bool valid() 214 | { 215 | return m_node.has_value(); 216 | } 217 | 218 | private: 219 | 220 | optional_ptr> m_parent; 221 | optional_ptr> m_node; 222 | }; 223 | 224 | //! Iterator of child attributes of xml_node 225 | template 226 | class attribute_iterator 227 | { 228 | 229 | public: 230 | 231 | using value_type = xml_attribute; 232 | using reference = xml_attribute &; 233 | using pointer = xml_attribute *; 234 | using iterator_category = std::bidirectional_iterator_tag; 235 | using difference_type = long; 236 | 237 | attribute_iterator() 238 | : m_attribute() 239 | { 240 | } 241 | 242 | explicit attribute_iterator(xml_node const &node) 243 | : m_attribute(node.first_attribute()) 244 | { 245 | } 246 | 247 | attribute_iterator(attribute_iterator && other) noexcept : m_attribute(other.m_attribute) {} 248 | attribute_iterator(attribute_iterator const & other) : m_attribute(other.m_attribute) {} 249 | 250 | reference operator *() const 251 | { 252 | return const_cast(*m_attribute); 253 | } 254 | 255 | pointer operator->() const 256 | { 257 | return const_cast(m_attribute.get()); 258 | } 259 | 260 | attribute_iterator& operator++() 261 | { 262 | m_attribute = m_attribute->next_attribute(); 263 | return *this; 264 | } 265 | 266 | attribute_iterator operator++(int) 267 | { 268 | attribute_iterator tmp = *this; 269 | ++*this; 270 | return tmp; 271 | } 272 | 273 | attribute_iterator& operator--() 274 | { 275 | m_attribute = m_attribute->previous_attribute(); 276 | return *this; 277 | } 278 | 279 | attribute_iterator operator--(int) 280 | { 281 | attribute_iterator tmp = *this; 282 | --*this; 283 | return tmp; 284 | } 285 | 286 | bool operator ==(const attribute_iterator &rhs) const 287 | { 288 | return m_attribute == rhs.m_attribute; 289 | } 290 | 291 | bool operator !=(const attribute_iterator &rhs) const 292 | { 293 | return m_attribute != rhs.m_attribute; 294 | } 295 | 296 | attribute_iterator & operator = (attribute_iterator && other) noexcept { 297 | m_attribute = other.m_attribute; 298 | return *this; 299 | } 300 | 301 | attribute_iterator & operator = (attribute_iterator const & other) { 302 | m_attribute = other.m_attribute; 303 | return *this; 304 | } 305 | 306 | private: 307 | 308 | optional_ptr> m_attribute; 309 | 310 | }; 311 | 312 | //! Container adaptor for child nodes 313 | template 314 | class children 315 | { 316 | xml_node const & m_node; 317 | public: 318 | explicit children(xml_node const & node) : m_node(node) {} 319 | explicit children(optional_ptr> const ptr) : m_node(ptr.value()) {} 320 | children(children && other) noexcept : m_node(other.m_node) {} 321 | children(children const & other) : m_node(other.m_node) {} 322 | 323 | using const_iterator = node_iterator; 324 | using iterator = node_iterator; 325 | 326 | iterator begin() { 327 | return iterator(m_node); 328 | } 329 | iterator end() { 330 | return {}; 331 | } 332 | const_iterator begin() const { 333 | return const_iterator(m_node); 334 | } 335 | const_iterator end() const { 336 | return {}; 337 | } 338 | }; 339 | 340 | //! Container adaptor for child nodes 341 | template 342 | class descendants 343 | { 344 | xml_node & m_node; 345 | public: 346 | explicit descendants(xml_node & node) : m_node(node) {} 347 | explicit descendants(optional_ptr> ptr) : m_node(ptr.value()) {} 348 | descendants(descendants && other) noexcept : m_node(other.m_node) {} 349 | descendants(descendants const & other) : m_node(other.m_node) {} 350 | 351 | using const_iterator = descendant_iterator; 352 | using iterator = descendant_iterator; 353 | 354 | iterator begin() { 355 | return iterator(&m_node); 356 | } 357 | iterator end() { 358 | return {}; 359 | } 360 | const_iterator begin() const { 361 | return const_iterator(&m_node); 362 | } 363 | const_iterator end() const { 364 | return {}; 365 | } 366 | }; 367 | 368 | //! Container adaptor for attributes 369 | template 370 | class attributes 371 | { 372 | xml_node const & m_node; 373 | public: 374 | explicit attributes(xml_node const & node) : m_node(node) {} 375 | explicit attributes(optional_ptr> ptr) : m_node(ptr.value()) {} 376 | 377 | using const_iterator = attribute_iterator; 378 | using iterator = attribute_iterator; 379 | 380 | iterator begin() { 381 | return iterator{m_node}; 382 | } 383 | iterator end() { 384 | return {}; 385 | } 386 | const_iterator begin() const { 387 | return const_iterator{m_node}; 388 | } 389 | const_iterator end() const { 390 | return {}; 391 | } 392 | }; 393 | } 394 | 395 | template 396 | inline constexpr bool std::ranges::enable_borrowed_range> = true; 397 | 398 | template 399 | inline constexpr bool std::ranges::enable_borrowed_range> = true; 400 | 401 | 402 | #endif 403 | -------------------------------------------------------------------------------- /include/flxml/predicates.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dave on 29/07/2024. 3 | // 4 | 5 | #ifndef RAPIDXML_RAPIDXML_PREDICATES_HPP 6 | #define RAPIDXML_RAPIDXML_PREDICATES_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace flxml { 14 | template class xpath; 15 | namespace internal { 16 | template 17 | class xpath_base; 18 | 19 | template 20 | class name : public flxml::internal::xpath_base { 21 | private: 22 | std::basic_string m_name; 23 | std::optional> m_xmlns; 24 | public: 25 | explicit name(std::basic_string_view n) 26 | : xpath_base(), m_name(n) {} 27 | 28 | explicit name(std::basic_string const & xmlns, std::basic_string_view n) 29 | : xpath_base(), m_name(n), m_xmlns(xmlns) {} 30 | 31 | bool do_match(const xml_node & t) override { 32 | if (m_xmlns.has_value() && t.xmlns() != m_xmlns.value()) return false; 33 | return (t.type() == node_type::node_element) && (t.name() == m_name || m_name == "*"); 34 | } 35 | }; 36 | 37 | template 38 | class value : public flxml::internal::xpath_base { 39 | private: 40 | std::basic_string m_value; 41 | public: 42 | explicit value(std::basic_string_view v) 43 | : xpath_base(), m_value(v) {} 44 | 45 | bool do_match(const xml_node & t) override { 46 | return (t.type() == node_type::node_element) && (t.value() == m_value); 47 | } 48 | }; 49 | 50 | template 51 | class xmlns : public flxml::internal::xpath_base { 52 | private: 53 | std::basic_string m_xmlns; 54 | public: 55 | explicit xmlns(std::basic_string_view v) 56 | : xpath_base(), m_xmlns(v) {} 57 | 58 | bool do_match(const xml_node & t) override { 59 | return (t.type() == node_type::node_element) && (t.xmlns() == m_xmlns); 60 | } 61 | }; 62 | 63 | template 64 | class attr : public flxml::internal::xpath_base { 65 | private: 66 | std::basic_string m_name; 67 | std::basic_string m_value; 68 | std::optional> m_xmlns; 69 | public: 70 | explicit attr(std::basic_string_view n, std::basic_string_view v) 71 | : xpath_base(), m_name(n), m_value(v) {} 72 | 73 | explicit attr(std::basic_string const & x, std::basic_string_view n, std::basic_string_view v) 74 | : xpath_base(), m_name(n), m_value(v), m_xmlns(x) {} 75 | 76 | bool do_match(const xml_node & t) override { 77 | if (t.type() != node_type::node_element) return false; 78 | for (auto const & attr : t.attributes()) { 79 | if (m_xmlns.has_value()) { 80 | if (m_name == "*" || attr.local_name() != m_name) continue; 81 | if (attr.xmlns() != m_xmlns.value()) continue; 82 | } else { 83 | if (m_name == "*" || attr.name() != m_name) continue; 84 | } 85 | return attr.value() == m_value; 86 | } 87 | return false; 88 | } 89 | }; 90 | 91 | template 92 | class root : public flxml::internal::xpath_base { 93 | public: 94 | root() = default; 95 | 96 | generator &> do_gather(xml_node & t) override { 97 | for (auto & x : t.children()) { 98 | co_yield x; 99 | } 100 | } 101 | 102 | bool do_match(const xml_node & t) override { 103 | return t.type() == node_type::node_document || t.type() == node_type::node_element; 104 | } 105 | }; 106 | 107 | template 108 | class any : public flxml::internal::xpath_base { 109 | public: 110 | any() = default; 111 | 112 | generator &> do_gather(xml_node & t) override { 113 | co_yield t; // self 114 | for (auto & x : t.descendants()) { 115 | co_yield x; 116 | } 117 | } 118 | 119 | bool do_match(const xml_node & t) override { 120 | return t.type() == node_type::node_document || t.type() == node_type::node_element; 121 | } 122 | }; 123 | 124 | template 125 | class xpath_base { 126 | private: 127 | std::list>> m_contexts; 128 | public: 129 | 130 | xpath_base() = default; 131 | 132 | virtual ~xpath_base() = default; 133 | 134 | virtual generator &> do_gather(xml_node & t) { 135 | co_yield t; 136 | } 137 | 138 | generator &> gather(xml_node & t) { 139 | for (auto & x : do_gather(t)) { 140 | if (match(x)) co_yield x; 141 | } 142 | } 143 | 144 | virtual bool do_match(const xml_node & t) = 0; 145 | 146 | bool match(xml_node & t) { 147 | if (!do_match(t)) { 148 | return false; 149 | } 150 | for(auto & context : m_contexts) { 151 | if (!context->first(t)) { 152 | return false; 153 | } 154 | } 155 | return true; 156 | } 157 | 158 | void context(std::unique_ptr> && xp) { 159 | m_contexts.emplace_back(std::move(xp)); 160 | } 161 | 162 | auto & contexts() const { 163 | return m_contexts; 164 | } 165 | }; 166 | 167 | std::map xmlns_empty = {}; 168 | } 169 | 170 | template 171 | class xpath : public internal::xpath_base { 172 | private: 173 | std::vector>> m_chain; 174 | std::map const & m_xmlns; 175 | 176 | public: 177 | bool do_match(const xml_node & t) override { 178 | return false; 179 | } 180 | 181 | auto const & chain() const { 182 | return m_chain; 183 | } 184 | std::string const & prefix_lookup(std::basic_string_view const & prefix) const { 185 | std::basic_string p{prefix}; 186 | auto it = m_xmlns.find(p); 187 | if (it != m_xmlns.end()) { 188 | return (*it).second; 189 | } 190 | throw std::runtime_error("XPath contains unknown prefix"); 191 | } 192 | 193 | static void parse_predicate(std::basic_string_view const &name, xpath &xp, bool inner) { 194 | using xml_doc = xml_document; 195 | if (name.starts_with('@')) { 196 | std::basic_string text = "(text); 208 | auto attr = doc.first_node()->first_attribute(); 209 | auto colon = attr->name().find(':'); 210 | if (colon != xml_attribute::view_type::npos) { 211 | auto const & uri = xp.prefix_lookup(attr->name().substr(0, colon)); 212 | xp.m_chain.push_back(std::make_unique>(uri, attr->local_name(), attr->value())); 213 | } else { 214 | xp.m_chain.push_back(std::make_unique>(star ? "*" : attr->name(), attr->value())); 215 | } 216 | } else if (name.starts_with("text()")) { 217 | // text match 218 | std::basic_string text = "(text); 223 | auto attr = doc.first_node()->first_attribute(); 224 | xp.m_chain.push_back(std::make_unique>(attr->value())); 225 | } else if (name.starts_with("namespace-uri()")) { 226 | // text match 227 | std::basic_string text = "(text); 232 | auto attr = doc.first_node()->first_attribute(); 233 | xp.m_chain.push_back(std::make_unique>(attr->value())); 234 | } else { 235 | if (xp.m_chain.empty() && inner) { 236 | xp.m_chain.push_back(std::make_unique>()); 237 | } 238 | auto colon = name.find(':'); 239 | if (colon != std::basic_string_view::npos) { 240 | auto const & uri = xp.prefix_lookup(name.substr(0, colon)); 241 | xp.m_chain.push_back(std::make_unique>(uri, name.substr(colon + 1))); 242 | } else { 243 | xp.m_chain.push_back(std::make_unique>(name)); 244 | } 245 | } 246 | } 247 | 248 | static bool parse_inner(std::map & xmlns, std::basic_string_view &view, xpath &xp, bool first=false, bool inner=false) { 249 | if (view.starts_with("//")) { 250 | xp.m_chain.push_back(std::make_unique>()); 251 | view.remove_prefix(2); 252 | } else if (view.starts_with('/')) { 253 | xp.m_chain.push_back(std::make_unique>()); 254 | view.remove_prefix(1); 255 | } else if (first && !inner) { 256 | xp.m_chain.push_back(std::make_unique>()); 257 | } 258 | for (typename std::basic_string_view::size_type i = 0; i != view.size(); ++i) { 259 | switch (view[i]) { 260 | case '/': 261 | case ']': 262 | if (i == 0) throw std::runtime_error("Empty name?"); 263 | case '[': 264 | if (i != 0) parse_predicate(view.substr(0, i), xp, inner); 265 | } 266 | switch (view[i]) { 267 | case ']': 268 | view.remove_prefix(i + 1); 269 | if (!inner) throw std::runtime_error("Unexpected ] in input"); 270 | return true; 271 | case '[': 272 | view.remove_prefix(i + 1); 273 | xp.m_chain[xp.m_chain.size() - 1]->context(parse_cont(xmlns, view)); 274 | return false; 275 | case '/': 276 | view.remove_prefix(i ); 277 | return false; 278 | } 279 | } 280 | if (!view.empty()) { 281 | parse_predicate(view, xp, inner); 282 | view.remove_prefix(view.length()); 283 | } 284 | return true; 285 | } 286 | 287 | static std::unique_ptr> parse_cont(std::map & xmlns, std::basic_string_view &view) { 288 | if (view.empty()) throw std::runtime_error("Context expression is empty"); 289 | auto xp = std::make_unique>(xmlns); 290 | if (!parse_inner(xmlns, view, *xp, true, true)) { 291 | while (!view.empty()) { 292 | if (parse_inner(xmlns, view, *xp, false, true)) break; 293 | } 294 | } 295 | return xp; 296 | } 297 | 298 | static std::unique_ptr> parse(std::map & xmlns, std::basic_string_view &view) { 299 | if (view.empty()) throw std::runtime_error("XPath expression is empty"); 300 | auto xp = std::make_unique>(xmlns); 301 | if (!parse_inner(xmlns, view, *xp, true, false)) { 302 | while (!view.empty()) { 303 | if (parse_inner(xmlns, view, *xp, false, false)) break; 304 | } 305 | } 306 | return xp; 307 | } 308 | static std::unique_ptr> parse(std::map & xmlns, std::basic_string_view const &view) { 309 | std::basic_string_view sv(view); 310 | return parse(xmlns, sv); 311 | } 312 | static std::unique_ptr> parse(std::map & xmlns, std::basic_string const &view) { 313 | std::basic_string_view sv(view); 314 | return parse(xmlns, sv); 315 | } 316 | static std::unique_ptr> parse(std::map & xmlns, const char * view) { 317 | std::basic_string_view sv(view); 318 | return parse(xmlns, sv); 319 | } 320 | static std::unique_ptr> parse(std::basic_string_view &sv) { 321 | return parse(internal::xmlns_empty, sv); 322 | } 323 | static std::unique_ptr> parse(std::basic_string const &view) { 324 | std::basic_string_view sv(view); 325 | return parse(internal::xmlns_empty, sv); 326 | } 327 | static std::unique_ptr> parse(std::basic_string_view const &view) { 328 | std::basic_string_view sv(view); 329 | return parse(internal::xmlns_empty, sv); 330 | } 331 | static std::unique_ptr> parse(const char * view) { 332 | std::basic_string_view sv(view); 333 | return parse(internal::xmlns_empty, sv); 334 | } 335 | 336 | explicit xpath(std::map & xmlns) : m_xmlns(xmlns) {} 337 | 338 | flxml::generator &> all(xml_node & current, unsigned int depth = 0) { 339 | if (depth >= m_chain.size()) throw std::logic_error("Depth exceeded"); 340 | auto & xp = m_chain[depth]; 341 | depth++; 342 | for (auto & r : xp->gather(current)) { 343 | if (depth >= m_chain.size()) { 344 | co_yield r; 345 | } else { 346 | for (auto & t : all(r, depth)) { 347 | co_yield t; 348 | } 349 | } 350 | } 351 | } 352 | 353 | xml_node::ptr first(xml_node & current) { 354 | for (auto &r: all(current)) { 355 | return &r; 356 | } 357 | return {}; 358 | } 359 | }; 360 | } 361 | 362 | #endif //RAPIDXML_RAPIDXML_PREDICATES_HPP 363 | -------------------------------------------------------------------------------- /include/flxml/print.h: -------------------------------------------------------------------------------- 1 | #ifndef RAPIDXML_PRINT_HPP_INCLUDED 2 | #define RAPIDXML_PRINT_HPP_INCLUDED 3 | 4 | // Copyright (C) 2006, 2009 Marcin Kalicinski 5 | // Version 1.13 6 | // Revision $DateTime: 2009/05/13 01:46:17 $ 7 | //! \file rapidxml_print.hpp This file contains rapidxml printer implementation 8 | 9 | #include 10 | 11 | // Only include streams if not disabled 12 | #ifndef FLXML_NO_STREAMS 13 | #include 14 | #include 15 | #endif 16 | 17 | namespace flxml 18 | { 19 | 20 | /////////////////////////////////////////////////////////////////////// 21 | // Printing flags 22 | 23 | const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function. 24 | 25 | /////////////////////////////////////////////////////////////////////// 26 | // Internal 27 | 28 | //! \cond internal 29 | namespace internal 30 | { 31 | 32 | /////////////////////////////////////////////////////////////////////////// 33 | // Internal character operations 34 | 35 | // Copy characters from given range to given output iterator 36 | template 37 | inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out) 38 | { 39 | while (begin != end) 40 | *out++ = *begin++; 41 | return out; 42 | } 43 | 44 | template 45 | inline OutIt copy_chars(std::basic_string_view const & sv, OutIt out) { 46 | return copy_chars(sv.data(), sv.data() + sv.size(), out); 47 | } 48 | 49 | // Copy characters from given range to given output iterator and expand 50 | // characters into references (< > ' " &) 51 | template 52 | inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out) 53 | { 54 | while (begin != end) 55 | { 56 | if (*begin == noexpand) 57 | { 58 | *out++ = *begin; // No expansion, copy character 59 | } 60 | else 61 | { 62 | switch (*begin) 63 | { 64 | case Ch('<'): 65 | *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';'); 66 | break; 67 | case Ch('>'): 68 | *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';'); 69 | break; 70 | case Ch('\''): 71 | *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';'); 72 | break; 73 | case Ch('"'): 74 | *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';'); 75 | break; 76 | case Ch('&'): 77 | *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';'); 78 | break; 79 | default: 80 | *out++ = *begin; // No expansion, copy character 81 | } 82 | } 83 | ++begin; // Step to next character 84 | } 85 | return out; 86 | } 87 | 88 | 89 | template 90 | inline OutIt copy_and_expand_chars(std::basic_string_view const & sv, Ch noexpand, OutIt out) { 91 | return copy_and_expand_chars(sv.data(), sv.data() + sv.size(), noexpand, out); 92 | } 93 | // Fill given output iterator with repetitions of the same character 94 | template 95 | inline OutIt fill_chars(OutIt out, int n, Ch ch) 96 | { 97 | for (int i = 0; i < n; ++i) 98 | *out++ = ch; 99 | return out; 100 | } 101 | 102 | // Find character 103 | template 104 | inline bool find_char(const Ch *begin, const Ch *end) 105 | { 106 | while (begin != end) 107 | if (*begin++ == ch) 108 | return true; 109 | return false; 110 | } 111 | 112 | /////////////////////////////////////////////////////////////////////////// 113 | // Internal printing operations 114 | 115 | // Print node 116 | template 117 | inline OutIt print_node(OutIt out, const optional_ptr> node, int flags, int indent); 118 | 119 | // Print children of the node 120 | template 121 | inline OutIt print_children(OutIt out, const optional_ptr> node, int flags, int indent) 122 | { 123 | for (auto child = node->first_node(); child; child = child->next_sibling()) 124 | out = print_node(out, child, flags, indent); 125 | return out; 126 | } 127 | 128 | // Print attributes of the node 129 | template 130 | inline OutIt print_attributes(OutIt out, const optional_ptr> node, int flags) 131 | { 132 | for (auto attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute()) 133 | { 134 | if (!(attribute->name().empty()) || attribute->value_raw().empty()) 135 | { 136 | // Print attribute name 137 | *out = Ch(' '), ++out; 138 | out = copy_chars(attribute->name(), out); 139 | *out = Ch('='), ++out; 140 | if (attribute->quote() && !attribute->value_decoded()) { 141 | // Shortcut here; just dump out the raw value. 142 | *out++ = attribute->quote(); 143 | out = copy_chars(attribute->value_raw(), out); 144 | **out++ = attribute->quote(); 145 | } else { 146 | // Print attribute value using appropriate quote type 147 | if (attribute->value().find('"') != std::basic_string_view::npos) { 148 | *out = Ch('\''), ++out; 149 | out = copy_and_expand_chars(attribute->value(), Ch('"'), out); 150 | *out = Ch('\''), ++out; 151 | } else { 152 | *out = Ch('"'), ++out; 153 | out = copy_and_expand_chars(attribute->value(), Ch('\''), out); 154 | *out = Ch('"'), ++out; 155 | } 156 | } 157 | } 158 | } 159 | return out; 160 | } 161 | 162 | // Print data node 163 | template 164 | inline OutIt print_data_node(OutIt out, const optional_ptr> node, int flags, int indent) 165 | { 166 | assert(node->type() == node_type::node_data); 167 | if (!(flags & print_no_indenting)) 168 | out = fill_chars(out, indent, Ch('\t')); 169 | if (!node->value_decoded()) { 170 | out = copy_chars(node->value_raw(), out); 171 | } else { 172 | out = copy_and_expand_chars(node->value(), Ch(0), out); 173 | } 174 | return out; 175 | } 176 | 177 | // Print data node 178 | template 179 | inline OutIt print_cdata_node(OutIt out, const optional_ptr> node, int flags, int indent) 180 | { 181 | assert(node->type() == node_type::node_cdata); 182 | if (!(flags & print_no_indenting)) 183 | out = fill_chars(out, indent, Ch('\t')); 184 | *out = Ch('<'); ++out; 185 | *out = Ch('!'); ++out; 186 | *out = Ch('['); ++out; 187 | *out = Ch('C'); ++out; 188 | *out = Ch('D'); ++out; 189 | *out = Ch('A'); ++out; 190 | *out = Ch('T'); ++out; 191 | *out = Ch('A'); ++out; 192 | *out = Ch('['); ++out; 193 | out = copy_chars(node->value(), out); 194 | *out = Ch(']'); ++out; 195 | *out = Ch(']'); ++out; 196 | *out = Ch('>'); ++out; 197 | return out; 198 | } 199 | 200 | // Print element node 201 | template 202 | inline OutIt print_element_node(OutIt out, const optional_ptr> node, int flags, int indent) 203 | { 204 | assert(node->type() == node_type::node_element); 205 | 206 | // Print element name and attributes, if any 207 | if (!(flags & print_no_indenting)) 208 | out = fill_chars(out, indent, Ch('\t')); 209 | *out = Ch('<'), ++out; 210 | if (!node->prefix().empty()) { 211 | out = copy_chars(node->prefix(), out); 212 | *out = Ch(':'); ++out; 213 | } 214 | out = copy_chars(node->name(), out); 215 | out = print_attributes(out, node, flags); 216 | 217 | // If node is childless 218 | if (node->value().empty() && !node->first_node()) 219 | { 220 | // Print childless node tag ending 221 | *out = Ch('/'), ++out; 222 | *out = Ch('>'), ++out; 223 | } 224 | else 225 | { 226 | // Print normal node tag ending 227 | *out = Ch('>'), ++out; 228 | 229 | // If the node is clean, just output the contents and move on. 230 | // Can only do this if we're not indenting, otherwise pretty-print won't work. 231 | if (node->clean() && (flags & print_no_indenting)) { 232 | out = copy_chars(node->contents(), out); 233 | } else { 234 | 235 | // Test if node contains a single data node only (and no other nodes) 236 | auto child = node->first_node(); 237 | if (!child) { 238 | // If node has no children, only print its value without indenting 239 | if (!node->value_decoded()) { 240 | out = copy_chars(node->value_raw(), out); 241 | } else { 242 | out = copy_and_expand_chars(node->value(), Ch(0), out); 243 | } 244 | } else if (!child->next_sibling() && child->type() == node_type::node_data) { 245 | // If node has a sole data child, only print its value without indenting 246 | if (!child->value_decoded()) { 247 | out = copy_chars(child->value_raw(), out); 248 | } else { 249 | out = copy_and_expand_chars(child->value(), Ch(0), out); 250 | } 251 | } else { 252 | // Print all children with full indenting 253 | if (!(flags & print_no_indenting)) 254 | *out = Ch('\n'), ++out; 255 | out = print_children(out, node, flags, indent + 1); 256 | if (!(flags & print_no_indenting)) 257 | out = fill_chars(out, indent, Ch('\t')); 258 | } 259 | } 260 | 261 | // Print node end 262 | *out = Ch('<'), ++out; 263 | *out = Ch('/'), ++out; 264 | if (!node->prefix().empty()) { 265 | out = copy_chars(node->prefix(), out); 266 | *out = Ch(':'); ++out; 267 | } 268 | out = copy_chars(node->name(), out); 269 | *out = Ch('>'), ++out; 270 | } 271 | return out; 272 | } 273 | 274 | // Print declaration node 275 | template 276 | inline OutIt print_declaration_node(OutIt out, const optional_ptr> node, int flags, int indent) 277 | { 278 | // Print declaration start 279 | if (!(flags & print_no_indenting)) 280 | out = fill_chars(out, indent, Ch('\t')); 281 | *out = Ch('<'), ++out; 282 | *out = Ch('?'), ++out; 283 | *out = Ch('x'), ++out; 284 | *out = Ch('m'), ++out; 285 | *out = Ch('l'), ++out; 286 | 287 | // Print attributes 288 | out = print_attributes(out, node, flags); 289 | 290 | // Print declaration end 291 | *out = Ch('?'), ++out; 292 | *out = Ch('>'), ++out; 293 | 294 | return out; 295 | } 296 | 297 | // Print comment node 298 | template 299 | inline OutIt print_comment_node(OutIt out, const optional_ptr> node, int flags, int indent) 300 | { 301 | assert(node->type() == node_type::node_comment); 302 | if (!(flags & print_no_indenting)) 303 | out = fill_chars(out, indent, Ch('\t')); 304 | *out = Ch('<'), ++out; 305 | *out = Ch('!'), ++out; 306 | *out = Ch('-'), ++out; 307 | *out = Ch('-'), ++out; 308 | out = copy_chars(node->value(), out); 309 | *out = Ch('-'), ++out; 310 | *out = Ch('-'), ++out; 311 | *out = Ch('>'), ++out; 312 | return out; 313 | } 314 | 315 | // Print doctype node 316 | template 317 | inline OutIt print_doctype_node(OutIt out, const optional_ptr> node, int flags, int indent) 318 | { 319 | assert(node->type() == node_type::node_doctype); 320 | if (!(flags & print_no_indenting)) 321 | out = fill_chars(out, indent, Ch('\t')); 322 | *out = Ch('<'), ++out; 323 | *out = Ch('!'), ++out; 324 | *out = Ch('D'), ++out; 325 | *out = Ch('O'), ++out; 326 | *out = Ch('C'), ++out; 327 | *out = Ch('T'), ++out; 328 | *out = Ch('Y'), ++out; 329 | *out = Ch('P'), ++out; 330 | *out = Ch('E'), ++out; 331 | *out = Ch(' '), ++out; 332 | out = copy_chars(node->value(), out); 333 | *out = Ch('>'), ++out; 334 | return out; 335 | } 336 | 337 | // Print pi node 338 | template 339 | inline OutIt print_pi_node(OutIt out, const optional_ptr> node, int flags, int indent) 340 | { 341 | assert(node->type() == node_type::node_pi); 342 | if (!(flags & print_no_indenting)) 343 | out = fill_chars(out, indent, Ch('\t')); 344 | *out = Ch('<'), ++out; 345 | *out = Ch('?'), ++out; 346 | out = copy_chars(node->name(), out); 347 | *out = Ch(' '), ++out; 348 | out = copy_chars(node->value(), out); 349 | *out = Ch('?'), ++out; 350 | *out = Ch('>'), ++out; 351 | return out; 352 | } 353 | 354 | // Print literal node 355 | template 356 | inline OutIt print_literal_node(OutIt out, const optional_ptr> node, int flags, int indent) 357 | { 358 | assert(node->type() == node_type::node_literal); 359 | if (!(flags & print_no_indenting)) 360 | out = fill_chars(out, indent, Ch('\t')); 361 | out = copy_chars(node->value(), out); 362 | return out; 363 | } 364 | 365 | // Print node 366 | // Print node 367 | template 368 | inline OutIt print_node(OutIt out, const optional_ptr> node, int flags, int indent) 369 | { 370 | // Print proper node type 371 | switch (node->type()) 372 | { 373 | // Document 374 | case node_document: 375 | out = print_children(out, node, flags, indent); 376 | break; 377 | 378 | // Element 379 | case node_element: 380 | out = print_element_node(out, node, flags, indent); 381 | break; 382 | 383 | // Data 384 | case node_data: 385 | out = print_data_node(out, node, flags, indent); 386 | break; 387 | 388 | // CDATA 389 | case node_cdata: 390 | out = print_cdata_node(out, node, flags, indent); 391 | break; 392 | 393 | // Declaration 394 | case node_declaration: 395 | out = print_declaration_node(out, node, flags, indent); 396 | break; 397 | 398 | // Comment 399 | case node_comment: 400 | out = print_comment_node(out, node, flags, indent); 401 | break; 402 | 403 | // Doctype 404 | case node_doctype: 405 | out = print_doctype_node(out, node, flags, indent); 406 | break; 407 | 408 | // Pi 409 | case node_pi: 410 | out = print_pi_node(out, node, flags, indent); 411 | break; 412 | 413 | case node_literal: 414 | out = print_literal_node(out, node, flags, indent); 415 | break; 416 | 417 | // Unknown 418 | default: 419 | assert(0); 420 | break; 421 | } 422 | 423 | // If indenting not disabled, add line break after node 424 | if (!(flags & print_no_indenting)) 425 | *out = Ch('\n'), ++out; 426 | 427 | // Return modified iterator 428 | return out; 429 | } 430 | 431 | } 432 | //! \endcond 433 | 434 | /////////////////////////////////////////////////////////////////////////// 435 | // Printing 436 | 437 | //! Prints XML to given output iterator. 438 | //! \param out Output iterator to print to. 439 | //! \param node Node to be printed. Pass xml_document to print entire document. 440 | //! \param flags Flags controlling how XML is printed. 441 | //! \return Output iterator pointing to position immediately after last character of printed text. 442 | template 443 | inline OutIt print(OutIt out, const xml_node &node, int flags = 0) 444 | { 445 | flxml::optional_ptr ptr(const_cast *>(&node)); 446 | return internal::print_node(out, ptr, flags, 0); 447 | } 448 | 449 | #ifndef RAPIDXML_NO_STREAMS 450 | 451 | //! Prints XML to given output stream. 452 | //! \param out Output stream to print to. 453 | //! \param node Node to be printed. Pass xml_document to print entire document. 454 | //! \param flags Flags controlling how XML is printed. 455 | //! \return Output stream. 456 | template 457 | inline std::basic_ostream &print(std::basic_ostream &out, const xml_node &node, int flags = 0) 458 | { 459 | print(std::ostream_iterator(out), node, flags); 460 | return out; 461 | } 462 | 463 | //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process. 464 | //! \param out Output stream to print to. 465 | //! \param node Node to be printed. 466 | //! \return Output stream. 467 | template 468 | inline std::basic_ostream &operator <<(std::basic_ostream &out, const xml_node &node) 469 | { 470 | return print(out, node); 471 | } 472 | 473 | #endif 474 | 475 | } 476 | 477 | #endif 478 | -------------------------------------------------------------------------------- /include/flxml/tables.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by dwd on 9/7/24. 3 | // 4 | 5 | #ifndef RAPIDXML_RAPIDXML_TABLES_HPP 6 | #define RAPIDXML_RAPIDXML_TABLES_HPP 7 | 8 | #include 9 | #include 10 | 11 | /////////////////////////////////////////////////////////////////////// 12 | // Internals 13 | 14 | //! \cond internal 15 | namespace flxml::internal { 16 | 17 | // Struct that contains lookup tables for the parser 18 | struct lookup_tables { 19 | // Whitespace (space \n \r \t) 20 | static inline const std::vector lookup_whitespace = 21 | { 22 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 23 | false, false, false, false, false, false, false, false, false, true , true , false, false, true , false, false, // 0 24 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 1 25 | true , false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 2 26 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 3 27 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 4 28 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 5 29 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 6 30 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 7 31 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 8 32 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // 9 33 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // A 34 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // B 35 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // C 36 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // D 37 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, // E 38 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false // F 39 | }; 40 | 41 | // Element name (anything but space \n \r \t / > ? \0 and :) 42 | static inline const std::vector lookup_element_name = 43 | { 44 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 45 | false, true , true , true , true , true , true , true , true , false, false, true , true , false, true , true , // 0 46 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 47 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , false, // 2 48 | true , true , true , true , true , true , true , true , true , true , false, true , true , true , false, false, // 3 49 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 50 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 51 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 52 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 53 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 54 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 55 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 56 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 57 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 58 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 59 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 60 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 61 | }; 62 | 63 | // Node name (anything but space \n \r \t / > ? \0) 64 | static inline const std::vector lookup_node_name = 65 | { 66 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 67 | false, true , true , true , true , true , true , true , true , false, false, true , true , false, true , true , // 0 68 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 69 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , false, // 2 70 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , false, false, // 3 71 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 72 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 73 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 74 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 75 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 76 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 77 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 78 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 79 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 80 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 81 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 82 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 83 | }; 84 | 85 | // Text (i.e. PCDATA) (anything but < \0) 86 | static inline const std::vector lookup_text = 87 | { 88 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 89 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 90 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 91 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 2 92 | true , true , true , true , true , true , true , true , true , true , true , true , false, true , true , true , // 3 93 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 94 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 95 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 96 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 97 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 98 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 99 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 100 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 101 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 102 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 103 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 104 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 105 | }; 106 | 107 | // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled 108 | // (anything but < \0 &) 109 | static inline const std::vector lookup_text_pure_no_ws = 110 | { 111 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 112 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 113 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 114 | true , true , true , true , true , true , false, true , true , true , true , true , true , true , true , true , // 2 115 | true , true , true , true , true , true , true , true , true , true , true , true , false, true , true , true , // 3 116 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 117 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 118 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 119 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 120 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 121 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 122 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 123 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 124 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 125 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 126 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 127 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 128 | }; 129 | 130 | // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled 131 | // (anything but < \0 & space \n \r \t) 132 | static inline const std::vector lookup_text_pure_with_ws = 133 | { 134 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 135 | false, true , true , true , true , true , true , true , true , false, false, true , true , false, true , true , // 0 136 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 137 | false, true , true , true , true , true , false, true , true , true , true , true , true , true , true , true , // 2 138 | true , true , true , true , true , true , true , true , true , true , true , true , false, true , true , true , // 3 139 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 140 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 141 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 142 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 143 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 144 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 145 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 146 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 147 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 148 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 149 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 150 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 151 | }; 152 | 153 | // Attribute name (anything but space \n \r \t / < > = ? ! \0) 154 | static inline const std::vector lookup_attribute_name = 155 | { 156 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 157 | false, true , true , true , true , true , true , true , true , false, false, true , true , false, true , true , // 0 158 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 159 | false, false, true , true , true , true , true , true , true , true , true , true , true , true , true , false, // 2 160 | true , true , true , true , true , true , true , true , true , true , true , true , false, false, false, false, // 3 161 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 162 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 163 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 164 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 165 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 166 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 167 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 168 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 169 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 170 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 171 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 172 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 173 | }; 174 | 175 | // Attribute data with single quote (anything but ' \0) 176 | static inline const std::vector lookup_attribute_data_1 = 177 | { 178 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 179 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 180 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 181 | true , true , true , true , true , true , true , false, true , true , true , true , true , true , true , true , // 2 182 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 3 183 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 184 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 185 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 186 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 187 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 188 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 189 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 190 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 191 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 192 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 193 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 194 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 195 | }; 196 | 197 | // Attribute data with single quote that does not require processing (anything but ' \0 &) 198 | static inline const std::vector lookup_attribute_data_1_pure = 199 | { 200 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 201 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 202 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 203 | true , true , true , true , true , true , false, false, true , true , true , true , true , true , true , true , // 2 204 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 3 205 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 206 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 207 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 208 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 209 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 210 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 211 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 212 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 213 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 214 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 215 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 216 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 217 | }; 218 | 219 | // Attribute data with double quote (anything but " \0) 220 | static inline const std::vector lookup_attribute_data_2 = 221 | { 222 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 223 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 224 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 225 | true , true , false, true , true , true , true , true , true , true , true , true , true , true , true , true , // 2 226 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 3 227 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 228 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 229 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 230 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 231 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 232 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 233 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 234 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 235 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 236 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 237 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 238 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , 1 // F 239 | }; 240 | 241 | // Attribute data with double quote that does not require processing (anything but " \0 &) 242 | static inline const std::vector lookup_attribute_data_2_pure = 243 | { 244 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 245 | false, true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 0 246 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 1 247 | true , true , false, true , true , true , false, true , true , true , true , true , true , true , true , true , // 2 248 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 3 249 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 4 250 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 5 251 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 6 252 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 7 253 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 8 254 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // 9 255 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // A 256 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // B 257 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // C 258 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // D 259 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , // E 260 | true , true , true , true , true , true , true , true , true , true , true , true , true , true , true , true // F 261 | }; 262 | 263 | // Digits (dec and hex, 255 denotes end of numeric character reference) 264 | static inline const std::array lookup_digits = 265 | { 266 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 267 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0 268 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1 269 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2 270 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3 271 | 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4 272 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5 273 | 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6 274 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7 275 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8 276 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9 277 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A 278 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B 279 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C 280 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D 281 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E 282 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F 283 | }; 284 | }; 285 | } 286 | //! \endcond 287 | 288 | #endif //RAPIDXML_RAPIDXML_TABLES_HPP 289 | --------------------------------------------------------------------------------