summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsteven-vd <steven@vandorp.lu>2026-08-16 15:00:32 +0200
committersteven-vd <steven@vandorp.lu>2026-08-17 09:31:42 +0200
commitee081ca70322cd01d0642fdb1860e6bfbfb48e85 (patch)
tree295fdba93b08b24562c6d49e971bbf3425eac50c
Initial commitHEAD0.1.0master
-rw-r--r--.env/build.sh49
-rw-r--r--.env/debug.sh5
-rw-r--r--.gitignore2
-rw-r--r--LICENSE20
-rw-r--r--README.md161
-rw-r--r--benchmarks/RapidXML/.env/build.sh6
-rw-r--r--benchmarks/RapidXML/.env/debug.sh7
-rw-r--r--benchmarks/RapidXML/.gitignore1
-rw-r--r--benchmarks/RapidXML/bench.cpp81
-rw-r--r--benchmarks/RapidXML/benchmark-results.xml26
-rw-r--r--benchmarks/RapidXML/count_highway_crossings.cpp84
-rw-r--r--benchmarks/RapidXML/pubmed_len.cpp46
-rw-r--r--benchmarks/RapidXML/rapidxml.hpp2601
-rw-r--r--benchmarks/harness.h94
-rw-r--r--benchmarks/harness.rs59
-rw-r--r--benchmarks/imxml/.env/build.sh7
-rw-r--r--benchmarks/imxml/.env/debug.sh7
-rw-r--r--benchmarks/imxml/.gitignore1
-rw-r--r--benchmarks/imxml/bench.c52
-rw-r--r--benchmarks/imxml/benchmark-results-gcc.xml24
-rw-r--r--benchmarks/imxml/benchmark-results-tcc.xml24
-rw-r--r--benchmarks/imxml/benchmark-results.xml26
-rw-r--r--benchmarks/imxml/count_highway_crossings.c64
-rw-r--r--benchmarks/imxml/pubmed_len.c48
-rw-r--r--benchmarks/imxml/pubmed_len_with_entities.c47
-rw-r--r--benchmarks/pugixml/.env/build.sh6
-rw-r--r--benchmarks/pugixml/.env/debug.sh7
-rw-r--r--benchmarks/pugixml/.gitignore1
-rw-r--r--benchmarks/pugixml/bench.cpp76
-rw-r--r--benchmarks/pugixml/benchmark-results.xml26
-rw-r--r--benchmarks/pugixml/count_highway_crossings.cpp54
-rw-r--r--benchmarks/pugixml/pubmed_len.cpp49
-rw-r--r--benchmarks/pugixml/pugiconfig.hpp81
-rw-r--r--benchmarks/pugixml/pugixml.cpp13776
-rw-r--r--benchmarks/pugixml/pugixml.hpp1604
-rw-r--r--benchmarks/quick-xml/.env/build.sh5
-rw-r--r--benchmarks/quick-xml/.env/debug.sh8
-rw-r--r--benchmarks/quick-xml/.gitignore1
-rw-r--r--benchmarks/quick-xml/Cargo.lock25
-rw-r--r--benchmarks/quick-xml/Cargo.toml14
-rw-r--r--benchmarks/quick-xml/benchmark-results.xml26
-rw-r--r--benchmarks/quick-xml/src/count_highway_crossings.rs74
-rw-r--r--benchmarks/quick-xml/src/main.rs39
-rw-r--r--benchmarks/quick-xml/src/pubmed_len.rs82
-rw-r--r--download_benchmark_data.sh6
-rw-r--r--examples/c89.c33
-rw-r--r--examples/nolibc.c71
-rw-r--r--examples/simple.c27
-rw-r--r--imxml.h1934
49 files changed, 21567 insertions, 0 deletions
diff --git a/.env/build.sh b/.env/build.sh
new file mode 100644
index 0000000..5846fd7
--- /dev/null
+++ b/.env/build.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+set -e
+
+CLANG_TIDY_FLAGS="
+ *,
+ -llvm-header-guard,
+ -readability-identifier-length
+ -readability-braces-around-statements
+ -readability-else-after-return
+ -readability-function-cognitive-complexity
+ -readability-magic-numbers
+ -cppcoreguidelines-init-variables
+ -cppcoreguidelines-avoid-non-const-global-variables
+ -cppcoreguidelines-avoid-magic-numbers
+ -bugprone-macro-parentheses
+ -bugprone-easily-swappable-parameters
+ -altera-unroll-loops
+ -altera-id-dependent-backward-branch
+ -llvm-else-after-return
+ -hicpp-braces-around-statements
+"
+
+CC_FLAGS="
+ --std=c89 \
+ -Werror=vla \
+
+ -Wall -Wpedantic -Wextra -pedantic-errors \
+ -Wswitch-enum \
+"
+CC_FLAGS+=" -Werror"
+
+CC_FLAGS+=" -I."
+
+CC_FLAGS+=" -O0 -g3 -march=native -fsanitize=array-bounds -fsanitize=bounds"
+#CC_FLAGS+=" -O3 -flto -g3 -march=native -fno-omit-frame-pointer" # for profiling
+#CC_FLAGS+=" -O3 -DNDEBUG -flto -march=native"
+
+CC_FLAGS+=" -Wgnu -Weverything -Wno-unsafe-buffer-usage \
+ -Wno-padded -Wno-covered-switch-default \
+ "
+
+#TARGET="examples/simple.c"
+TARGET="examples/c89.c"
+#clang-tidy $TARGET -checks="$CLANG_TIDY_FLAGS" -- $CC_FLAGS
+clang $CC_FLAGS $TARGET
+
+#clang -Os -nostdlib -ffreestanding -static -fno-stack-protector -fuse-ld=lld -I. examples/nolibc.c -o smallest
+
diff --git a/.env/debug.sh b/.env/debug.sh
new file mode 100644
index 0000000..2a61b04
--- /dev/null
+++ b/.env/debug.sh
@@ -0,0 +1,5 @@
+set -e
+. ./.env/build.sh
+
+./a.out
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93f54e6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+a.out
+dat
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f07d3dc
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright 2026 Steven Van Dorp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a803092
--- /dev/null
+++ b/README.md
@@ -0,0 +1,161 @@
+# EXPERIMENTAL
+This library is still in an experimental stage and should not be used in
+environments where stability/safety/security/reliability are important. Some
+minor features like normalization and recursive entity references aren't
+supported yet.
+
+While this library is in an experimental state, semantic versioning
+conventions will not be guaranteed and minor updates may introduce breaking
+API/ABI changes. Semantic versioning will be guaranteed once this library
+hits 1.0.
+
+# Why use imxml?
+imxml is a portable, high-performance, zero-allocation, immediate-mode XML
+parser designed for applications that know the approximate structure of the XML
+they consume.
+
+The rationale of this library is that you generally don't need to parse
+arbitrary XML. Instead, you're targeting some subset. This goes beyond just
+disabling certain features, as even the structure of your XML is typically
+bounded. For example, if you're parsing OpenStreetMap (OSM) data, you know that
+you'll have one root <osm> element, which contains one <bounds> element,
+followed by several <node> elements, which may contain <tag> elements, then
+several <way> elements, and so on. This "immediate-mode" API design allows you
+to skip parsing anything you're not interested in, while also indicating your
+data's expected layout to the compiler, which allows the optimizer to do a
+better job.
+
+## Performance
+benchmarks/imxml/benchmark-results.xml contains benchmark results for some
+workloads on an AMD Ryzen 5 3600. In these benchmarks, imxml achieves a minimum
+2.9x performance improvement compared to quick-xml (the fastest mainstream
+parser I could find) for these workloads, and up to 12x in extreme cases. Note
+that these are synthetic benchmarks designed to stress the raw parsing
+throughput and nothing else. Your code likely contains all sorts of extra
+business logic that will change your perceived throughput, though imxml's
+immediate-mode API design should give your optimizer the best chance to
+generate fast code.
+
+## Memory
+imxml's memory usage is completely static, there are no allocations, and it
+doesn't modify the input buffer. Returned strings are simply views into the
+input buffer or, in the case of character references, views into a static
+(optionally thread-local) buffer.
+
+## Compatibility
+Standard C89 (ANSI C) and C++98 are supported. The library has been tested with
+clang, gcc, and tcc. Every standards-compliant C compiler should work, though
+if emmintrin.h/immintrin.h isn't supported, only the fallback scalar
+implementation will work (unless you define your own instruction set).
+
+UTF-8, ISO-Latin-1, ASCII, and generally all char/byte-based encodings are
+supported. UTF-16 is not supported.
+
+The library itself has no dependencies, not even libc, but optional
+platform-specific convenience functions can be enabled with flags. Right now,
+there is only a Linux helper implementation for loading files.
+
+## Extensibility
+Currently imxml supports the MMX, SSE2, and AVX2 instruction sets, as well as a
+fallback scalar configuration for compilers that don't support SIMD intrinsics
+(e.g. tcc). You can define custom instruction set definitions if you want SIMD
+acceleration on more exotic platforms.
+
+## Features
+ - [X] Entities
+ - [X] Standard entity references
+ - [X] Unicode character references
+ - [X] DTD-defined entity references
+ - [X] CDATA
+ - [X] Comments
+ - [X] Namespaces
+ - [X] Customizable SIMD instruction sets
+ - [ ] Content normalization
+ - [ ] Platform-specific helper functions
+ - [X] Linux
+ - [ ] Windows
+ - [ ] OSX
+
+# Usage
+Download imxml.h and add this to your code:
+```
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+```
+
+## Example
+```
+#define IMXML_LINUX // We're using Linux, of course
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES // OSM always uses double-quotes
+#define IMXML_NO_SUPPORT_COMMENTS // OSM doesn't include comments
+#define IMXML_NO_SUPPORT_CDATA // OSM doesn't include CDATA
+#define IMXML_NO_SUPPORT_ENTITIES // We don't need to parse entities
+#define IMXML_NO_SUPPORT_NAMESPACES // OSM doesn't include namespaces
+#define IMXML_NO_CHECK_BOUNDS // We know our data will end with </osm>
+#define THREADLOCAL // We disable thread-local by defining it as nothing
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+#include <stdio.h> // for printf
+
+int main (void) {
+ size_t file_size;
+ // We need to use this file_open function because the input data needs to
+ // be padded to make sure we're not reading out-of-bounds
+ char* const file = imxml_linux_file_open("dat/yellowstone.osm", &file_size, false);
+ if (file == 0) return -1;
+ XmlParser p = {0}; // zero-init is important
+ p.head = file;
+
+ // Verify we're really dealing with osm data. This is mostly unnecessary,
+ // but it doesn't cost much and might catch some dumb bugs
+ if (!xml_parse_header(&p, NULL)) return -1;
+ if (!xml_tag_expect(&p, "osm")) return -1;
+ if (!xml_has_children(&p)) return -1;
+ if (!xml_tag_expect(&p, "bounds")) return -1;
+ if (xml_has_children(&p)) return -1;
+
+ size_t highway_crossing_count = 0;
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("node"))) {
+ if (xml_has_children(&p)) {
+ while (true) {
+ tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("/node"))) break;
+ ImxmlString key = xml_value(&p);
+ if (!imxml_streql(key, imxml_strlit("highway"))) continue;
+ ImxmlString value = xml_value(&p);
+ if (imxml_streql(value, imxml_strlit("crossing"))) {
+ highway_crossing_count += 1;
+ }
+ }
+ }
+ } else {
+ // encountered non-node tag, which means we can early-out (because
+ // OSM groups all of the <node> elements up-front).
+ // Alternatively, we could check if tag == "/osm", but then we'd
+ // just be skipping over all the <way> and <relation> tags, which
+ // would be a waste of time
+ break;
+ }
+ }
+
+ printf("%zu\n", highway_crossing_count);
+ imxml_linux_file_close(file, file_size);
+ return 0;
+}
+```
+
+See the examples/ and benchmarks/imxml/ directories for more examples.
+
+# Contributing
+
+If you have any ideas or issues, send them to steven@vandorp.lu.
+
+If you want to modify code, make your changes in a separate branch,
+generate the patch(es) (`git format-patch master` from inside your branch)
+and send the patch file(s) to steven@vandorp.lu.
+
+# Documentation
+See the DOCUMENTATION section in the source code.
+
diff --git a/benchmarks/RapidXML/.env/build.sh b/benchmarks/RapidXML/.env/build.sh
new file mode 100644
index 0000000..a2e7c04
--- /dev/null
+++ b/benchmarks/RapidXML/.env/build.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -e
+
+clang++ bench.cpp count_highway_crossings.cpp pubmed_len.cpp -O3 -flto -march=native -o bench
+
diff --git a/benchmarks/RapidXML/.env/debug.sh b/benchmarks/RapidXML/.env/debug.sh
new file mode 100644
index 0000000..d3fa5a9
--- /dev/null
+++ b/benchmarks/RapidXML/.env/debug.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+. .env/build.sh
+./bench
+
diff --git a/benchmarks/RapidXML/.gitignore b/benchmarks/RapidXML/.gitignore
new file mode 100644
index 0000000..ba99195
--- /dev/null
+++ b/benchmarks/RapidXML/.gitignore
@@ -0,0 +1 @@
+bench
diff --git a/benchmarks/RapidXML/bench.cpp b/benchmarks/RapidXML/bench.cpp
new file mode 100644
index 0000000..c057203
--- /dev/null
+++ b/benchmarks/RapidXML/bench.cpp
@@ -0,0 +1,81 @@
+#include <fstream>
+#include <vector>
+
+#include "../harness.h"
+
+std::vector<char> load_file(char const* const path, size_t* const buf_size) {
+ std::ifstream file(path, std::ios::binary | std::ios::ate);
+ if (!file) {
+ *buf_size = 0;
+ return {};
+ }
+ std::streamsize size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::vector<char> buffer(static_cast<size_t>(size));
+ if (!file.read(buffer.data(), size)) {
+ *buf_size = 0;
+ return {};
+ }
+
+ *buf_size = buffer.size();
+ buffer.push_back(0); // null terminator
+ return buffer;
+}
+
+size_t count_highway_crossings (char* buffer, size_t len);
+size_t count_highway_crossings_cheat_and_early_out (char* buffer, size_t len);
+size_t pubmed_len (char* buffer, size_t len);
+size_t pubmed_len_with_entities (char* buffer, size_t len);
+
+int main (void) {
+ FILE* out = fopen("benchmark-results.xml", "wb");
+ if (!out) goto err;
+
+ fprintf(out,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<benchmarks library=\"RapidXML\">\n"
+ );
+
+ size_t buf_size;
+
+ if (true) {
+ auto buf = load_file("../../dat/yellowstone.osm", &buf_size);
+ char* buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(count_highway_crossings);
+
+ buf = load_file("../../dat/yellowstone.osm", &buf_size);
+ buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(count_highway_crossings_cheat_and_early_out);
+ }
+
+ if (true) {
+ {
+ auto buf = load_file("../../dat/PMC176545.xml", &buf_size);
+ char* buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(pubmed_len);
+ }
+ {
+ auto buf = load_file("../../dat/PMC176545.xml", &buf_size);
+ char* buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(pubmed_len_with_entities);
+ }
+ }
+
+ fprintf(out,
+ "</benchmarks>\n\n"
+ );
+
+ goto noerr;
+ err:
+ if (out) fclose(out);
+ fprintf(stderr, "ERROR\n");
+ return -1;
+ noerr:
+ return 0;
+}
+
diff --git a/benchmarks/RapidXML/benchmark-results.xml b/benchmarks/RapidXML/benchmark-results.xml
new file mode 100644
index 0000000..0176892
--- /dev/null
+++ b/benchmarks/RapidXML/benchmark-results.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="RapidXML">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">1384075150</time>
+ <throughput unit="MiB/s">328.993370</throughput>
+ <energy-consumption unit="J/GiB">80.423432</energy-consumption>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">1362597340</time>
+ <throughput unit="MiB/s">334.179097</throughput>
+ <energy-consumption unit="J/GiB">97.345299</energy-consumption>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>75210</res>
+ <time unit="ns">231360</time>
+ <throughput unit="MiB/s">635.770511</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74813</res>
+ <time unit="ns">126320</time>
+ <throughput unit="MiB/s">1164.438454</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/RapidXML/count_highway_crossings.cpp b/benchmarks/RapidXML/count_highway_crossings.cpp
new file mode 100644
index 0000000..6d0ecf6
--- /dev/null
+++ b/benchmarks/RapidXML/count_highway_crossings.cpp
@@ -0,0 +1,84 @@
+#include "rapidxml.hpp"
+#include <cstddef>
+#include <cstring>
+
+namespace rx = rapidxml;
+
+inline bool is_highway_crossing(rx::xml_node<>* tag_node) {
+ rx::xml_attribute<>* k = tag_node->first_attribute();
+ if (!k) return false;
+
+ rx::xml_attribute<>* v = k->next_attribute();
+ if (!v) return false;
+
+ return std::strcmp(k->value(), "highway") == 0 &&
+ std::strcmp(v->value(), "crossing") == 0;
+}
+
+size_t count_highway_crossings(char* buffer, size_t len) {
+ rx::xml_document<> doc;
+
+ try {
+ doc.parse<rx::parse_no_data_nodes | rx::parse_no_element_values>(buffer);
+ } catch (const rx::parse_error&) {
+ return 0;
+ }
+
+ size_t count = 0;
+
+ rx::xml_node<>* osm = doc.first_node("osm");
+ if (!osm)
+ return count;
+
+ for (rx::xml_node<>* node = osm->first_node("node");
+ node;
+ node = node->next_sibling("node")) {
+
+ for (rx::xml_node<>* tag = node->first_node("tag");
+ tag;
+ tag = tag->next_sibling("tag")) {
+
+ if (is_highway_crossing(tag))
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+size_t count_highway_crossings_cheat_and_early_out(char* buffer, size_t len) {
+ rx::xml_document<> doc;
+
+ try {
+ doc.parse<rx::parse_no_data_nodes | rx::parse_no_element_values>(buffer);
+ } catch (const rx::parse_error&) {
+ return 0;
+ }
+
+ size_t count = 0;
+
+ rx::xml_node<>* osm = doc.first_node("osm");
+ if (!osm)
+ return count;
+
+ // Equivalent to iterating over all children and stopping at "way".
+ // next_sibling() avoids inspecting unrelated nodes between elements.
+ for (rx::xml_node<>* top = osm->first_node();
+ top;
+ top = top->next_sibling()) {
+
+ if (std::strcmp(top->name(), "way") == 0)
+ break;
+
+ for (rx::xml_node<>* tag = top->first_node("tag");
+ tag;
+ tag = tag->next_sibling("tag")) {
+
+ if (is_highway_crossing(tag))
+ ++count;
+ }
+ }
+
+ return count;
+}
+
diff --git a/benchmarks/RapidXML/pubmed_len.cpp b/benchmarks/RapidXML/pubmed_len.cpp
new file mode 100644
index 0000000..f25f33b
--- /dev/null
+++ b/benchmarks/RapidXML/pubmed_len.cpp
@@ -0,0 +1,46 @@
+#include "rapidxml.hpp"
+#include <cstring>
+
+inline bool is_content_root(const char* name, std::size_t len) {
+ return (len == 8 && std::memcmp(name, "abstract", 8) == 0) ||
+ (len == 4 && std::memcmp(name, "body", 4) == 0);
+}
+
+size_t sum_text(const rapidxml::xml_node<>* node) {
+ size_t total = 0;
+ for (auto* child = node->first_node(); child; child = child->next_sibling()) {
+ if (child->type() == rapidxml::node_type::node_data) {
+ total += child->value_size();
+ } else if (child->type() == rapidxml::node_type::node_element) {
+ total += sum_text(child);
+ }
+ }
+ return total;
+}
+
+size_t walk(const rapidxml::xml_node<>* node) {
+ size_t total = 0;
+ for (auto* child = node->first_node(); child; child = child->next_sibling()) {
+ if (child->type() != rapidxml::node_type::node_element) continue;
+ if (is_content_root(child->name(), child->name_size())) {
+ total += sum_text(child);
+ } else {
+ total += walk(child);
+ }
+ }
+ return total;
+}
+
+size_t pubmed_len(char* buffer, size_t /*len*/) {
+ rapidxml::xml_document<> doc;
+ doc.parse<rapidxml::parse_no_entity_translation |
+ rapidxml::parse_no_string_terminators>(buffer);
+ return walk(&doc);
+}
+
+size_t pubmed_len_with_entities(char* buffer, size_t /*len*/) {
+ rapidxml::xml_document<> doc;
+ doc.parse<rapidxml::parse_no_string_terminators>(buffer);
+ return walk(&doc);
+}
+
diff --git a/benchmarks/RapidXML/rapidxml.hpp b/benchmarks/RapidXML/rapidxml.hpp
new file mode 100644
index 0000000..cf4ad50
--- /dev/null
+++ b/benchmarks/RapidXML/rapidxml.hpp
@@ -0,0 +1,2601 @@
+#ifndef RAPIDXML_HPP_INCLUDED
+#define RAPIDXML_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
+// Version 1.17
+// Revision $DateTime: 2024/06/15 19:38:51 $
+//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation
+
+// If standard library is disabled, user must provide implementations of required functions and typedefs
+#if !defined(RAPIDXML_NO_STDLIB)
+ #include <cstdlib> // For std::size_t
+ #include <cassert> // For assert
+ #include <new> // For placement new
+#endif
+
+// On MSVC, disable "conditional expression is constant" warning (level 4).
+// This warning is almost impossible to avoid with certain types of templated code
+#ifdef _MSC_VER
+ #pragma warning(push)
+ #pragma warning(disable:4127) // Conditional expression is constant
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// RAPIDXML_PARSE_ERROR
+
+#if defined(RAPIDXML_NO_EXCEPTIONS)
+
+#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); }
+
+namespace rapidxml
+{
+ //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS,
+ //! this function is called to notify user about the error.
+ //! It must be defined by the user.
+ //! <br><br>
+ //! This function cannot return. If it does, the results are undefined.
+ //! <br><br>
+ //! A very simple definition might look like that:
+ //! <pre>
+ //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+ //! {
+ //! std::cout << "Parse error: " << what << "\n";
+ //! std::abort();
+ //! }
+ //! </pre>
+ //! \param what Human readable description of the error.
+ //! \param where Pointer to character data where error was detected.
+ void parse_error_handler(const char *what, void *where);
+}
+
+#else
+
+#include <exception> // For std::exception
+
+#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where)
+
+namespace rapidxml
+{
+
+ //! Parse error exception.
+ //! This exception is thrown by the parser when an error occurs.
+ //! Use what() function to get human-readable error message.
+ //! Use where() function to get a pointer to position within source text where error was detected.
+ //! <br><br>
+ //! If throwing exceptions by the parser is undesirable,
+ //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included.
+ //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception.
+ //! This function must be defined by the user.
+ //! <br><br>
+ //! This class derives from <code>std::exception</code> class.
+ class parse_error: public std::exception
+ {
+
+ public:
+
+ //! Constructs parse error
+ parse_error(const char *what, void *where)
+ : m_what(what)
+ , m_where(where)
+ {
+ }
+
+ //! Gets human readable description of error.
+ //! \return Pointer to null terminated description of the error.
+ virtual const char *what() const throw()
+ {
+ return m_what;
+ }
+
+ //! Gets pointer to character data where error happened.
+ //! Ch should be the same as char type of xml_document that produced the error.
+ //! \return Pointer to location within the parsed string where error occured.
+ template<class Ch>
+ Ch *where() const
+ {
+ return reinterpret_cast<Ch *>(m_where);
+ }
+
+ private:
+
+ const char *m_what;
+ void *m_where;
+
+ };
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// Pool sizes
+
+#ifndef RAPIDXML_STATIC_POOL_SIZE
+ // Size of static memory block of memory_pool.
+ // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+ // No dynamic memory allocations are performed by memory_pool until static memory is exhausted.
+ #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_DYNAMIC_POOL_SIZE
+ // Size of dynamic memory block of memory_pool.
+ // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+ // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool.
+ #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_ALIGNMENT
+ // Memory allocation alignment.
+ // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer.
+ // All memory allocations for nodes, attributes and strings will be aligned to this value.
+ // This must be a power of 2 and at least 1, otherwise memory_pool will not work.
+ #define RAPIDXML_ALIGNMENT sizeof(void *)
+#endif
+
+namespace rapidxml
+{
+ // Forward declarations
+ template<class Ch> class xml_node;
+ template<class Ch> class xml_attribute;
+ template<class Ch> class xml_document;
+
+ //! Enumeration listing all node types produced by the parser.
+ //! Use xml_node::type() function to query node type.
+ enum class node_type
+ {
+ node_document, //!< A document node. Name and value are empty.
+ node_element, //!< An element node. Name contains element name. Value contains text of first data node.
+ node_data, //!< A data node. Name is empty. Value contains data text.
+ node_cdata, //!< A CDATA node. Name is empty. Value contains data text.
+ node_comment, //!< A comment node. Name is empty. Value contains comment text.
+ node_declaration, //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
+ node_doctype, //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
+ node_pi //!< A PI node. Name contains target. Value contains instructions.
+ };
+
+ ///////////////////////////////////////////////////////////////////////
+ // Parsing flags
+
+ //! Parse flag instructing the parser to not create data nodes.
+ //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_data_nodes = 0x1;
+
+ //! Parse flag instructing the parser to not use text of first data node as a value of parent element.
+ //! Can be combined with other flags by use of | operator.
+ //! Note that child data nodes of element node take precendence over its value when printing.
+ //! That is, if element has one or more child data nodes <em>and</em> a value, the value will be ignored.
+ //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_element_values = 0x2;
+
+ //! Parse flag instructing the parser to not place zero terminators after strings in the source text.
+ //! By default zero terminators are placed, modifying source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_string_terminators = 0x4;
+
+ //! Parse flag instructing the parser to not translate entities in the source text.
+ //! By default entities are translated, modifying source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_entity_translation = 0x8;
+
+ //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters.
+ //! By default, UTF-8 handling is enabled.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_utf8 = 0x10;
+
+ //! Parse flag instructing the parser to create XML declaration node.
+ //! By default, declaration node is not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_declaration_node = 0x20;
+
+ //! Parse flag instructing the parser to create comments nodes.
+ //! By default, comment nodes are not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_comment_nodes = 0x40;
+
+ //! Parse flag instructing the parser to create DOCTYPE node.
+ //! By default, doctype node is not created.
+ //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_doctype_node = 0x80;
+
+ //! Parse flag instructing the parser to create PI nodes.
+ //! By default, PI nodes are not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_pi_nodes = 0x100;
+
+ //! Parse flag instructing the parser to validate closing tag names.
+ //! If not set, name inside closing tag is irrelevant to the parser.
+ //! By default, closing tags are not validated.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_validate_closing_tags = 0x200;
+
+ //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes.
+ //! By default, whitespace is not trimmed.
+ //! This flag does not cause the parser to modify source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_trim_whitespace = 0x400;
+
+ //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character.
+ //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag.
+ //! By default, whitespace is not normalized.
+ //! If this flag is specified, source text will be modified.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_normalize_whitespace = 0x800;
+
+ // Compound flags
+
+ //! Parse flags which represent default behaviour of the parser.
+ //! This is always equal to 0, so that all other flags can be simply ored together.
+ //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values.
+ //! This also means that meaning of each flag is a <i>negation</i> of the default setting.
+ //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is <i>enabled</i> by default,
+ //! and using the flag will disable it.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_default = 0;
+
+ //! A combination of parse flags that forbids any modifications of the source text.
+ //! This also results in faster parsing. However, note that the following will occur:
+ //! <ul>
+ //! <li>names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends</li>
+ //! <li>entities will not be translated</li>
+ //! <li>whitespace will not be normalized</li>
+ //! </ul>
+ //! See xml_document::parse() function.
+ const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;
+
+ //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_fastest = parse_non_destructive | parse_no_data_nodes;
+
+ //! A combination of parse flags resulting in largest amount of data being extracted.
+ //! This usually results in slowest parsing.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internals
+
+ //! \cond internal
+ namespace internal
+ {
+
+ // Struct that contains lookup tables for the parser
+ // It must be a template to allow correct linking (because it has static data members, which are defined in a header file).
+ template<int Dummy>
+ struct lookup_tables
+ {
+ static const unsigned char lookup_whitespace[256]; // Whitespace table
+ static const unsigned char lookup_node_name[256]; // Node name table
+ static const unsigned char lookup_text[256]; // Text table
+ static const unsigned char lookup_text_pure_no_ws[256]; // Text table
+ static const unsigned char lookup_text_pure_with_ws[256]; // Text table
+ static const unsigned char lookup_attribute_name[256]; // Attribute name table
+ static const unsigned char lookup_attribute_data_1[256]; // Attribute data table with single quote
+ static const unsigned char lookup_attribute_data_1_pure[256]; // Attribute data table with single quote
+ static const unsigned char lookup_attribute_data_2[256]; // Attribute data table with double quotes
+ static const unsigned char lookup_attribute_data_2_pure[256]; // Attribute data table with double quotes
+ static const unsigned char lookup_digits[256]; // Digits
+ static const unsigned char lookup_upcase[256]; // To uppercase conversion table for ASCII characters
+ };
+
+ // Find length of the string
+ template<class Ch>
+ inline std::size_t measure(const Ch *p)
+ {
+ const Ch *tmp = p;
+ while (*tmp)
+ ++tmp;
+ return static_cast<std::size_t>(tmp - p);
+ }
+
+ // Compare strings for equality
+ template<class Ch>
+ inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive)
+ {
+ if (size1 != size2)
+ return false;
+ if (case_sensitive)
+ {
+ for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+ if (*p1 != *p2)
+ return false;
+ }
+ else
+ {
+ for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+ if (lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)])
+ return false;
+ }
+ return true;
+ }
+ }
+ //! \endcond
+
+ ///////////////////////////////////////////////////////////////////////
+ // Memory pool
+
+ //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation.
+ //! In most cases, you will not need to use this class directly.
+ //! However, if you need to create nodes manually or modify names/values of nodes,
+ //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory.
+ //! Not only is this faster than allocating them by using <code>new</code> operator,
+ //! but also their lifetime will be tied to the lifetime of document,
+ //! possibly simplyfing memory management.
+ //! <br><br>
+ //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool.
+ //! You can also call allocate_string() function to allocate strings.
+ //! Such strings can then be used as names or values of nodes without worrying about their lifetime.
+ //! Note that there is no <code>free()</code> function -- all allocations are freed at once when clear() function is called,
+ //! or when the pool is destroyed.
+ //! <br><br>
+ //! It is also possible to create a standalone memory_pool, and use it
+ //! to allocate nodes, whose lifetime will not be tied to any document.
+ //! <br><br>
+ //! Pool maintains <code>RAPIDXML_STATIC_POOL_SIZE</code> bytes of statically allocated memory.
+ //! Until static memory is exhausted, no dynamic memory allocations are done.
+ //! When static memory is exhausted, pool allocates additional blocks of memory of size <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> each,
+ //! by using global <code>new[]</code> and <code>delete[]</code> operators.
+ //! This behaviour can be changed by setting custom allocation routines.
+ //! Use set_allocator() function to set them.
+ //! <br><br>
+ //! Allocations for nodes, attributes and strings are aligned at <code>RAPIDXML_ALIGNMENT</code> bytes.
+ //! This value defaults to the size of pointer on target architecture.
+ //! <br><br>
+ //! To obtain absolutely top performance from the parser,
+ //! it is important that all nodes are allocated from a single, contiguous block of memory.
+ //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably.
+ //! If required, you can tweak <code>RAPIDXML_STATIC_POOL_SIZE</code>, <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> and <code>RAPIDXML_ALIGNMENT</code>
+ //! to obtain best wasted memory to performance compromise.
+ //! To do it, define their values before rapidxml.hpp file is included.
+ //! \param Ch Character type of created nodes.
+ template<class Ch = char>
+ class memory_pool
+ {
+
+ public:
+
+ //! \cond internal
+ typedef void *(alloc_func)(std::size_t); // Type of user-defined function used to allocate memory
+ typedef void (free_func)(void *); // Type of user-defined function used to free memory
+ //! \endcond
+
+ //! Constructs empty pool with default allocator functions.
+ memory_pool()
+ : m_alloc_func(0)
+ , m_free_func(0)
+ {
+ init();
+ }
+
+ //! Destroys pool and frees all the memory.
+ //! This causes memory occupied by nodes allocated by the pool to be freed.
+ //! Nodes allocated from the pool are no longer valid.
+ ~memory_pool()
+ {
+ clear();
+ }
+
+ //! Allocates a new node from the pool, and optionally assigns name and value to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param type Type of node to create.
+ //! \param name Name to assign to the node, or 0 to assign no name.
+ //! \param value Value to assign to the node, or 0 to assign no value.
+ //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+ //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+ //! \return Pointer to allocated node. This pointer will never be NULL.
+ xml_node<Ch> *allocate_node(node_type type,
+ const Ch *name = 0, const Ch *value = 0,
+ std::size_t name_size = 0, std::size_t value_size = 0)
+ {
+ void *memory = allocate_aligned(sizeof(xml_node<Ch>));
+ xml_node<Ch> *node = new(memory) xml_node<Ch>(type);
+ if (name)
+ {
+ if (name_size > 0)
+ node->name(name, name_size);
+ else
+ node->name(name);
+ }
+ if (value)
+ {
+ if (value_size > 0)
+ node->value(value, value_size);
+ else
+ node->value(value);
+ }
+ return node;
+ }
+
+ //! Allocates a new attribute from the pool, and optionally assigns name and value to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param name Name to assign to the attribute, or 0 to assign no name.
+ //! \param value Value to assign to the attribute, or 0 to assign no value.
+ //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+ //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+ //! \return Pointer to allocated attribute. This pointer will never be NULL.
+ xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0,
+ std::size_t name_size = 0, std::size_t value_size = 0)
+ {
+ void *memory = allocate_aligned(sizeof(xml_attribute<Ch>));
+ xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>;
+ if (name)
+ {
+ if (name_size > 0)
+ attribute->name(name, name_size);
+ else
+ attribute->name(name);
+ }
+ if (value)
+ {
+ if (value_size > 0)
+ attribute->value(value, value_size);
+ else
+ attribute->value(value);
+ }
+ return attribute;
+ }
+
+ //! Allocates a char array of given size from the pool, and optionally copies a given string to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param source String to initialize the allocated memory with, or 0 to not initialize it.
+ //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.
+ //! \return Pointer to allocated char array. This pointer will never be NULL.
+ Ch *allocate_string(const Ch *source = 0, std::size_t size = 0)
+ {
+ assert(source || size); // Either source or size (or both) must be specified
+ if (size == 0)
+ size = internal::measure(source) + 1;
+ Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch)));
+ if (source)
+ for (std::size_t i = 0; i < size; ++i)
+ result[i] = source[i];
+ return result;
+ }
+
+ //! Clones an xml_node and its hierarchy of child nodes and attributes.
+ //! Nodes and attributes are allocated from this memory pool.
+ //! Names and values are not cloned, they are shared between the clone and the source.
+ //! Result node can be optionally specified as a second parameter,
+ //! in which case its contents will be replaced with cloned source node.
+ //! This is useful when you want to clone entire document.
+ //! \param source Node to clone.
+ //! \param result Node to put results in, or 0 to automatically allocate result node
+ //! \return Pointer to cloned node. This pointer will never be NULL.
+ xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0)
+ {
+ // Prepare result node
+ if (result)
+ {
+ result->remove_all_attributes();
+ result->remove_all_nodes();
+ result->type(source->type());
+ }
+ else
+ result = allocate_node(source->type());
+
+ // Clone name and value
+ result->name(source->name(), source->name_size());
+ result->value(source->value(), source->value_size());
+
+ // Clone child nodes and attributes
+ for (xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling())
+ result->append_node(clone_node(child));
+ for (xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute())
+ result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size()));
+
+ return result;
+ }
+
+ //! Clears the pool.
+ //! This causes memory occupied by nodes allocated by the pool to be freed.
+ //! Any nodes or strings allocated from the pool will no longer be valid.
+ void clear()
+ {
+ while (m_begin != m_static_memory)
+ {
+ char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin;
+ if (m_free_func)
+ m_free_func(m_begin);
+ else
+ delete[] m_begin;
+ m_begin = previous_begin;
+ }
+ init();
+ }
+
+ //! Sets or resets the user-defined memory allocation functions for the pool.
+ //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined.
+ //! Allocation function must not return invalid pointer on failure. It should either throw,
+ //! stop the program, or use <code>longjmp()</code> function to pass control to other place of program.
+ //! If it returns invalid pointer, results are undefined.
+ //! <br><br>
+ //! User defined allocation functions must have the following forms:
+ //! <br><code>
+ //! <br>void *allocate(std::size_t size);
+ //! <br>void free(void *pointer);
+ //! </code><br>
+ //! \param af Allocation function, or 0 to restore default function
+ //! \param ff Free function, or 0 to restore default function
+ void set_allocator(alloc_func *af, free_func *ff)
+ {
+ assert(m_begin == m_static_memory && m_ptr == align(m_begin)); // Verify that no memory is allocated yet
+ m_alloc_func = af;
+ m_free_func = ff;
+ }
+
+ private:
+
+ struct header
+ {
+ char *previous_begin;
+ };
+
+ void init()
+ {
+ m_begin = m_static_memory;
+ m_ptr = align(m_begin);
+ m_end = m_static_memory + sizeof(m_static_memory);
+ }
+
+ char *align(char *ptr)
+ {
+ std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1));
+ return ptr + alignment;
+ }
+
+ char *allocate_raw(std::size_t size)
+ {
+ // Allocate
+ void *memory;
+ if (m_alloc_func) // Allocate memory using either user-specified allocation function or global operator new[]
+ {
+ memory = m_alloc_func(size);
+ assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp
+ }
+ else
+ {
+ memory = new char[size];
+#ifdef RAPIDXML_NO_EXCEPTIONS
+ if (!memory) // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc
+ RAPIDXML_PARSE_ERROR("out of memory", 0);
+#endif
+ }
+ return static_cast<char *>(memory);
+ }
+
+ void *allocate_aligned(std::size_t size)
+ {
+ // Calculate aligned pointer
+ char *result = align(m_ptr);
+
+ // If not enough memory left in current pool, allocate a new pool
+ if (result + size > m_end)
+ {
+ // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE)
+ std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE;
+ if (pool_size < size)
+ pool_size = size;
+
+ // Allocate
+ std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size; // 2 alignments required in worst case: one for header, one for actual allocation
+ char *raw_memory = allocate_raw(alloc_size);
+
+ // Setup new pool in allocated memory
+ char *pool = align(raw_memory);
+ header *new_header = reinterpret_cast<header *>(pool);
+ new_header->previous_begin = m_begin;
+ m_begin = raw_memory;
+ m_ptr = pool + sizeof(header);
+ m_end = raw_memory + alloc_size;
+
+ // Calculate aligned pointer again using new pool
+ result = align(m_ptr);
+ }
+
+ // Update pool and return aligned pointer
+ m_ptr = result + size;
+ return result;
+ }
+
+ char *m_begin; // Start of raw memory making up current pool
+ char *m_ptr; // First free byte in current pool
+ char *m_end; // One past last available byte in current pool
+ char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; // Static raw memory
+ alloc_func *m_alloc_func; // Allocator function, or 0 if default is to be used
+ free_func *m_free_func; // Free function, or 0 if default is to be used
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML base
+
+ //! Base class for xml_node and xml_attribute implementing common functions:
+ //! name(), name_size(), value(), value_size() and parent().
+ //! \param Ch Character type to use
+ template<class Ch = char>
+ class xml_base
+ {
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ // Construct a base with empty name, value and parent
+ xml_base() : m_name(0), m_value(0), m_name_size(0), m_value_size(0), m_parent(0)
+ {
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node data access
+
+ //! Gets name of the node.
+ //! Interpretation of name depends on type of node.
+ //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+ //! <br><br>
+ //! Use name_size() function to determine length of the name.
+ //! \return Name of node, or empty string if node has no name.
+ Ch *name() const
+ {
+ return m_name ? m_name : nullstr();
+ }
+
+ //! Gets size of node name, not including terminator character.
+ //! This function works correctly irrespective of whether name is or is not zero terminated.
+ //! \return Size of node name, in characters.
+ std::size_t name_size() const
+ {
+ return m_name ? m_name_size : 0;
+ }
+
+ //! Gets value of node.
+ //! Interpretation of value depends on type of node.
+ //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+ //! <br><br>
+ //! Use value_size() function to determine length of the value.
+ //! \return Value of node, or empty string if node has no value.
+ Ch *value() const
+ {
+ return m_value ? m_value : nullstr();
+ }
+
+ //! Gets size of node value, not including terminator character.
+ //! This function works correctly irrespective of whether value is or is not zero terminated.
+ //! \return Size of node value, in characters.
+ std::size_t value_size() const
+ {
+ return m_value ? m_value_size : 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node modification
+
+ //! Sets name of node to a non zero-terminated string.
+ //! See \ref ownership_of_strings.
+ //! <br><br>
+ //! Note that node does not own its name or value, it only stores a pointer to it.
+ //! It will not delete or otherwise free the pointer on destruction.
+ //! It is reponsibility of the user to properly manage lifetime of the string.
+ //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+ //! on destruction of the document the string will be automatically freed.
+ //! <br><br>
+ //! Size of name must be specified separately, because name does not have to be zero terminated.
+ //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+ //! \param name Name of node to set. Does not have to be zero terminated.
+ //! \param size Size of name, in characters. This does not include zero terminator, if one is present.
+ void name(const Ch *name, std::size_t size)
+ {
+ m_name = const_cast<Ch *>(name);
+ m_name_size = size;
+ }
+
+ //! Sets name of node to a zero-terminated string.
+ //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t).
+ //! \param name Name of node to set. Must be zero terminated.
+ void name(const Ch *name)
+ {
+ this->name(name, internal::measure(name));
+ }
+
+ //! Sets value of node to a non zero-terminated string.
+ //! See \ref ownership_of_strings.
+ //! <br><br>
+ //! Note that node does not own its name or value, it only stores a pointer to it.
+ //! It will not delete or otherwise free the pointer on destruction.
+ //! It is reponsibility of the user to properly manage lifetime of the string.
+ //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+ //! on destruction of the document the string will be automatically freed.
+ //! <br><br>
+ //! Size of value must be specified separately, because it does not have to be zero terminated.
+ //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+ //! <br><br>
+ //! If an element has a child node of type node_type::node_data, it will take precedence over element value when printing.
+ //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.
+ //! \param value value of node to set. Does not have to be zero terminated.
+ //! \param size Size of value, in characters. This does not include zero terminator, if one is present.
+ void value(const Ch *value, std::size_t size)
+ {
+ m_value = const_cast<Ch *>(value);
+ m_value_size = size;
+ }
+
+ //! Sets value of node to a zero-terminated string.
+ //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t).
+ //! \param value Vame of node to set. Must be zero terminated.
+ void value(const Ch *value)
+ {
+ this->value(value, internal::measure(value));
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets node parent.
+ //! \return Pointer to parent node, or 0 if there is no parent.
+ xml_node<Ch> *parent() const
+ {
+ return m_parent;
+ }
+
+ protected:
+
+ // Return empty string
+ static Ch *nullstr()
+ {
+ static Ch zero = Ch('\0');
+ return &zero;
+ }
+
+ Ch *m_name; // Name of node, or 0 if no name
+ Ch *m_value; // Value of node, or 0 if no value
+ std::size_t m_name_size; // Length of node name, or undefined of no name
+ std::size_t m_value_size; // Length of node value, or undefined if no value
+ xml_node<Ch> *m_parent; // Pointer to parent node, or 0 if none
+
+ };
+
+ //! Class representing attribute node of XML document.
+ //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base).
+ //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing.
+ //! Thus, this text must persist in memory for the lifetime of attribute.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_attribute: public xml_base<Ch>
+ {
+
+ friend class xml_node<Ch>;
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ //! Constructs an empty attribute with the specified type.
+ //! Consider using memory_pool of appropriate xml_document if allocating attributes manually.
+ xml_attribute() : m_prev_attribute(nullptr), m_next_attribute(nullptr)
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets document of which attribute is a child.
+ //! \return Pointer to document that contains this attribute, or 0 if there is no parent document.
+ xml_document<Ch> *document() const
+ {
+ if (xml_node<Ch> *node = this->parent())
+ {
+ while (node->parent())
+ node = node->parent();
+ return node->type() == node_type::node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+ }
+ else
+ return 0;
+ }
+
+ //! Gets previous attribute, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return this->m_parent ? m_prev_attribute : 0;
+ }
+
+ //! Gets next attribute, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return this->m_parent ? m_next_attribute : 0;
+ }
+
+ private:
+
+ xml_attribute<Ch> *m_prev_attribute; // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero
+ xml_attribute<Ch> *m_next_attribute; // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero
+
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML node
+
+ //! Class representing a node of XML document.
+ //! Each node may have associated name and value strings, which are available through name() and value() functions.
+ //! Interpretation of name and value depends on type of the node.
+ //! Type of node can be determined by using type() function.
+ //! <br><br>
+ //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing.
+ //! Thus, this text must persist in the memory for the lifetime of node.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_node: public xml_base<Ch>
+ {
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ //! Constructs an empty node with the specified type.
+ //! Consider using memory_pool of appropriate document to allocate nodes manually.
+ //! \param type Type of node to construct.
+ xml_node(node_type type) : m_type(type), m_first_node(0), m_last_node(nullptr), m_first_attribute(0), m_last_attribute(nullptr), m_prev_sibling(nullptr), m_next_sibling(nullptr)
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node data access
+
+ //! Gets type of node.
+ //! \return Type of node.
+ node_type type() const
+ {
+ return m_type;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets document of which node is a child.
+ //! \return Pointer to document that contains this node, or 0 if there is no parent document.
+ xml_document<Ch> *document() const
+ {
+ xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this);
+ while (node->parent())
+ node = node->parent();
+ return node->type() == node_type::node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+ }
+
+ //! Gets first child node, optionally matching node name.
+ //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found child, or 0 if not found.
+ xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *child = m_first_node; child; child = child->next_sibling())
+ if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+ return child;
+ return 0;
+ }
+ else
+ return m_first_node;
+ }
+
+ //! Gets last child node, optionally matching node name.
+ //! Behaviour is undefined if node has no children.
+ //! Use first_node() to test if node has children.
+ //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found child, or 0 if not found.
+ xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(m_first_node); // Cannot query for last child if node has no children
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling())
+ if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+ return child;
+ return 0;
+ }
+ else
+ return m_last_node;
+ }
+
+ //! Gets previous sibling node, optionally matching node name.
+ //! Behaviour is undefined if node has no parent.
+ //! Use parent() to test if node has a parent.
+ //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found sibling, or 0 if not found.
+ xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(this->m_parent); // Cannot query for siblings if node has no parent
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling)
+ if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+ return sibling;
+ return 0;
+ }
+ else
+ return m_prev_sibling;
+ }
+
+ //! Gets next sibling node, optionally matching node name.
+ //! Behaviour is undefined if node has no parent.
+ //! Use parent() to test if node has a parent.
+ //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found sibling, or 0 if not found.
+ xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(this->m_parent); // Cannot query for siblings if node has no parent
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)
+ if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+ return sibling;
+ return 0;
+ }
+ else
+ return m_next_sibling;
+ }
+
+ //! Gets first attribute of node, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return m_first_attribute;
+ }
+
+ //! Gets last attribute of node, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return m_first_attribute ? m_last_attribute : 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node modification
+
+ //! Sets type of node.
+ //! \param type Type of node to set.
+ void type(node_type type)
+ {
+ m_type = type;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node manipulation
+
+ //! Prepends a new child node.
+ //! The prepended child becomes the first child, and all existing children are moved one position back.
+ //! \param child Node to prepend.
+ void prepend_node(xml_node<Ch> *child)
+ {
+ assert(child && !child->parent() && child->type() != node_type::node_document);
+ if (first_node())
+ {
+ child->m_next_sibling = m_first_node;
+ m_first_node->m_prev_sibling = child;
+ }
+ else
+ {
+ child->m_next_sibling = 0;
+ m_last_node = child;
+ }
+ m_first_node = child;
+ child->m_parent = this;
+ child->m_prev_sibling = 0;
+ }
+
+ //! Appends a new child node.
+ //! The appended child becomes the last child.
+ //! \param child Node to append.
+ void append_node(xml_node<Ch> *child)
+ {
+ assert(child && !child->parent() && child->type() != node_type::node_document);
+ if (first_node())
+ {
+ child->m_prev_sibling = m_last_node;
+ m_last_node->m_next_sibling = child;
+ }
+ else
+ {
+ child->m_prev_sibling = 0;
+ m_first_node = child;
+ }
+ m_last_node = child;
+ child->m_parent = this;
+ child->m_next_sibling = 0;
+ }
+
+ //! Inserts a new child node at specified place inside the node.
+ //! All children after and including the specified node are moved one position back.
+ //! \param where Place where to insert the child, or 0 to insert at the back.
+ //! \param child Node to insert.
+ void insert_node(xml_node<Ch> *where, xml_node<Ch> *child)
+ {
+ assert(!where || where->parent() == this);
+ assert(child && !child->parent() && child->type() != node_type::node_document);
+ if (where == m_first_node)
+ prepend_node(child);
+ else if (where == 0)
+ append_node(child);
+ else
+ {
+ child->m_prev_sibling = where->m_prev_sibling;
+ child->m_next_sibling = where;
+ where->m_prev_sibling->m_next_sibling = child;
+ where->m_prev_sibling = child;
+ child->m_parent = this;
+ }
+ }
+
+ //! Removes first child node.
+ //! If node has no children, behaviour is undefined.
+ //! Use first_node() to test if node has children.
+ void remove_first_node()
+ {
+ assert(first_node());
+ xml_node<Ch> *child = m_first_node;
+ m_first_node = child->m_next_sibling;
+ if (child->m_next_sibling)
+ child->m_next_sibling->m_prev_sibling = 0;
+ else
+ m_last_node = 0;
+ child->m_parent = 0;
+ }
+
+ //! Removes last child of the node.
+ //! If node has no children, behaviour is undefined.
+ //! Use first_node() to test if node has children.
+ void remove_last_node()
+ {
+ assert(first_node());
+ xml_node<Ch> *child = m_last_node;
+ if (child->m_prev_sibling)
+ {
+ m_last_node = child->m_prev_sibling;
+ child->m_prev_sibling->m_next_sibling = 0;
+ }
+ else
+ m_first_node = 0;
+ child->m_parent = 0;
+ }
+
+ //! Removes specified child from the node
+ // \param where Pointer to child to be removed.
+ void remove_node(xml_node<Ch> *where)
+ {
+ assert(where && where->parent() == this);
+ assert(first_node());
+ if (where == m_first_node)
+ remove_first_node();
+ else if (where == m_last_node)
+ remove_last_node();
+ else
+ {
+ where->m_prev_sibling->m_next_sibling = where->m_next_sibling;
+ where->m_next_sibling->m_prev_sibling = where->m_prev_sibling;
+ where->m_parent = 0;
+ }
+ }
+
+ //! Removes all child nodes (but not attributes).
+ void remove_all_nodes()
+ {
+ for (xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling)
+ node->m_parent = 0;
+ m_first_node = 0;
+ }
+
+ //! Prepends a new attribute to the node.
+ //! \param attribute Attribute to prepend.
+ void prepend_attribute(xml_attribute<Ch> *attribute)
+ {
+ assert(attribute && !attribute->parent());
+ if (first_attribute())
+ {
+ attribute->m_next_attribute = m_first_attribute;
+ m_first_attribute->m_prev_attribute = attribute;
+ }
+ else
+ {
+ attribute->m_next_attribute = 0;
+ m_last_attribute = attribute;
+ }
+ m_first_attribute = attribute;
+ attribute->m_parent = this;
+ attribute->m_prev_attribute = 0;
+ }
+
+ //! Appends a new attribute to the node.
+ //! \param attribute Attribute to append.
+ void append_attribute(xml_attribute<Ch> *attribute)
+ {
+ assert(attribute && !attribute->parent());
+ if (first_attribute())
+ {
+ attribute->m_prev_attribute = m_last_attribute;
+ m_last_attribute->m_next_attribute = attribute;
+ }
+ else
+ {
+ attribute->m_prev_attribute = 0;
+ m_first_attribute = attribute;
+ }
+ m_last_attribute = attribute;
+ attribute->m_parent = this;
+ attribute->m_next_attribute = 0;
+ }
+
+ //! Inserts a new attribute at specified place inside the node.
+ //! All attributes after and including the specified attribute are moved one position back.
+ //! \param where Place where to insert the attribute, or 0 to insert at the back.
+ //! \param attribute Attribute to insert.
+ void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute)
+ {
+ assert(!where || where->parent() == this);
+ assert(attribute && !attribute->parent());
+ if (where == m_first_attribute)
+ prepend_attribute(attribute);
+ else if (where == 0)
+ append_attribute(attribute);
+ else
+ {
+ attribute->m_prev_attribute = where->m_prev_attribute;
+ attribute->m_next_attribute = where;
+ where->m_prev_attribute->m_next_attribute = attribute;
+ where->m_prev_attribute = attribute;
+ attribute->m_parent = this;
+ }
+ }
+
+ //! Removes first attribute of the node.
+ //! If node has no attributes, behaviour is undefined.
+ //! Use first_attribute() to test if node has attributes.
+ void remove_first_attribute()
+ {
+ assert(first_attribute());
+ xml_attribute<Ch> *attribute = m_first_attribute;
+ if (attribute->m_next_attribute)
+ {
+ attribute->m_next_attribute->m_prev_attribute = 0;
+ }
+ else
+ m_last_attribute = 0;
+ attribute->m_parent = 0;
+ m_first_attribute = attribute->m_next_attribute;
+ }
+
+ //! Removes last attribute of the node.
+ //! If node has no attributes, behaviour is undefined.
+ //! Use first_attribute() to test if node has attributes.
+ void remove_last_attribute()
+ {
+ assert(first_attribute());
+ xml_attribute<Ch> *attribute = m_last_attribute;
+ if (attribute->m_prev_attribute)
+ {
+ attribute->m_prev_attribute->m_next_attribute = 0;
+ m_last_attribute = attribute->m_prev_attribute;
+ }
+ else
+ m_first_attribute = 0;
+ attribute->m_parent = 0;
+ }
+
+ //! Removes specified attribute from node.
+ //! \param where Pointer to attribute to be removed.
+ void remove_attribute(xml_attribute<Ch> *where)
+ {
+ assert(first_attribute() && where->parent() == this);
+ if (where == m_first_attribute)
+ remove_first_attribute();
+ else if (where == m_last_attribute)
+ remove_last_attribute();
+ else
+ {
+ where->m_prev_attribute->m_next_attribute = where->m_next_attribute;
+ where->m_next_attribute->m_prev_attribute = where->m_prev_attribute;
+ where->m_parent = 0;
+ }
+ }
+
+ //! Removes all attributes of node.
+ void remove_all_attributes()
+ {
+ for (xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute)
+ attribute->m_parent = 0;
+ m_first_attribute = 0;
+ }
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Restrictions
+
+ // No copying
+ xml_node(const xml_node &);
+ void operator =(const xml_node &);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Data members
+
+ // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0.
+ // This is required for maximum performance, as it allows the parser to omit initialization of
+ // unneded/redundant values.
+ //
+ // The rules are as follows:
+ // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively
+ // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage
+ // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage
+
+ node_type m_type; // Type of node; always valid
+ xml_node<Ch> *m_first_node; // Pointer to first child node, or 0 if none; always valid
+ xml_node<Ch> *m_last_node; // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero
+ xml_attribute<Ch> *m_first_attribute; // Pointer to first attribute of node, or 0 if none; always valid
+ xml_attribute<Ch> *m_last_attribute; // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero
+ xml_node<Ch> *m_prev_sibling; // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+ xml_node<Ch> *m_next_sibling; // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML document
+
+ //! This class represents root of the DOM hierarchy.
+ //! It is also an xml_node and a memory_pool through public inheritance.
+ //! Use parse() function to build a DOM tree from a zero-terminated XML text string.
+ //! parse() function allocates memory for nodes and attributes by using functions of xml_document,
+ //! which are inherited from memory_pool.
+ //! To access root node of the document, use the document itself, as if it was an xml_node.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_document: public xml_node<Ch>, public memory_pool<Ch>
+ {
+
+ public:
+
+ //! Constructs empty XML document
+ xml_document()
+ : xml_node<Ch>(node_type::node_document)
+ {
+ }
+
+ //! Parses zero-terminated XML string according to given flags.
+ //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used.
+ //! The string must persist for the lifetime of the document.
+ //! In case of error, rapidxml::parse_error exception will be thrown.
+ //! <br><br>
+ //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning.
+ //! Make sure that data is zero-terminated.
+ //! <br><br>
+ //! Document can be parsed into multiple times.
+ //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.
+ //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.
+ template<int Flags>
+ void parse(Ch *text)
+ {
+ assert(text);
+
+ // Remove current contents
+ this->remove_all_nodes();
+ this->remove_all_attributes();
+
+ // Parse BOM, if any
+ parse_bom<Flags>(text);
+
+ // Parse children
+ while (1)
+ {
+ // Skip whitespace before node
+ skip<whitespace_pred, Flags>(text);
+ if (*text == 0)
+ break;
+
+ // Parse and append new child
+ if (*text == Ch('<'))
+ {
+ ++text; // Skip '<'
+ if (xml_node<Ch> *node = parse_node<Flags>(text))
+ this->append_node(node);
+ }
+ else
+ RAPIDXML_PARSE_ERROR("expected <", text);
+ }
+
+ }
+
+ //! Clears the document by deleting all nodes and clearing the memory pool.
+ //! All nodes owned by document pool are destroyed.
+ void clear()
+ {
+ this->remove_all_nodes();
+ this->remove_all_attributes();
+ memory_pool<Ch>::clear();
+ }
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internal character utility functions
+
+ // Detect whitespace character
+ struct whitespace_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect node name character
+ struct node_name_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect attribute name character
+ struct attribute_name_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA)
+ struct text_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA) that does not require processing
+ struct text_pure_no_ws_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA) that does not require processing
+ struct text_pure_with_ws_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect attribute value character
+ template<Ch Quote>
+ struct attribute_value_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ if (Quote == Ch('\''))
+ return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
+ if (Quote == Ch('\"'))
+ return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
+ return 0; // Should never be executed, to avoid warnings on Comeau
+ }
+ };
+
+ // Detect attribute value character
+ template<Ch Quote>
+ struct attribute_value_pure_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ if (Quote == Ch('\''))
+ return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)];
+ if (Quote == Ch('\"'))
+ return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)];
+ return 0; // Should never be executed, to avoid warnings on Comeau
+ }
+ };
+
+ // Insert coded character, using UTF8 or 8-bit ASCII
+ template<int Flags>
+ static void insert_coded_character(Ch *&text, unsigned long code)
+ {
+ if (Flags & parse_no_utf8)
+ {
+ // Insert 8-bit ASCII character
+ // Todo: possibly verify that code is less than 256 and use replacement char otherwise?
+ text[0] = static_cast<char>(code);
+ text += 1;
+ }
+ else
+ {
+ // Insert UTF8 sequence
+ if (code < 0x80) // 1 byte sequence
+ {
+ text[0] = static_cast<char>(code);
+ text += 1;
+ }
+ else if (code < 0x800) // 2 byte sequence
+ {
+ text[1] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<char>(code | 0xC0);
+ text += 2;
+ }
+ else if (code < 0x10000) // 3 byte sequence
+ {
+ text[2] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[1] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<char>(code | 0xE0);
+ text += 3;
+ }
+ else if (code < 0x110000) // 4 byte sequence
+ {
+ text[3] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[2] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[1] = static_cast<char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<char>(code | 0xF0);
+ text += 4;
+ }
+ else // Invalid, only codes up to 0x10FFFF are allowed in Unicode
+ {
+ RAPIDXML_PARSE_ERROR("invalid numeric character entity", text);
+ }
+ }
+ }
+
+ // Skip characters until predicate evaluates to true
+ template<class StopPred, int Flags>
+ static void skip(Ch *&text)
+ {
+ Ch *tmp = text;
+ while (StopPred::test(*tmp))
+ ++tmp;
+ text = tmp;
+ }
+
+ // Skip characters until predicate evaluates to true while doing the following:
+ // - replacing XML character entity references with proper characters (&apos; &amp; &quot; &lt; &gt; &#...;)
+ // - condensing whitespace sequences to single space character
+ template<class StopPred, class StopPredPure, int Flags>
+ static Ch *skip_and_expand_character_refs(Ch *&text)
+ {
+ // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip
+ if (Flags & parse_no_entity_translation &&
+ !(Flags & parse_normalize_whitespace) &&
+ !(Flags & parse_trim_whitespace))
+ {
+ skip<StopPred, Flags>(text);
+ return text;
+ }
+
+ // Use simple skip until first modification is detected
+ skip<StopPredPure, Flags>(text);
+
+ // Use translation skip
+ Ch *src = text;
+ Ch *dest = src;
+ while (StopPred::test(*src))
+ {
+ // If entity translation is enabled
+ if (!(Flags & parse_no_entity_translation))
+ {
+ // Test if replacement is needed
+ if (src[0] == Ch('&'))
+ {
+ switch (src[1])
+ {
+
+ // &amp; &apos;
+ case Ch('a'):
+ if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';'))
+ {
+ *dest = Ch('&');
+ ++dest;
+ src += 5;
+ continue;
+ }
+ if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';'))
+ {
+ *dest = Ch('\'');
+ ++dest;
+ src += 6;
+ continue;
+ }
+ break;
+
+ // &quot;
+ case Ch('q'):
+ if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';'))
+ {
+ *dest = Ch('"');
+ ++dest;
+ src += 6;
+ continue;
+ }
+ break;
+
+ // &gt;
+ case Ch('g'):
+ if (src[2] == Ch('t') && src[3] == Ch(';'))
+ {
+ *dest = Ch('>');
+ ++dest;
+ src += 4;
+ continue;
+ }
+ break;
+
+ // &lt;
+ case Ch('l'):
+ if (src[2] == Ch('t') && src[3] == Ch(';'))
+ {
+ *dest = Ch('<');
+ ++dest;
+ src += 4;
+ continue;
+ }
+ break;
+
+ // &#...; - assumes ASCII
+ case Ch('#'):
+ if (src[2] == Ch('x'))
+ {
+ unsigned long code = 0;
+ src += 3; // Skip &#x
+ while (1)
+ {
+ unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+ if (digit == 0xFF)
+ break;
+ code = code * 16 + digit;
+ ++src;
+ }
+ insert_coded_character<Flags>(dest, code); // Put character in output
+ }
+ else
+ {
+ unsigned long code = 0;
+ src += 2; // Skip &#
+ while (1)
+ {
+ unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+ if (digit == 0xFF)
+ break;
+ code = code * 10 + digit;
+ ++src;
+ }
+ insert_coded_character<Flags>(dest, code); // Put character in output
+ }
+ if (*src == Ch(';'))
+ ++src;
+ else
+ RAPIDXML_PARSE_ERROR("expected ;", src);
+ continue;
+
+ // Something else
+ default:
+ // Ignore, just copy '&' verbatim
+ break;
+
+ }
+ }
+ }
+
+ // If whitespace condensing is enabled
+ if (Flags & parse_normalize_whitespace)
+ {
+ // Test if condensing is needed
+ if (whitespace_pred::test(*src))
+ {
+ *dest = Ch(' '); ++dest; // Put single space in dest
+ ++src; // Skip first whitespace char
+ // Skip remaining whitespace chars
+ while (whitespace_pred::test(*src))
+ ++src;
+ continue;
+ }
+ }
+
+ // No replacement, only copy character
+ *dest++ = *src++;
+
+ }
+
+ // Return new end
+ text = src;
+ return dest;
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internal parsing functions
+
+ // Parse BOM, if any
+ template<int Flags>
+ void parse_bom(Ch *&text)
+ {
+ // UTF-8?
+ if (static_cast<unsigned char>(text[0]) == 0xEF &&
+ static_cast<unsigned char>(text[1]) == 0xBB &&
+ static_cast<unsigned char>(text[2]) == 0xBF)
+ {
+ text += 3; // Skup utf-8 bom
+ }
+ }
+
+ // Parse XML declaration (<?xml...)
+ template<int Flags>
+ xml_node<Ch> *parse_xml_declaration(Ch *&text)
+ {
+ // If parsing of declaration is disabled
+ if (!(Flags & parse_declaration_node))
+ {
+ // Skip until end of declaration
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 2; // Skip '?>'
+ return 0;
+ }
+
+ // Create declaration
+ xml_node<Ch> *declaration = this->allocate_node(node_type::node_declaration);
+
+ // Skip whitespace before attributes or ?>
+ skip<whitespace_pred, Flags>(text);
+
+ // Parse declaration attributes
+ parse_node_attributes<Flags>(text, declaration);
+
+ // Skip ?>
+ if (text[0] != Ch('?') || text[1] != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected ?>", text);
+ text += 2;
+
+ return declaration;
+ }
+
+ // Parse XML comment (<!--...)
+ template<int Flags>
+ xml_node<Ch> *parse_comment(Ch *&text)
+ {
+ // If parsing of comments is disabled
+ if (!(Flags & parse_comment_nodes))
+ {
+ // Skip until end of comment
+ while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 3; // Skip '-->'
+ return 0; // Do not produce comment node
+ }
+
+ // Remember value start
+ Ch *value = text;
+
+ // Skip until end of comment
+ while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Create comment node
+ xml_node<Ch> *comment = this->allocate_node(node_type::node_comment);
+ comment->value(value, static_cast<std::size_t>(text - value));
+
+ // Place zero terminator after comment value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 3; // Skip '-->'
+ return comment;
+ }
+
+ // Parse DOCTYPE
+ template<int Flags>
+ xml_node<Ch> *parse_doctype(Ch *&text)
+ {
+ // Remember value start
+ Ch *value = text;
+
+ // Skip to >
+ while (*text != Ch('>'))
+ {
+ // Determine character type
+ switch (*text)
+ {
+
+ // If '[' encountered, scan for matching ending ']' using naive algorithm with depth
+ // This works for all W3C test files except for 2 most wicked
+ case Ch('['):
+ {
+ ++text; // Skip '['
+ int depth = 1;
+ while (depth > 0)
+ {
+ switch (*text)
+ {
+ case Ch('['): ++depth; break;
+ case Ch(']'): --depth; break;
+ case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ }
+ ++text;
+ }
+ break;
+ }
+
+ // Error on end of text
+ case Ch('\0'):
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+
+ // Other character, skip it
+ default:
+ ++text;
+
+ }
+ }
+
+ // If DOCTYPE nodes enabled
+ if (Flags & parse_doctype_node)
+ {
+ // Create a new doctype node
+ xml_node<Ch> *doctype = this->allocate_node(node_type::node_doctype);
+ doctype->value(value, static_cast<std::size_t>(text - value));
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 1; // skip '>'
+ return doctype;
+ }
+ else
+ {
+ text += 1; // skip '>'
+ return 0;
+ }
+
+ }
+
+ // Parse PI
+ template<int Flags>
+ xml_node<Ch> *parse_pi(Ch *&text)
+ {
+ // If creation of PI nodes is enabled
+ if (Flags & parse_pi_nodes)
+ {
+ // Create pi node
+ xml_node<Ch> *pi = this->allocate_node(node_type::node_pi);
+
+ // Extract PI target name
+ Ch *name = text;
+ skip<node_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected PI target", text);
+ pi->name(name, static_cast<std::size_t>(text - name));
+
+ // Skip whitespace between pi target and pi
+ skip<whitespace_pred, Flags>(text);
+
+ // Remember start of pi
+ Ch *value = text;
+
+ // Skip to '?>'
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (*text == Ch('\0'))
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Set pi value (verbatim, no entity expansion or whitespace normalization)
+ pi->value(value, static_cast<std::size_t>(text - value));
+
+ // Place zero terminator after name and value
+ if (!(Flags & parse_no_string_terminators))
+ {
+ pi->name()[pi->name_size()] = Ch('\0');
+ pi->value()[pi->value_size()] = Ch('\0');
+ }
+
+ text += 2; // Skip '?>'
+ return pi;
+ }
+ else
+ {
+ // Skip to '?>'
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (*text == Ch('\0'))
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 2; // Skip '?>'
+ return 0;
+ }
+ }
+
+ // Parse and append data
+ // Return character that ends data.
+ // This is necessary because this character might have been overwritten by a terminating 0
+ template<int Flags>
+ Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start)
+ {
+ // Backup to contents start if whitespace trimming is disabled
+ if (!(Flags & parse_trim_whitespace))
+ text = contents_start;
+
+ // Skip until end of data
+ Ch *value = text, *end;
+ if (Flags & parse_normalize_whitespace)
+ end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text);
+ else
+ end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text);
+
+ // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after >
+ if (Flags & parse_trim_whitespace)
+ {
+ if (Flags & parse_normalize_whitespace)
+ {
+ // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end
+ if (*(end - 1) == Ch(' '))
+ --end;
+ }
+ else
+ {
+ // Backup until non-whitespace character is found
+ while (whitespace_pred::test(*(end - 1)))
+ --end;
+ }
+ }
+
+ // If characters are still left between end and value (this test is only necessary if normalization is enabled)
+ // Create new data node
+ if (!(Flags & parse_no_data_nodes))
+ {
+ xml_node<Ch> *data = this->allocate_node(node_type::node_data);
+ data->value(value, static_cast<std::size_t>(end - value));
+ node->append_node(data);
+ }
+
+ // Add data to parent node if no data exists yet
+ if (!(Flags & parse_no_element_values))
+ if (*node->value() == Ch('\0'))
+ node->value(value, static_cast<std::size_t>(end - value));
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ {
+ Ch ch = *text;
+ *end = Ch('\0');
+ return ch; // Return character that ends data; this is required because zero terminator overwritten it
+ }
+
+ // Return character that ends data
+ return *text;
+ }
+
+ // Parse CDATA
+ template<int Flags>
+ xml_node<Ch> *parse_cdata(Ch *&text)
+ {
+ // If CDATA is disabled
+ if (Flags & parse_no_data_nodes)
+ {
+ // Skip until end of cdata
+ while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 3; // Skip ]]>
+ return 0; // Do not produce CDATA node
+ }
+
+ // Skip until end of cdata
+ Ch *value = text;
+ while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Create new cdata node
+ xml_node<Ch> *cdata = this->allocate_node(node_type::node_cdata);
+ cdata->value(value, static_cast<std::size_t>(text - value));
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 3; // Skip ]]>
+ return cdata;
+ }
+
+ // Parse element node
+ template<int Flags>
+ xml_node<Ch> *parse_element(Ch *&text)
+ {
+ // Create element node
+ xml_node<Ch> *element = this->allocate_node(node_type::node_element);
+
+ // Extract element name
+ Ch *name = text;
+ skip<node_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected element name", text);
+ element->name(name, static_cast<std::size_t>(text - name));
+
+ // Skip whitespace between element name and attributes or >
+ skip<whitespace_pred, Flags>(text);
+
+ // Parse attributes, if any
+ parse_node_attributes<Flags>(text, element);
+
+ // Determine ending type
+ if (*text == Ch('>'))
+ {
+ ++text;
+ parse_node_contents<Flags>(text, element);
+ }
+ else if (*text == Ch('/'))
+ {
+ ++text;
+ if (*text != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected >", text);
+ ++text;
+ }
+ else
+ RAPIDXML_PARSE_ERROR("expected >", text);
+
+ // Place zero terminator after name
+ if (!(Flags & parse_no_string_terminators))
+ element->name()[element->name_size()] = Ch('\0');
+
+ // Return parsed element
+ return element;
+ }
+
+ // Determine node type, and parse it
+ template<int Flags>
+ xml_node<Ch> *parse_node(Ch *&text)
+ {
+ // Parse proper node type
+ switch (text[0])
+ {
+
+ // <...
+ default:
+ // Parse and append element node
+ return parse_element<Flags>(text);
+
+ // <?...
+ case Ch('?'):
+ ++text; // Skip ?
+ if ((text[0] == Ch('x') || text[0] == Ch('X')) &&
+ (text[1] == Ch('m') || text[1] == Ch('M')) &&
+ (text[2] == Ch('l') || text[2] == Ch('L')) &&
+ whitespace_pred::test(text[3]))
+ {
+ // '<?xml ' - xml declaration
+ text += 4; // Skip 'xml '
+ return parse_xml_declaration<Flags>(text);
+ }
+ else
+ {
+ // Parse PI
+ return parse_pi<Flags>(text);
+ }
+
+ // <!...
+ case Ch('!'):
+
+ // Parse proper subset of <! node
+ switch (text[1])
+ {
+
+ // <!-
+ case Ch('-'):
+ if (text[2] == Ch('-'))
+ {
+ // '<!--' - xml comment
+ text += 3; // Skip '!--'
+ return parse_comment<Flags>(text);
+ }
+ break;
+
+ // <![
+ case Ch('['):
+ if (text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') &&
+ text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('['))
+ {
+ // '<![CDATA[' - cdata
+ text += 8; // Skip '![CDATA['
+ return parse_cdata<Flags>(text);
+ }
+ break;
+
+ // <!D
+ case Ch('D'):
+ if (text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') &&
+ text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') &&
+ whitespace_pred::test(text[8]))
+ {
+ // '<!DOCTYPE ' - doctype
+ text += 9; // skip '!DOCTYPE '
+ return parse_doctype<Flags>(text);
+ }
+
+ } // switch
+
+ // Attempt to skip other, unrecognized node types starting with <!
+ ++text; // Skip !
+ while (*text != Ch('>'))
+ {
+ if (*text == 0)
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ ++text; // Skip '>'
+ return 0; // No node recognized
+
+ }
+ }
+
+ // Parse contents of the node - children, data etc.
+ template<int Flags>
+ void parse_node_contents(Ch *&text, xml_node<Ch> *node)
+ {
+ // For all children and text
+ while (1)
+ {
+ // Skip whitespace between > and node contents
+ Ch *contents_start = text; // Store start of node contents before whitespace is skipped
+ skip<whitespace_pred, Flags>(text);
+ Ch next_char = *text;
+
+ // After data nodes, instead of continuing the loop, control jumps here.
+ // This is because zero termination inside parse_and_append_data() function
+ // would wreak havoc with the above code.
+ // Also, skipping whitespace after data nodes is unnecessary.
+ after_data_node:
+
+ // Determine what comes next: node closing, child node, data node, or 0?
+ switch (next_char)
+ {
+
+ // Node closing or child node
+ case Ch('<'):
+ if (text[1] == Ch('/'))
+ {
+ // Text has only whitespace characters (clown, 2009/10/14, https://sourceforge.net/p/rapidxml/patches/4/).
+ if (contents_start != text && !(Flags & parse_trim_whitespace))
+ parse_and_append_data<Flags>(node, contents_start, contents_start);
+
+ // Node closing
+ text += 2; // Skip '</'
+ if (Flags & parse_validate_closing_tags)
+ {
+ // Skip and validate closing tag name
+ Ch *closing_name = text;
+ skip<node_name_pred, Flags>(text);
+ if (!internal::compare(node->name(), node->name_size(), closing_name, static_cast<std::size_t>(text - closing_name), true))
+ RAPIDXML_PARSE_ERROR("invalid closing tag name", text);
+ }
+ else
+ {
+ // No validation, just skip name
+ skip<node_name_pred, Flags>(text);
+ }
+ // Skip remaining whitespace after node name
+ skip<whitespace_pred, Flags>(text);
+ if (*text != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected >", text);
+ ++text; // Skip '>'
+ return; // Node closed, finished parsing contents
+ }
+ else
+ {
+ // Child node
+ ++text; // Skip '<'
+ if (xml_node<Ch> *child = parse_node<Flags>(text))
+ node->append_node(child);
+
+ // Skip whitespace between closing-tag of child node
+ // and the next element (clown, 2009/10/14, https://sourceforge.net/p/rapidxml/patches/4/).
+ skip<whitespace_pred, Flags>(text);
+ }
+ break;
+
+ // End of data - error
+ case Ch('\0'):
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+
+ // Data node
+ default:
+ next_char = parse_and_append_data<Flags>(node, text, contents_start);
+ contents_start = text; // (clown, 2009/10/16, https://sourceforge.net/p/rapidxml/patches/4/)
+ goto after_data_node; // Bypass regular processing after data nodes
+
+ }
+ }
+ }
+
+ // Parse XML attributes of the node
+ template<int Flags>
+ void parse_node_attributes(Ch *&text, xml_node<Ch> *node)
+ {
+ // For all attributes
+ while (attribute_name_pred::test(*text))
+ {
+ // Extract attribute name
+ Ch *name = text;
+ ++text; // Skip first character of attribute name
+ skip<attribute_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected attribute name", name);
+
+ // Create new attribute
+ xml_attribute<Ch> *attribute = this->allocate_attribute();
+ attribute->name(name, static_cast<std::size_t>(text - name));
+ node->append_attribute(attribute);
+
+ // Skip whitespace after attribute name
+ skip<whitespace_pred, Flags>(text);
+
+ // Skip =
+ if (*text != Ch('='))
+ RAPIDXML_PARSE_ERROR("expected =", text);
+ ++text;
+
+ // Add terminating zero after name
+ if (!(Flags & parse_no_string_terminators))
+ attribute->name()[attribute->name_size()] = 0;
+
+ // Skip whitespace after =
+ skip<whitespace_pred, Flags>(text);
+
+ // Skip quote and remember if it was ' or "
+ Ch quote = *text;
+ if (quote != Ch('\'') && quote != Ch('"'))
+ RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+ ++text;
+
+ // Extract attribute value and expand char refs in it
+ Ch *value = text, *end;
+ const int AttFlags = Flags & ~parse_normalize_whitespace; // No whitespace normalization in attributes
+ if (quote == Ch('\''))
+ end = skip_and_expand_character_refs<attribute_value_pred<Ch('\'')>, attribute_value_pure_pred<Ch('\'')>, AttFlags>(text);
+ else
+ end = skip_and_expand_character_refs<attribute_value_pred<Ch('"')>, attribute_value_pure_pred<Ch('"')>, AttFlags>(text);
+
+ // Set attribute value
+ attribute->value(value, static_cast<std::size_t>(end - value));
+
+ // Make sure that end quote is present
+ if (*text != quote)
+ RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+ ++text; // Skip quote
+
+ // Add terminating zero after value
+ if (!(Flags & parse_no_string_terminators))
+ attribute->value()[attribute->value_size()] = 0;
+
+ // Skip whitespace after attribute value
+ skip<whitespace_pred, Flags>(text);
+ }
+ }
+
+ };
+
+ //! \cond internal
+ namespace internal
+ {
+
+ // Whitespace (space \n \r \t)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
+ };
+
+ // Node name (anything but space \n \r \t / > ? \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_node_name[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) (anything but < \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled
+ // (anything but < \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled
+ // (anything but < \0 & space \n \r \t)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute name (anything but space \n \r \t / < > = ? ! \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with single quote (anything but ' \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with single quote that does not require processing (anything but ' \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with double quote (anything but " \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with double quote that does not require processing (anything but " \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Digits (dec and hex, 255 denotes end of numeric character reference)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_digits[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3
+ 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5
+ 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F
+ };
+
+ // Upper case conversion
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_upcase[256] =
+ {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A B C D E F
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 1
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 2
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 3
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 4
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 5
+ 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 6
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127, // 7
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 8
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 9
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // A
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // B
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // C
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // D
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // E
+ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // F
+ };
+ }
+ //! \endcond
+
+}
+
+// Undefine internal macros
+#undef RAPIDXML_PARSE_ERROR
+
+// On MSVC, restore warnings state
+#ifdef _MSC_VER
+ #pragma warning(pop)
+#endif
+
+#endif
diff --git a/benchmarks/harness.h b/benchmarks/harness.h
new file mode 100644
index 0000000..72164b4
--- /dev/null
+++ b/benchmarks/harness.h
@@ -0,0 +1,94 @@
+#include <stdint.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static uint64_t time_get_ns (void) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+ return (uint64_t)ts.tv_sec * 1000UL*1000UL*1000UL + (uint64_t)ts.tv_nsec;
+}
+
+uint64_t read_energy_uj (void) {
+ FILE *f = fopen("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj", "r");
+ if (!f) {
+ perror("Failed to open energy counter (Are you running with sudo?)");
+ return 0;
+ }
+ uint64_t energy;
+ if (fscanf(f, "%lu", &energy) != 1) {
+ energy = 0;
+ }
+ fclose(f);
+ return energy;
+}
+
+#define BLACK_BOX(x) \
+ __asm__ __volatile__("" : "+g"(x))
+
+#define MIN(A,B) (A) < (B) ? (A) : (B)
+
+void bench_ (
+ char const* const name,
+ #ifdef CONST_BUFFER
+ size_t(*f)(char const*, size_t),
+ char const* buffer,
+ #else
+ size_t(*f)(char*, size_t),
+ char* buffer,
+ #endif
+ size_t buffer_size,
+ FILE* out
+) {
+ int64_t budget = 5UL*1000UL*1000UL*1000UL;
+
+ uint64_t best = UINT64_MAX;
+ uint64_t best_energy = UINT64_MAX;
+
+ #ifndef CONST_BUFFER
+ char* const tmp_buf = (char*)malloc(buffer_size);
+ memcpy(tmp_buf, buffer, buffer_size);
+ #endif
+
+ uint64_t res = f(buffer, buffer_size);
+
+ fprintf(out,
+ " <benchmark name=\"%s\">\n"
+ " <res>%zu</res>\n",
+ name,
+ res
+ );
+
+ while (budget > 0) {
+ #ifndef CONST_BUFFER
+ memcpy(buffer, tmp_buf, buffer_size);
+ #endif
+ uint64_t start_energy = read_energy_uj();
+ uint64_t start = time_get_ns();
+ res = f(buffer, buffer_size);
+ uint64_t elapsed = time_get_ns() - start;
+ best = MIN(best, elapsed);
+ best_energy = MIN(best_energy, read_energy_uj() - start_energy);
+ budget -= elapsed;
+ BLACK_BOX(res);
+ }
+
+ fprintf(out,
+ " <time unit=\"ns\">%zu</time>\n"
+ " <throughput unit=\"MiB/s\">%f</throughput>\n",
+ best,
+ ((double)(buffer_size) / (1024.0*1024.0)) / ((double)(best) / (1000.0*1000.0*1000.0))
+ );
+
+ if (best_energy != 0) {
+ fprintf(out,
+ " <energy-consumption unit=\"J/GiB\">%f</energy-consumption>\n",
+ ((double)(best_energy) / (1000.0*1000.0)) / ((double)(buffer_size) / (1024.0*1024.0*1024.0))
+ );
+ }
+ fprintf(out, " </benchmark>\n");
+}
+
+#define bench(FN) bench_(#FN, FN, buffer, buf_size, out)
+
diff --git a/benchmarks/harness.rs b/benchmarks/harness.rs
new file mode 100644
index 0000000..c0f90f0
--- /dev/null
+++ b/benchmarks/harness.rs
@@ -0,0 +1,59 @@
+use std::{
+ fs,
+ fmt::Write,
+ hint::black_box,
+ time::{Duration, Instant},
+};
+
+fn read_energy_uj() -> u64 {
+ fs::read_to_string("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj")
+ .map(|s| s.trim().parse().unwrap_or(0))
+ .unwrap_or_else(|e| {
+ eprintln!("Failed to open energy counter (Are you running with sudo?): {e}");
+ 0
+ })
+}
+
+pub fn bench(name: String, f: fn(&[u8]) -> usize, buffer: &[u8], out: &mut String) {
+ let mut budget: isize = 5_000_000_000;
+
+ let mut best = Duration::MAX;
+ let mut best_energy = u64::MAX;
+
+ writeln!(out, " <benchmark name=\"{}\">", name).unwrap();
+
+ let res = f(buffer);
+
+ while budget > 0 {
+ let energy_start = read_energy_uj();
+ let start = Instant::now();
+ let res = f(buffer);
+ let elapsed = start.elapsed();
+ best = best.min(elapsed);
+ best_energy = best_energy.min(read_energy_uj() - energy_start);
+ budget -= elapsed.as_nanos() as isize;
+ black_box(res);
+ }
+
+ writeln!(out,
+ "\x20 <res>{}</res>\n\
+ \x20 <time unit=\"ns\">{}</time>\n\
+ \x20 <throughput unit=\"MiB/s\">{}</throughput>",
+ res,
+ best.as_nanos(),
+ (buffer.len() as f64 / (1024.0 * 1024.0)) / best.as_secs_f64(),
+ ).unwrap();
+
+ if best_energy != 0 {
+ writeln!(out,
+ "\x20 <energy-consumption unit=\"J/GiB\">{}</energy-consumption>",
+ ((best_energy as f64) / (1000.0 * 1000.0)) / (buffer.len() as f64 / (1024.0 * 1024.0 * 1024.0)),
+ ).unwrap();
+ }
+ writeln!(out,
+ "\x20 </benchmark>",
+ ).unwrap();
+
+}
+
+
diff --git a/benchmarks/imxml/.env/build.sh b/benchmarks/imxml/.env/build.sh
new file mode 100644
index 0000000..0f15189
--- /dev/null
+++ b/benchmarks/imxml/.env/build.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+clang bench.c count_highway_crossings.c pubmed_len.c pubmed_len_with_entities.c -I ../../ -O3 -march=native -flto -o bench
+#clang bench.c count_highway_crossings.c pubmed_len.c pubmed_len_with_entities.c -I ../../src/ -g3 -march=native -o bench
+
diff --git a/benchmarks/imxml/.env/debug.sh b/benchmarks/imxml/.env/debug.sh
new file mode 100644
index 0000000..d3fa5a9
--- /dev/null
+++ b/benchmarks/imxml/.env/debug.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+. .env/build.sh
+./bench
+
diff --git a/benchmarks/imxml/.gitignore b/benchmarks/imxml/.gitignore
new file mode 100644
index 0000000..ba99195
--- /dev/null
+++ b/benchmarks/imxml/.gitignore
@@ -0,0 +1 @@
+bench
diff --git a/benchmarks/imxml/bench.c b/benchmarks/imxml/bench.c
new file mode 100644
index 0000000..d2c6688
--- /dev/null
+++ b/benchmarks/imxml/bench.c
@@ -0,0 +1,52 @@
+#define IMXML_LINUX
+#define IMXML_IMPLEMENTATION /* for file loading */
+#include <imxml.h>
+
+#define CONST_BUFFER
+#include "../harness.h"
+
+size_t count_highway_crossings (char const* buffer, size_t len);
+size_t count_highway_crossings_cheat_and_early_out (char const* buffer, size_t len);
+size_t pubmed_len (char const* buffer, size_t len);
+size_t pubmed_len_with_entities (char const* buffer, size_t len);
+
+int main (void) {
+ FILE* out = fopen("benchmark-results.xml", "wb");
+ if (!out) goto err;
+
+ fprintf(out,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<benchmarks library=\"imxml\">\n"
+ );
+
+ size_t buf_size;
+
+ if (true) {
+ char* const buffer = imxml_linux_file_open("../../dat/yellowstone.osm", &buf_size, true);
+ if (!buffer) goto err;
+ bench(count_highway_crossings);
+ bench(count_highway_crossings_cheat_and_early_out);
+ imxml_linux_file_close(buffer, buf_size);
+ }
+
+ if (true) {
+ char* const buffer = imxml_linux_file_open("../../dat/PMC176545.xml", &buf_size, true);
+ if (!buffer) goto err;
+ bench(pubmed_len);
+ bench(pubmed_len_with_entities);
+ imxml_linux_file_close(buffer, buf_size);
+ }
+
+ fprintf(out,
+ "</benchmarks>\n\n"
+ );
+
+ goto noerr;
+ err:
+ if (out) fclose(out);
+ fprintf(stderr, "ERROR\n");
+ return -1;
+ noerr:
+ return 0;
+}
+
diff --git a/benchmarks/imxml/benchmark-results-gcc.xml b/benchmarks/imxml/benchmark-results-gcc.xml
new file mode 100644
index 0000000..4a4406c
--- /dev/null
+++ b/benchmarks/imxml/benchmark-results-gcc.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="imxml">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">185038389</time>
+ <throughput unit="MiB/s">2460.870262</throughput>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">30228588</time>
+ <throughput unit="MiB/s">15063.735982</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>75315</res>
+ <time unit="ns">64620</time>
+ <throughput unit="MiB/s">2297.082946</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74918</res>
+ <time unit="ns">59090</time>
+ <throughput unit="MiB/s">2512.057878</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/imxml/benchmark-results-tcc.xml b/benchmarks/imxml/benchmark-results-tcc.xml
new file mode 100644
index 0000000..9ba9254
--- /dev/null
+++ b/benchmarks/imxml/benchmark-results-tcc.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="imxml">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">1509735696</time>
+ <throughput unit="MiB/s">301.612706</throughput>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">676925888</time>
+ <throughput unit="MiB/s">672.681422</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>75315</res>
+ <time unit="ns">528470</time>
+ <throughput unit="MiB/s">280.881602</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74918</res>
+ <time unit="ns">785550</time>
+ <throughput unit="MiB/s">188.959964</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/imxml/benchmark-results.xml b/benchmarks/imxml/benchmark-results.xml
new file mode 100644
index 0000000..d87fd38
--- /dev/null
+++ b/benchmarks/imxml/benchmark-results.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="imxml">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">171566002</time>
+ <throughput unit="MiB/s">2654.112490</throughput>
+ <energy-consumption unit="J/GiB">9.937154</energy-consumption>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">29684754</time>
+ <throughput unit="MiB/s">15339.708348</throughput>
+ <energy-consumption unit="J/GiB">2.009624</energy-consumption>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>75315</res>
+ <time unit="ns">40620</time>
+ <throughput unit="MiB/s">3654.295913</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74918</res>
+ <time unit="ns">47830</time>
+ <throughput unit="MiB/s">3103.439264</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/imxml/count_highway_crossings.c b/benchmarks/imxml/count_highway_crossings.c
new file mode 100644
index 0000000..f758a05
--- /dev/null
+++ b/benchmarks/imxml/count_highway_crossings.c
@@ -0,0 +1,64 @@
+#define IMXML_LINUX
+#define IMXML_SIMD_DEFAULT AVX2
+#define IMXML_NO_SUPPORT_ENTITIES
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES
+#define IMXML_NO_SUPPORT_COMMENTS
+#define IMXML_NO_SUPPORT_CDATA
+#define IMXML_NO_SUPPORT_CUSTOM_ENTITIES
+#define THREADLOCAL
+#define IMXML_NO_CHECK_BOUNDS
+#define IMXML_NO_SUPPORT_NAMESPACES
+#define IMXML_IMPLEMENTATION
+#include <imxml.h>
+
+size_t count_highway_crossings_cheat_and_early_out (char const* const buffer) {
+ XmlParser p = {.head = buffer};
+ size_t total = 0;
+
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("tag"))) {
+ ImxmlString key = xml_value(&p);
+ if (!imxml_streql(key, imxml_strlit("highway"))) continue;
+ ImxmlString value = xml_value(&p);
+ if (imxml_streql(value, imxml_strlit("crossing"))) {
+ total += 1;
+ }
+ } else if (imxml_streql(tag, imxml_strlit("way"))) {
+ break;
+ }
+ }
+
+ return total;
+}
+
+size_t count_highway_crossings (char const* const buffer) {
+ XmlParser p = {.head = buffer};
+ size_t total = 0;
+
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("node"))) {
+ if (xml_has_children(&p)) {
+ while (true) {
+ tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("/node"))) break;
+ ImxmlString key = xml_value(&p);
+ if (!imxml_streql(key, imxml_strlit("highway"))) continue;
+ ImxmlString value = xml_value(&p);
+ if (imxml_streql(value, imxml_strlit("crossing"))) {
+ total += 1;
+ }
+ }
+ }
+ } else if (imxml_streql(tag, imxml_strlit("/osm"))) {
+ /* encountered non-node tag, which means we can early-out.
+ * Alternatively, we could check if tag == "/osm", but then we'd
+ * just be skipping over all the <way> and <relation> tags */
+ break;
+ }
+ }
+
+ return total;
+}
+
diff --git a/benchmarks/imxml/pubmed_len.c b/benchmarks/imxml/pubmed_len.c
new file mode 100644
index 0000000..b104e54
--- /dev/null
+++ b/benchmarks/imxml/pubmed_len.c
@@ -0,0 +1,48 @@
+#define IMXML_SIMD_DEFAULT AVX2
+#define IMXML_NO_SUPPORT_ENTITIES
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES
+#define IMXML_NO_SUPPORT_COMMENTS
+#define IMXML_NO_SUPPORT_CDATA
+#define IMXML_NO_SUPPORT_CUSTOM_ENTITIES
+#define THREADLOCAL
+#define IMXML_NO_CHECK_BOUNDS
+#define IMXML_NO_SUPPORT_NAMESPACES
+#define IMXML_IMPLEMENTATION
+#include <imxml.h>
+
+size_t pubmed_len (char const* const buffer) {
+ XmlParser p = {
+ .head = buffer,
+ };
+
+ size_t total = 0;
+
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ bool const is_closing = tag.items[0] == '/';
+
+ if (is_closing) {
+ if (imxml_streql(tag, imxml_strlit("/article"))) break;
+ continue;
+ }
+ if ( !imxml_streql(tag, imxml_strlit("abstract"))
+ && !imxml_streql(tag, imxml_strlit("body"))
+ ) {
+ continue;
+ }
+ ImxmlString opening_tag = tag;
+
+ while (true) {
+ XmlContent content = xml_content(&p);
+ if (content.type == XML_CONTENT_TAG) {
+ if (xml_is_closing_tag_for(content.data, opening_tag)) break;
+ continue;
+ }
+ ImxmlString text = xml_content_to_string(content, NULL);
+ total += text.count;
+ }
+ }
+
+ return total;
+}
+
diff --git a/benchmarks/imxml/pubmed_len_with_entities.c b/benchmarks/imxml/pubmed_len_with_entities.c
new file mode 100644
index 0000000..11709da
--- /dev/null
+++ b/benchmarks/imxml/pubmed_len_with_entities.c
@@ -0,0 +1,47 @@
+#define IMXML_SIMD_DEFAULT AVX2
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES
+#define IMXML_NO_SUPPORT_COMMENTS
+#define IMXML_NO_SUPPORT_CDATA
+#define IMXML_NO_SUPPORT_CUSTOM_ENTITIES
+#define THREADLOCAL
+#define IMXML_NO_CHECK_BOUNDS
+#define IMXML_NO_SUPPORT_NAMESPACES
+#define IMXML_IMPLEMENTATION
+#include <imxml.h>
+
+size_t pubmed_len_with_entities (char const* const buffer) {
+ XmlParser p = {
+ .head = buffer,
+ };
+
+ size_t total = 0;
+
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ bool const is_closing = tag.items[0] == '/';
+
+ if (is_closing) {
+ if (imxml_streql(tag, imxml_strlit("/article"))) break;
+ continue;
+ }
+ if ( !imxml_streql(tag, imxml_strlit("abstract"))
+ && !imxml_streql(tag, imxml_strlit("body"))
+ ) {
+ continue;
+ }
+ ImxmlString opening_tag = tag;
+
+ while (true) {
+ XmlContent content = xml_content(&p);
+ if (content.type == XML_CONTENT_TAG) {
+ if (xml_is_closing_tag_for(content.data, opening_tag)) break;
+ continue;
+ }
+ ImxmlString text = xml_content_to_string(content, NULL);
+ total += text.count;
+ }
+ }
+
+ return total;
+}
+
diff --git a/benchmarks/pugixml/.env/build.sh b/benchmarks/pugixml/.env/build.sh
new file mode 100644
index 0000000..0b387b2
--- /dev/null
+++ b/benchmarks/pugixml/.env/build.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -e
+
+clang++ pugixml.cpp bench.cpp count_highway_crossings.cpp pubmed_len.cpp -O3 -flto -march=native -o bench
+
diff --git a/benchmarks/pugixml/.env/debug.sh b/benchmarks/pugixml/.env/debug.sh
new file mode 100644
index 0000000..d3fa5a9
--- /dev/null
+++ b/benchmarks/pugixml/.env/debug.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+. .env/build.sh
+./bench
+
diff --git a/benchmarks/pugixml/.gitignore b/benchmarks/pugixml/.gitignore
new file mode 100644
index 0000000..ba99195
--- /dev/null
+++ b/benchmarks/pugixml/.gitignore
@@ -0,0 +1 @@
+bench
diff --git a/benchmarks/pugixml/bench.cpp b/benchmarks/pugixml/bench.cpp
new file mode 100644
index 0000000..abac120
--- /dev/null
+++ b/benchmarks/pugixml/bench.cpp
@@ -0,0 +1,76 @@
+#include "../harness.h"
+#include <fstream>
+#include <vector>
+
+std::vector<char> load_file(char const* const path, size_t* const buf_size) {
+ std::ifstream file(path, std::ios::binary | std::ios::ate);
+ if (!file) {
+ *buf_size = 0;
+ return {};
+ }
+ std::streamsize size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::vector<char> buffer(static_cast<size_t>(size));
+ if (!file.read(buffer.data(), size)) {
+ *buf_size = 0;
+ return {};
+ }
+
+ *buf_size = buffer.size();
+ return buffer;
+}
+
+size_t count_highway_crossings (char* buffer, size_t len);
+size_t count_highway_crossings_cheat_and_early_out (char* buffer, size_t len);
+size_t pubmed_len (char* buffer, size_t len);
+size_t pubmed_len_with_entities (char* buffer, size_t len);
+
+int main (void) {
+ FILE* out = fopen("benchmark-results.xml", "wb");
+ if (!out) goto err;
+
+ fprintf(out,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<benchmarks library=\"pugixml\">\n"
+ );
+
+ size_t buf_size;
+
+ if (true) {
+ auto buf = load_file("../../dat/yellowstone.osm", &buf_size);
+ char* buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(count_highway_crossings);
+
+ buf = load_file("../../dat/yellowstone.osm", &buf_size);
+ buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(count_highway_crossings_cheat_and_early_out);
+ }
+
+ if (true) {
+ auto buf = load_file("../../dat/PMC176545.xml", &buf_size);
+ char* buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(pubmed_len);
+
+ buf = load_file("../../dat/PMC176545.xml", &buf_size);
+ buffer = buf.data();
+ if (!buf_size) goto err;
+ bench(pubmed_len_with_entities);
+ }
+
+ fprintf(out,
+ "</benchmarks>\n\n"
+ );
+
+ goto noerr;
+ err:
+ if (out) fclose(out);
+ fprintf(stderr, "ERROR\n");
+ return -1;
+ noerr:
+ return 0;
+}
+
diff --git a/benchmarks/pugixml/benchmark-results.xml b/benchmarks/pugixml/benchmark-results.xml
new file mode 100644
index 0000000..74f5333
--- /dev/null
+++ b/benchmarks/pugixml/benchmark-results.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="pugixml">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">1042282937</time>
+ <throughput unit="MiB/s">436.879020</throughput>
+ <energy-consumption unit="J/GiB">61.156623</energy-consumption>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">972969714</time>
+ <throughput unit="MiB/s">468.001770</throughput>
+ <energy-consumption unit="J/GiB">65.492539</energy-consumption>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>74911</res>
+ <time unit="ns">223540</time>
+ <throughput unit="MiB/s">658.011387</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74911</res>
+ <time unit="ns">223671</time>
+ <throughput unit="MiB/s">657.626002</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/pugixml/count_highway_crossings.cpp b/benchmarks/pugixml/count_highway_crossings.cpp
new file mode 100644
index 0000000..76a4bd0
--- /dev/null
+++ b/benchmarks/pugixml/count_highway_crossings.cpp
@@ -0,0 +1,54 @@
+#include "pugixml.hpp"
+#include <cstddef>
+#include <cstring>
+
+// Returns true and increments count if `tag_node`'s first two attributes
+// are (value) "highway" then "crossing", matching the original's
+// position-based (not name-based) attribute check.
+inline bool is_highway_crossing(pugi::xml_node tag_node) {
+ pugi::xml_attribute k = tag_node.first_attribute();
+ if (!k) return false;
+
+ pugi::xml_attribute v = k.next_attribute();
+ if (!v) return false;
+
+ return std::strcmp(k.value(), "highway") == 0 &&
+ std::strcmp(v.value(), "crossing") == 0;
+}
+
+size_t count_highway_crossings (char* buffer, size_t len) {
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_buffer_inplace(buffer, len, pugi::parse_minimal);
+ if (!result) return 0;
+ //if (!result) {
+ // fprintf(stderr, "pugixml error: %s at offset %td\n", result.description(), result.offset);
+ //}
+
+ size_t count = 0;
+
+ for (pugi::xml_node node : doc.child("osm").children("node")) {
+ for (pugi::xml_node tag : node.children("tag")) {
+ if (is_highway_crossing(tag)) ++count;
+ }
+ }
+
+ return count;
+}
+
+size_t count_highway_crossings_cheat_and_early_out (char* buffer, size_t len) {
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_buffer_inplace(buffer, len, pugi::parse_minimal);
+ if (!result) return 0;
+
+ size_t count = 0;
+
+ for (pugi::xml_node top : doc.child("osm").children()) {
+ if (std::strcmp(top.name(), "way") == 0) break;
+
+ for (pugi::xml_node tag : top.children("tag")) {
+ if (is_highway_crossing(tag)) ++count;
+ }
+ }
+
+ return count;
+}
diff --git a/benchmarks/pugixml/pubmed_len.cpp b/benchmarks/pugixml/pubmed_len.cpp
new file mode 100644
index 0000000..ec9e16c
--- /dev/null
+++ b/benchmarks/pugixml/pubmed_len.cpp
@@ -0,0 +1,49 @@
+#include "pugixml.hpp"
+#include <cstring>
+
+inline bool is_content_root(const char* name) {
+ return std::strcmp(name, "abstract") == 0 || std::strcmp(name, "body") == 0;
+}
+
+// Sums length of all node_pcdata descendants (CDATA excluded to match
+// quick-xml's Event::Text-only accounting).
+size_t sum_pcdata(const pugi::xml_node& node) {
+ size_t total = 0;
+ for (pugi::xml_node child = node.first_child(); child; child = child.next_sibling()) {
+ if (child.type() == pugi::node_pcdata) {
+ total += std::strlen(child.value());
+ } else if (child.type() == pugi::node_element) {
+ total += sum_pcdata(child);
+ }
+ }
+ return total;
+}
+
+// Searches for <abstract>/<body> anywhere in the tree; once found, all
+// descendant text counts regardless of further nesting.
+size_t walk(const pugi::xml_node& node) {
+ size_t total = 0;
+ for (pugi::xml_node child = node.first_child(); child; child = child.next_sibling()) {
+ if (child.type() != pugi::node_element) continue;
+ if (is_content_root(child.name())) {
+ total += sum_pcdata(child);
+ } else {
+ total += walk(child);
+ }
+ }
+ return total;
+}
+
+size_t pubmed_len(char* buffer, size_t len) {
+ pugi::xml_document doc;
+ unsigned int flags = pugi::parse_default | pugi::parse_escapes;
+ doc.load_buffer_inplace(buffer, len, flags);
+ return walk(doc);
+}
+
+size_t pubmed_len_with_entities(char* buffer, size_t len) {
+ pugi::xml_document doc;
+ unsigned int flags = pugi::parse_default;
+ doc.load_buffer_inplace(buffer, len, flags);
+ return walk(doc);
+}
diff --git a/benchmarks/pugixml/pugiconfig.hpp b/benchmarks/pugixml/pugiconfig.hpp
new file mode 100644
index 0000000..025d447
--- /dev/null
+++ b/benchmarks/pugixml/pugiconfig.hpp
@@ -0,0 +1,81 @@
+/**
+ * pugixml parser - version 1.16
+ * --------------------------------------------------------
+ * Report bugs and download new versions at https://pugixml.org/
+ *
+ * SPDX-FileCopyrightText: Copyright (C) 2006-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * SPDX-License-Identifier: MIT
+ *
+ * See LICENSE.md or notice at the end of this file.
+ */
+
+#ifndef HEADER_PUGICONFIG_HPP
+#define HEADER_PUGICONFIG_HPP
+
+// Uncomment this to enable wchar_t mode
+// #define PUGIXML_WCHAR_MODE
+
+// Uncomment this to enable from_chars/to_chars for number conversion
+// #define PUGIXML_CHARCONV_FLOAT
+
+// Uncomment this to enable compact mode
+// #define PUGIXML_COMPACT
+
+// Uncomment this to disable XPath
+// #define PUGIXML_NO_XPATH
+
+// Uncomment this to disable STL
+// #define PUGIXML_NO_STL
+
+// Uncomment this to disable exceptions
+// #define PUGIXML_NO_EXCEPTIONS
+
+// Set this to control attributes for public classes/functions, i.e.:
+// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
+// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
+// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
+// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
+
+// Tune these constants to adjust memory-related behavior
+// #define PUGIXML_MEMORY_PAGE_SIZE 32768
+// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
+// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
+
+// Tune this constant to adjust max nesting for XPath queries
+// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
+
+// Uncomment this to switch to header-only version
+// #define PUGIXML_HEADER_ONLY
+
+// Uncomment this to enable long long support (usually enabled automatically)
+// #define PUGIXML_HAS_LONG_LONG
+
+// Uncomment this to enable support for std::string_view (usually enabled automatically)
+// #define PUGIXML_HAS_STRING_VIEW
+
+#endif
+
+/**
+ * Copyright (c) 2006-2026 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/benchmarks/pugixml/pugixml.cpp b/benchmarks/pugixml/pugixml.cpp
new file mode 100644
index 0000000..7bb7beb
--- /dev/null
+++ b/benchmarks/pugixml/pugixml.cpp
@@ -0,0 +1,13776 @@
+/**
+ * pugixml parser - version 1.16
+ * --------------------------------------------------------
+ * Report bugs and download new versions at https://pugixml.org/
+ *
+ * SPDX-FileCopyrightText: Copyright (C) 2006-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * SPDX-License-Identifier: MIT
+ *
+ * See LICENSE.md or notice at the end of this file.
+ */
+
+#ifndef SOURCE_PUGIXML_CPP
+#define SOURCE_PUGIXML_CPP
+
+#include "pugixml.hpp"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#ifdef PUGIXML_WCHAR_MODE
+# include <wchar.h>
+#endif
+
+#ifndef PUGIXML_NO_XPATH
+# include <math.h>
+# include <float.h>
+#endif
+
+#ifndef PUGIXML_NO_STL
+# include <istream>
+# include <ostream>
+# include <string>
+#endif
+
+#ifdef PUGIXML_CHARCONV_FLOAT
+# include <charconv>
+#endif
+
+// For placement new
+#include <new>
+
+// For load_file
+#if defined(__linux__) || defined(__APPLE__)
+#include <sys/stat.h>
+#endif
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable: 4127) // conditional expression is constant
+# pragma warning(disable: 4324) // structure was padded due to __declspec(align())
+# pragma warning(disable: 4702) // unreachable code
+# pragma warning(disable: 4996) // this function or variable may be unsafe
+#endif
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // NULL as null pointer constant
+# pragma clang diagnostic ignored "-Wsign-conversion" // implicit conversion changes signedness
+#endif
+
+#if defined(_MSC_VER) && defined(__c2__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated" // this function or variable may be unsafe
+#endif
+
+#ifdef __INTEL_COMPILER
+# pragma warning(disable: 177) // function was declared but never referenced
+# pragma warning(disable: 279) // controlling expression is constant
+# pragma warning(disable: 1478 1786) // function was declared "deprecated"
+# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type
+#endif
+
+#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY)
+# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away
+#endif
+
+#ifdef __BORLANDC__
+# pragma option push
+# pragma warn -8008 // condition is always false
+# pragma warn -8066 // unreachable code
+#endif
+
+#ifdef __SNC__
+// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug
+# pragma diag_suppress=178 // function was declared but never referenced
+# pragma diag_suppress=237 // controlling expression is constant
+#endif
+
+#ifdef __TI_COMPILER_VERSION__
+# pragma diag_suppress 179 // function was declared but never referenced
+#endif
+
+// Inlining controls
+#if defined(_MSC_VER) && _MSC_VER >= 1300
+# define PUGI_IMPL_NO_INLINE __declspec(noinline)
+#elif defined(__GNUC__)
+# define PUGI_IMPL_NO_INLINE __attribute__((noinline))
+#else
+# define PUGI_IMPL_NO_INLINE
+#endif
+
+// Unaligned memory access attribute
+#if defined(__GNUC__) && !defined(__c2__)
+# define PUGI_IMPL_UNALIGNED __attribute__((aligned(1)))
+#else
+# define PUGI_IMPL_UNALIGNED
+#endif
+
+// Branch weight controls
+#if defined(__GNUC__) && !defined(__c2__)
+# define PUGI_IMPL_UNLIKELY(cond) __builtin_expect(cond, 0)
+#else
+# define PUGI_IMPL_UNLIKELY(cond) (cond)
+#endif
+
+// Simple static assertion
+#define PUGI_IMPL_STATIC_ASSERT(cond) do { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } while (0)
+
+// Digital Mars C++ bug workaround for passing char loaded from memory via stack
+#ifdef __DMC__
+# define PUGI_IMPL_DMC_VOLATILE volatile
+#else
+# define PUGI_IMPL_DMC_VOLATILE
+#endif
+
+// Integer sanitizer workaround; we only apply this for clang since gcc8 has no_sanitize but not unsigned-integer-overflow and produces "attribute directive ignored" warnings
+#if defined(__clang__) && defined(__has_attribute)
+# if __has_attribute(no_sanitize)
+# define PUGI_IMPL_UNSIGNED_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow")))
+# else
+# define PUGI_IMPL_UNSIGNED_OVERFLOW
+# endif
+#else
+# define PUGI_IMPL_UNSIGNED_OVERFLOW
+#endif
+
+// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all)
+#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST)
+using std::memcpy;
+using std::memmove;
+using std::memset;
+#endif
+
+// Old versions of GCC do not define ::malloc and ::free depending on header include order
+#if defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4))
+using std::malloc;
+using std::free;
+#endif
+
+// Some MinGW/GCC versions have headers that erroneously omit LLONG_MIN/LLONG_MAX/ULLONG_MAX definitions from limits.h in some configurations
+#if defined(PUGIXML_HAS_LONG_LONG) && defined(__GNUC__) && !defined(LLONG_MAX) && !defined(LLONG_MIN) && !defined(ULLONG_MAX)
+# define LLONG_MIN (-LLONG_MAX - 1LL)
+# define LLONG_MAX __LONG_LONG_MAX__
+# define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL)
+#endif
+
+// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features
+#if defined(_MSC_VER) && !defined(__S3E__) && !defined(_WIN32_WCE)
+# define PUGI_IMPL_MSVC_CRT_VERSION _MSC_VER
+#elif defined(_WIN32_WCE)
+# define PUGI_IMPL_MSVC_CRT_VERSION 1310 // MSVC7.1
+#endif
+
+// Not all platforms have snprintf; we define a wrapper that uses snprintf if possible. This only works with buffers with a known size.
+#if __cplusplus >= 201103
+# define PUGI_IMPL_SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__)
+#elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400
+# define PUGI_IMPL_SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__)
+#elif defined(__APPLE__) && __clang_major__ >= 14 // Xcode 14 marks sprintf as deprecated while still using C++98 by default
+# define PUGI_IMPL_SNPRINTF(buf, fmt, arg1, arg2) snprintf(buf, sizeof(buf), fmt, arg1, arg2)
+#else
+# define PUGI_IMPL_SNPRINTF sprintf
+#endif
+
+// We put implementation details into an anonymous namespace in source mode, but have to keep it in non-anonymous namespace in header-only mode to prevent binary bloat.
+#ifdef PUGIXML_HEADER_ONLY
+# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl {
+# define PUGI_IMPL_NS_END } }
+# define PUGI_IMPL_FN inline
+# define PUGI_IMPL_FN_NO_INLINE inline
+#else
+# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces
+# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl {
+# define PUGI_IMPL_NS_END } }
+# else
+# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { namespace {
+# define PUGI_IMPL_NS_END } } }
+# endif
+# define PUGI_IMPL_FN
+# define PUGI_IMPL_FN_NO_INLINE PUGI_IMPL_NO_INLINE
+#endif
+
+// uintptr_t
+#if (defined(_MSC_VER) && _MSC_VER < 1600) || (defined(__BORLANDC__) && __BORLANDC__ < 0x561)
+namespace pugi
+{
+# ifndef _UINTPTR_T_DEFINED
+ typedef size_t uintptr_t;
+# endif
+
+ typedef unsigned __int8 uint8_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int32 uint32_t;
+}
+#else
+# include <stdint.h>
+#endif
+
+// Memory allocation
+PUGI_IMPL_NS_BEGIN
+ PUGI_IMPL_FN void* default_allocate(size_t size)
+ {
+ return malloc(size);
+ }
+
+ PUGI_IMPL_FN void default_deallocate(void* ptr)
+ {
+ free(ptr);
+ }
+
+ template <typename T>
+ struct xml_memory_management_function_storage
+ {
+ static allocation_function allocate;
+ static deallocation_function deallocate;
+ };
+
+ // Global allocation functions are stored in class statics so that in header mode linker deduplicates them
+ // Without a template<> we'll get multiple definitions of the same static
+ template <typename T> allocation_function xml_memory_management_function_storage<T>::allocate = default_allocate;
+ template <typename T> deallocation_function xml_memory_management_function_storage<T>::deallocate = default_deallocate;
+
+ typedef xml_memory_management_function_storage<int> xml_memory;
+PUGI_IMPL_NS_END
+
+// String utilities
+PUGI_IMPL_NS_BEGIN
+ // Get string length
+ PUGI_IMPL_FN size_t strlength(const char_t* s)
+ {
+ assert(s);
+
+ #ifdef PUGIXML_WCHAR_MODE
+ return wcslen(s);
+ #else
+ return strlen(s);
+ #endif
+ }
+
+ // Compare two strings
+ PUGI_IMPL_FN bool strequal(const char_t* src, const char_t* dst)
+ {
+ assert(src && dst);
+
+ #ifdef PUGIXML_WCHAR_MODE
+ return *src == *dst && wcscmp(src, dst) == 0;
+ #else
+ return *src == *dst && strcmp(src, dst) == 0;
+ #endif
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ // Check if the null-terminated dst string is equal to the entire contents of srcview
+ PUGI_IMPL_FN bool stringview_equal(string_view_t srcview, const char_t* dst)
+ {
+ // std::basic_string_view::compare(const char*) has the right behavior, but it performs an
+ // extra traversal of dst to compute its length.
+ assert(dst);
+ const char_t* src = srcview.data();
+ size_t srclen = srcview.size();
+
+ while (srclen && *dst && *src == *dst)
+ {
+ --srclen; ++dst; ++src;
+ }
+ return srclen == 0 && *dst == 0;
+ }
+#endif
+
+ // Compare lhs with [rhs, rhs + count)
+ PUGI_IMPL_FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count)
+ {
+ for (size_t i = 0; i < count; ++i)
+ if (lhs[i] != rhs[i])
+ return false;
+
+ return lhs[count] == 0;
+ }
+
+ // Get length of wide string, even if CRT lacks wide character support
+ PUGI_IMPL_FN size_t strlength_wide(const wchar_t* s)
+ {
+ assert(s);
+
+ #ifdef PUGIXML_WCHAR_MODE
+ return wcslen(s);
+ #else
+ const wchar_t* end = s;
+ while (*end) end++;
+ return static_cast<size_t>(end - s);
+ #endif
+ }
+PUGI_IMPL_NS_END
+
+// auto_ptr-like object for exception recovery
+PUGI_IMPL_NS_BEGIN
+ template <typename T> struct auto_deleter
+ {
+ typedef void (*D)(T*);
+
+ T* data;
+ D deleter;
+
+ auto_deleter(T* data_, D deleter_): data(data_), deleter(deleter_)
+ {
+ }
+
+ ~auto_deleter()
+ {
+ if (data) deleter(data);
+ }
+
+ T* release()
+ {
+ T* result = data;
+ data = NULL;
+ return result;
+ }
+ };
+PUGI_IMPL_NS_END
+
+#ifdef PUGIXML_COMPACT
+PUGI_IMPL_NS_BEGIN
+ class compact_hash_table
+ {
+ public:
+ compact_hash_table(): _items(NULL), _capacity(0), _count(0)
+ {
+ }
+
+ void clear()
+ {
+ if (_items)
+ {
+ xml_memory::deallocate(_items);
+ _items = NULL;
+ _capacity = 0;
+ _count = 0;
+ }
+ }
+
+ void* find(const void* key)
+ {
+ if (_capacity == 0) return NULL;
+
+ item_t* item = get_item(key);
+ assert(item);
+ assert(item->key == key || (item->key == NULL && item->value == NULL));
+
+ return item->value;
+ }
+
+ void insert(const void* key, void* value)
+ {
+ assert(_capacity != 0 && _count < _capacity - _capacity / 4);
+
+ item_t* item = get_item(key);
+ assert(item);
+
+ if (item->key == NULL)
+ {
+ _count++;
+ item->key = key;
+ }
+
+ item->value = value;
+ }
+
+ bool reserve(size_t extra = 16)
+ {
+ if (_count + extra >= _capacity - _capacity / 4)
+ return rehash(_count + extra);
+
+ return true;
+ }
+
+ private:
+ struct item_t
+ {
+ const void* key;
+ void* value;
+ };
+
+ item_t* _items;
+ size_t _capacity;
+
+ size_t _count;
+
+ bool rehash(size_t count);
+
+ item_t* get_item(const void* key)
+ {
+ assert(key);
+ assert(_capacity > 0);
+
+ size_t hashmod = _capacity - 1;
+ size_t bucket = hash(key) & hashmod;
+
+ for (size_t probe = 0; probe <= hashmod; ++probe)
+ {
+ item_t& probe_item = _items[bucket];
+
+ if (probe_item.key == key || probe_item.key == NULL)
+ return &probe_item;
+
+ // hash collision, quadratic probing
+ bucket = (bucket + probe + 1) & hashmod;
+ }
+
+ assert(false && "Hash table is full"); // unreachable
+ return NULL;
+ }
+
+ static PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash(const void* key)
+ {
+ unsigned int h = static_cast<unsigned int>(reinterpret_cast<uintptr_t>(key) & 0xffffffff);
+
+ // MurmurHash3 32-bit finalizer
+ h ^= h >> 16;
+ h *= 0x85ebca6bu;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35u;
+ h ^= h >> 16;
+
+ return h;
+ }
+ };
+
+ PUGI_IMPL_FN_NO_INLINE bool compact_hash_table::rehash(size_t count)
+ {
+ size_t capacity = 32;
+ while (count >= capacity - capacity / 4)
+ capacity *= 2;
+
+ compact_hash_table rt;
+ rt._capacity = capacity;
+ rt._items = static_cast<item_t*>(xml_memory::allocate(sizeof(item_t) * capacity));
+
+ if (!rt._items)
+ return false;
+
+ memset(rt._items, 0, sizeof(item_t) * capacity);
+
+ for (size_t i = 0; i < _capacity; ++i)
+ if (_items[i].key)
+ rt.insert(_items[i].key, _items[i].value);
+
+ if (_items)
+ xml_memory::deallocate(_items);
+
+ _capacity = capacity;
+ _items = rt._items;
+
+ assert(_count == rt._count);
+
+ return true;
+ }
+
+PUGI_IMPL_NS_END
+#endif
+
+PUGI_IMPL_NS_BEGIN
+#ifdef PUGIXML_COMPACT
+ static const uintptr_t xml_memory_block_alignment = 4;
+#else
+ static const uintptr_t xml_memory_block_alignment = sizeof(void*);
+#endif
+
+ // extra metadata bits
+ static const uintptr_t xml_memory_page_contents_shared_mask = 64;
+ static const uintptr_t xml_memory_page_name_allocated_mask = 32;
+ static const uintptr_t xml_memory_page_value_allocated_mask = 16;
+ static const uintptr_t xml_memory_page_type_mask = 15;
+
+ // combined masks for string uniqueness
+ static const uintptr_t xml_memory_page_name_allocated_or_shared_mask = xml_memory_page_name_allocated_mask | xml_memory_page_contents_shared_mask;
+ static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask;
+
+#ifdef PUGIXML_COMPACT
+ #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) // unused
+ #define PUGI_IMPL_GETPAGE_IMPL(header) (header).get_page()
+#else
+ #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) (((reinterpret_cast<char*>(object) - reinterpret_cast<char*>(page)) << 8) | (flags))
+ // this macro casts pointers through void* to avoid 'cast increases required alignment of target type' warnings
+ #define PUGI_IMPL_GETPAGE_IMPL(header) static_cast<impl::xml_memory_page*>(const_cast<void*>(static_cast<const void*>(reinterpret_cast<const char*>(&header) - (header >> 8))))
+#endif
+
+ #define PUGI_IMPL_GETPAGE(n) PUGI_IMPL_GETPAGE_IMPL((n)->header)
+ #define PUGI_IMPL_NODETYPE(n) static_cast<xml_node_type>((n)->header & impl::xml_memory_page_type_mask)
+
+ struct xml_allocator;
+
+ struct xml_memory_page
+ {
+ static xml_memory_page* construct(void* memory)
+ {
+ xml_memory_page* result = static_cast<xml_memory_page*>(memory);
+
+ result->allocator = NULL;
+ result->prev = NULL;
+ result->next = NULL;
+ result->busy_size = 0;
+ result->freed_size = 0;
+
+ #ifdef PUGIXML_COMPACT
+ result->compact_string_base = NULL;
+ result->compact_shared_parent = NULL;
+ result->compact_page_marker = NULL;
+ #endif
+
+ return result;
+ }
+
+ xml_allocator* allocator;
+
+ xml_memory_page* prev;
+ xml_memory_page* next;
+
+ size_t busy_size;
+ size_t freed_size;
+
+ #ifdef PUGIXML_COMPACT
+ char_t* compact_string_base;
+ void* compact_shared_parent;
+ uint32_t* compact_page_marker;
+ #endif
+ };
+
+ static const size_t xml_memory_page_size =
+ #ifdef PUGIXML_MEMORY_PAGE_SIZE
+ (PUGIXML_MEMORY_PAGE_SIZE)
+ #else
+ 32768
+ #endif
+ - sizeof(xml_memory_page);
+
+ struct xml_memory_string_header
+ {
+ uint16_t page_offset; // offset from page->data
+ uint16_t full_size; // 0 if string occupies whole page
+ };
+
+ struct xml_allocator
+ {
+ xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size)
+ {
+ #ifdef PUGIXML_COMPACT
+ _hash = NULL;
+ #endif
+ }
+
+ xml_memory_page* allocate_page(size_t data_size)
+ {
+ size_t size = sizeof(xml_memory_page) + data_size;
+
+ // allocate block with some alignment, leaving memory for worst-case padding
+ void* memory = xml_memory::allocate(size);
+ if (!memory) return NULL;
+
+ // prepare page structure
+ xml_memory_page* page = xml_memory_page::construct(memory);
+ assert(page);
+
+ assert(this == _root->allocator);
+ page->allocator = this;
+
+ return page;
+ }
+
+ static void deallocate_page(xml_memory_page* page)
+ {
+ xml_memory::deallocate(page);
+ }
+
+ void* allocate_memory_oob(size_t size, xml_memory_page*& out_page);
+
+ void* allocate_memory(size_t size, xml_memory_page*& out_page)
+ {
+ if (PUGI_IMPL_UNLIKELY(_busy_size + size > xml_memory_page_size))
+ return allocate_memory_oob(size, out_page);
+
+ void* buf = reinterpret_cast<char*>(_root) + sizeof(xml_memory_page) + _busy_size;
+
+ _busy_size += size;
+
+ out_page = _root;
+
+ return buf;
+ }
+
+ #ifdef PUGIXML_COMPACT
+ void* allocate_object(size_t size, xml_memory_page*& out_page)
+ {
+ void* result = allocate_memory(size + sizeof(uint32_t), out_page);
+ if (!result) return NULL;
+
+ // adjust for marker
+ ptrdiff_t offset = static_cast<char*>(result) - reinterpret_cast<char*>(out_page->compact_page_marker);
+
+ if (PUGI_IMPL_UNLIKELY(static_cast<uintptr_t>(offset) >= 256 * xml_memory_block_alignment))
+ {
+ // insert new marker
+ uint32_t* marker = static_cast<uint32_t*>(result);
+
+ *marker = static_cast<uint32_t>(reinterpret_cast<char*>(marker) - reinterpret_cast<char*>(out_page));
+ out_page->compact_page_marker = marker;
+
+ // since we don't reuse the page space until we reallocate it, we can just pretend that we freed the marker block
+ // this will make sure deallocate_memory correctly tracks the size
+ out_page->freed_size += sizeof(uint32_t);
+
+ return marker + 1;
+ }
+ else
+ {
+ // roll back uint32_t part
+ _busy_size -= sizeof(uint32_t);
+
+ return result;
+ }
+ }
+ #else
+ void* allocate_object(size_t size, xml_memory_page*& out_page)
+ {
+ return allocate_memory(size, out_page);
+ }
+ #endif
+
+ void deallocate_memory(void* ptr, size_t size, xml_memory_page* page)
+ {
+ if (page == _root) page->busy_size = _busy_size;
+
+ assert(ptr >= reinterpret_cast<char*>(page) + sizeof(xml_memory_page) && ptr < reinterpret_cast<char*>(page) + sizeof(xml_memory_page) + page->busy_size);
+ (void)!ptr;
+
+ page->freed_size += size;
+ assert(page->freed_size <= page->busy_size);
+
+ if (page->freed_size == page->busy_size)
+ {
+ if (page->next == NULL)
+ {
+ assert(_root == page);
+
+ // top page freed, just reset sizes
+ page->busy_size = 0;
+ page->freed_size = 0;
+
+ #ifdef PUGIXML_COMPACT
+ // reset compact state to maximize efficiency
+ page->compact_string_base = NULL;
+ page->compact_shared_parent = NULL;
+ page->compact_page_marker = NULL;
+ #endif
+
+ _busy_size = 0;
+ }
+ else
+ {
+ assert(_root != page);
+ assert(page->prev);
+
+ // remove from the list
+ page->prev->next = page->next;
+ page->next->prev = page->prev;
+
+ // deallocate
+ deallocate_page(page);
+ }
+ }
+ }
+
+ char_t* allocate_string(size_t length)
+ {
+ static const size_t max_encoded_offset = (1 << 16) * xml_memory_block_alignment;
+
+ PUGI_IMPL_STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset);
+
+ // allocate memory for string and header block
+ size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t);
+
+ // round size up to block alignment boundary
+ size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1);
+
+ xml_memory_page* page;
+ xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page));
+
+ if (!header) return NULL;
+
+ // setup header
+ ptrdiff_t page_offset = reinterpret_cast<char*>(header) - reinterpret_cast<char*>(page) - sizeof(xml_memory_page);
+
+ assert(page_offset % xml_memory_block_alignment == 0);
+ assert(page_offset >= 0 && static_cast<size_t>(page_offset) < max_encoded_offset);
+ header->page_offset = static_cast<uint16_t>(static_cast<size_t>(page_offset) / xml_memory_block_alignment);
+
+ // full_size == 0 for large strings that occupy the whole page
+ assert(full_size % xml_memory_block_alignment == 0);
+ assert(full_size < max_encoded_offset || (page->busy_size == full_size && page_offset == 0));
+ header->full_size = static_cast<uint16_t>(full_size < max_encoded_offset ? full_size / xml_memory_block_alignment : 0);
+
+ // round-trip through void* to avoid 'cast increases required alignment of target type' warning
+ // header is guaranteed a pointer-sized alignment, which should be enough for char_t
+ return static_cast<char_t*>(static_cast<void*>(header + 1));
+ }
+
+ void deallocate_string(char_t* string)
+ {
+ // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings
+ // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string
+
+ // get header
+ xml_memory_string_header* header = static_cast<xml_memory_string_header*>(static_cast<void*>(string)) - 1;
+ assert(header);
+
+ // deallocate
+ size_t page_offset = sizeof(xml_memory_page) + header->page_offset * xml_memory_block_alignment;
+ xml_memory_page* page = reinterpret_cast<xml_memory_page*>(static_cast<void*>(reinterpret_cast<char*>(header) - page_offset));
+
+ // if full_size == 0 then this string occupies the whole page
+ size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size * xml_memory_block_alignment;
+
+ deallocate_memory(header, full_size, page);
+ }
+
+ bool reserve()
+ {
+ #ifdef PUGIXML_COMPACT
+ return _hash->reserve();
+ #else
+ return true;
+ #endif
+ }
+
+ xml_memory_page* _root;
+ size_t _busy_size;
+
+ #ifdef PUGIXML_COMPACT
+ compact_hash_table* _hash;
+ #endif
+ };
+
+ PUGI_IMPL_FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page)
+ {
+ const size_t large_allocation_threshold = xml_memory_page_size / 4;
+
+ xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size);
+ out_page = page;
+
+ if (!page) return NULL;
+
+ if (size <= large_allocation_threshold)
+ {
+ _root->busy_size = _busy_size;
+
+ // insert page at the end of linked list
+ page->prev = _root;
+ _root->next = page;
+ _root = page;
+
+ _busy_size = size;
+ }
+ else
+ {
+ // insert page before the end of linked list, so that it is deleted as soon as possible
+ // the last page is not deleted even if it's empty (see deallocate_memory)
+ assert(_root->prev);
+
+ page->prev = _root->prev;
+ page->next = _root;
+
+ _root->prev->next = page;
+ _root->prev = page;
+
+ page->busy_size = size;
+ }
+
+ return reinterpret_cast<char*>(page) + sizeof(xml_memory_page);
+ }
+PUGI_IMPL_NS_END
+
+#ifdef PUGIXML_COMPACT
+PUGI_IMPL_NS_BEGIN
+ static const uintptr_t compact_alignment_log2 = 2;
+ static const uintptr_t compact_alignment = 1 << compact_alignment_log2;
+
+ class compact_header
+ {
+ public:
+ compact_header(xml_memory_page* page, unsigned int flags)
+ {
+ PUGI_IMPL_STATIC_ASSERT(xml_memory_block_alignment == compact_alignment);
+
+ ptrdiff_t offset = (reinterpret_cast<char*>(this) - reinterpret_cast<char*>(page->compact_page_marker));
+ assert(offset % compact_alignment == 0 && static_cast<uintptr_t>(offset) < 256 * compact_alignment);
+
+ _page = static_cast<unsigned char>(offset >> compact_alignment_log2);
+ _flags = static_cast<unsigned char>(flags);
+ }
+
+ void operator&=(uintptr_t mod)
+ {
+ _flags &= static_cast<unsigned char>(mod);
+ }
+
+ void operator|=(uintptr_t mod)
+ {
+ _flags |= static_cast<unsigned char>(mod);
+ }
+
+ uintptr_t operator&(uintptr_t mod) const
+ {
+ return _flags & mod;
+ }
+
+ xml_memory_page* get_page() const
+ {
+ // round-trip through void* to silence 'cast increases required alignment of target type' warnings
+ const char* page_marker = reinterpret_cast<const char*>(this) - (_page << compact_alignment_log2);
+ const char* page = page_marker - *reinterpret_cast<const uint32_t*>(static_cast<const void*>(page_marker));
+
+ return const_cast<xml_memory_page*>(reinterpret_cast<const xml_memory_page*>(static_cast<const void*>(page)));
+ }
+
+ private:
+ unsigned char _page;
+ unsigned char _flags;
+ };
+
+ PUGI_IMPL_FN xml_memory_page* compact_get_page(const void* object, int header_offset)
+ {
+ const compact_header* header = reinterpret_cast<const compact_header*>(static_cast<const char*>(object) - header_offset);
+
+ return header->get_page();
+ }
+
+ template <int header_offset, typename T> PUGI_IMPL_FN_NO_INLINE T* compact_get_value(const void* object)
+ {
+ return static_cast<T*>(compact_get_page(object, header_offset)->allocator->_hash->find(object));
+ }
+
+ template <int header_offset, typename T> PUGI_IMPL_FN_NO_INLINE void compact_set_value(const void* object, T* value)
+ {
+ compact_get_page(object, header_offset)->allocator->_hash->insert(object, value);
+ }
+
+ template <typename T, int header_offset, int start = -126> class compact_pointer
+ {
+ public:
+ compact_pointer(): _data(0)
+ {
+ }
+
+ void operator=(const compact_pointer& rhs)
+ {
+ *this = rhs + 0;
+ }
+
+ void operator=(T* value)
+ {
+ if (value)
+ {
+ // value is guaranteed to be compact-aligned; 'this' is not
+ // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*)
+ // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to
+ // compensate for arithmetic shift rounding for negative values
+ ptrdiff_t diff = reinterpret_cast<char*>(value) - reinterpret_cast<char*>(this);
+ ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) - start;
+
+ if (static_cast<uintptr_t>(offset) <= 253)
+ _data = static_cast<unsigned char>(offset + 1);
+ else
+ {
+ compact_set_value<header_offset>(this, value);
+
+ _data = 255;
+ }
+ }
+ else
+ _data = 0;
+ }
+
+ operator T*() const
+ {
+ if (_data)
+ {
+ if (_data < 255)
+ {
+ uintptr_t base = reinterpret_cast<uintptr_t>(this) & ~(compact_alignment - 1);
+
+ return reinterpret_cast<T*>(base + (_data - 1 + start) * compact_alignment);
+ }
+ else
+ return compact_get_value<header_offset, T>(this);
+ }
+ else
+ return NULL;
+ }
+
+ T* operator->() const
+ {
+ return *this;
+ }
+
+ private:
+ unsigned char _data;
+ };
+
+ template <typename T, int header_offset> class compact_pointer_parent
+ {
+ public:
+ compact_pointer_parent(): _data(0)
+ {
+ }
+
+ void operator=(const compact_pointer_parent& rhs)
+ {
+ *this = rhs + 0;
+ }
+
+ void operator=(T* value)
+ {
+ if (value)
+ {
+ // value is guaranteed to be compact-aligned; 'this' is not
+ // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*)
+ // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to
+ // compensate for arithmetic shift behavior for negative values
+ ptrdiff_t diff = reinterpret_cast<char*>(value) - reinterpret_cast<char*>(this);
+ ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) + 65533;
+
+ if (static_cast<uintptr_t>(offset) <= 65533)
+ {
+ _data = static_cast<unsigned short>(offset + 1);
+ }
+ else
+ {
+ xml_memory_page* page = compact_get_page(this, header_offset);
+
+ if (PUGI_IMPL_UNLIKELY(page->compact_shared_parent == NULL))
+ page->compact_shared_parent = value;
+
+ if (page->compact_shared_parent == value)
+ {
+ _data = 65534;
+ }
+ else
+ {
+ compact_set_value<header_offset>(this, value);
+
+ _data = 65535;
+ }
+ }
+ }
+ else
+ {
+ _data = 0;
+ }
+ }
+
+ operator T*() const
+ {
+ if (_data)
+ {
+ if (_data < 65534)
+ {
+ uintptr_t base = reinterpret_cast<uintptr_t>(this) & ~(compact_alignment - 1);
+
+ return reinterpret_cast<T*>(base + (_data - 1 - 65533) * compact_alignment);
+ }
+ else if (_data == 65534)
+ return static_cast<T*>(compact_get_page(this, header_offset)->compact_shared_parent);
+ else
+ return compact_get_value<header_offset, T>(this);
+ }
+ else
+ return NULL;
+ }
+
+ T* operator->() const
+ {
+ return *this;
+ }
+
+ private:
+ uint16_t _data;
+ };
+
+ template <int header_offset, int base_offset> class compact_string
+ {
+ public:
+ compact_string(): _data(0)
+ {
+ }
+
+ void operator=(const compact_string& rhs)
+ {
+ *this = rhs + 0;
+ }
+
+ void operator=(char_t* value)
+ {
+ if (value)
+ {
+ xml_memory_page* page = compact_get_page(this, header_offset);
+
+ if (PUGI_IMPL_UNLIKELY(page->compact_string_base == NULL))
+ page->compact_string_base = value;
+
+ ptrdiff_t offset = value - page->compact_string_base;
+
+ if (static_cast<uintptr_t>(offset) < (65535 << 7))
+ {
+ // round-trip through void* to silence 'cast increases required alignment of target type' warnings
+ uint16_t* base = reinterpret_cast<uint16_t*>(static_cast<void*>(reinterpret_cast<char*>(this) - base_offset));
+
+ if (*base == 0)
+ {
+ *base = static_cast<uint16_t>((offset >> 7) + 1);
+ _data = static_cast<unsigned char>((offset & 127) + 1);
+ }
+ else
+ {
+ ptrdiff_t remainder = offset - ((*base - 1) << 7);
+
+ if (static_cast<uintptr_t>(remainder) <= 253)
+ {
+ _data = static_cast<unsigned char>(remainder + 1);
+ }
+ else
+ {
+ compact_set_value<header_offset>(this, value);
+
+ _data = 255;
+ }
+ }
+ }
+ else
+ {
+ compact_set_value<header_offset>(this, value);
+
+ _data = 255;
+ }
+ }
+ else
+ {
+ _data = 0;
+ }
+ }
+
+ operator char_t*() const
+ {
+ if (_data)
+ {
+ if (_data < 255)
+ {
+ xml_memory_page* page = compact_get_page(this, header_offset);
+
+ // round-trip through void* to silence 'cast increases required alignment of target type' warnings
+ const uint16_t* base = reinterpret_cast<const uint16_t*>(static_cast<const void*>(reinterpret_cast<const char*>(this) - base_offset));
+ assert(*base);
+
+ ptrdiff_t offset = ((*base - 1) << 7) + (_data - 1);
+
+ return page->compact_string_base + offset;
+ }
+ else
+ {
+ return compact_get_value<header_offset, char_t>(this);
+ }
+ }
+ else
+ return NULL;
+ }
+
+ private:
+ unsigned char _data;
+ };
+PUGI_IMPL_NS_END
+#endif
+
+#ifdef PUGIXML_COMPACT
+namespace pugi
+{
+ struct xml_attribute_struct
+ {
+ xml_attribute_struct(impl::xml_memory_page* page): header(page, 0), namevalue_base(0)
+ {
+ PUGI_IMPL_STATIC_ASSERT(sizeof(xml_attribute_struct) == 8);
+ }
+
+ impl::compact_header header;
+
+ uint16_t namevalue_base;
+
+ impl::compact_string<4, 2> name;
+ impl::compact_string<5, 3> value;
+
+ impl::compact_pointer<xml_attribute_struct, 6> prev_attribute_c;
+ impl::compact_pointer<xml_attribute_struct, 7, 0> next_attribute;
+ };
+
+ struct xml_node_struct
+ {
+ xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(page, type), namevalue_base(0)
+ {
+ PUGI_IMPL_STATIC_ASSERT(sizeof(xml_node_struct) == 12);
+ }
+
+ impl::compact_header header;
+
+ uint16_t namevalue_base;
+
+ impl::compact_string<4, 2> name;
+ impl::compact_string<5, 3> value;
+
+ impl::compact_pointer_parent<xml_node_struct, 6> parent;
+
+ impl::compact_pointer<xml_node_struct, 8, 0> first_child;
+
+ impl::compact_pointer<xml_node_struct, 9> prev_sibling_c;
+ impl::compact_pointer<xml_node_struct, 10, 0> next_sibling;
+
+ impl::compact_pointer<xml_attribute_struct, 11, 0> first_attribute;
+ };
+}
+#else
+namespace pugi
+{
+ struct xml_attribute_struct
+ {
+ xml_attribute_struct(impl::xml_memory_page* page): name(NULL), value(NULL), prev_attribute_c(NULL), next_attribute(NULL)
+ {
+ header = PUGI_IMPL_GETHEADER_IMPL(this, page, 0);
+ }
+
+ uintptr_t header;
+
+ char_t* name;
+ char_t* value;
+
+ xml_attribute_struct* prev_attribute_c;
+ xml_attribute_struct* next_attribute;
+ };
+
+ struct xml_node_struct
+ {
+ xml_node_struct(impl::xml_memory_page* page, xml_node_type type): name(NULL), value(NULL), parent(NULL), first_child(NULL), prev_sibling_c(NULL), next_sibling(NULL), first_attribute(NULL)
+ {
+ header = PUGI_IMPL_GETHEADER_IMPL(this, page, type);
+ }
+
+ uintptr_t header;
+
+ char_t* name;
+ char_t* value;
+
+ xml_node_struct* parent;
+
+ xml_node_struct* first_child;
+
+ xml_node_struct* prev_sibling_c;
+ xml_node_struct* next_sibling;
+
+ xml_attribute_struct* first_attribute;
+ };
+}
+#endif
+
+PUGI_IMPL_NS_BEGIN
+ struct xml_extra_buffer
+ {
+ char_t* buffer;
+ xml_extra_buffer* next;
+ };
+
+ struct xml_document_struct: public xml_node_struct, public xml_allocator
+ {
+ xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(NULL), extra_buffers(NULL)
+ {
+ }
+
+ const char_t* buffer;
+
+ xml_extra_buffer* extra_buffers;
+
+ #ifdef PUGIXML_COMPACT
+ compact_hash_table hash;
+ #endif
+ };
+
+ template <typename Object> inline xml_allocator& get_allocator(const Object* object)
+ {
+ assert(object);
+
+ return *PUGI_IMPL_GETPAGE(object)->allocator;
+ }
+
+ template <typename Object> inline xml_document_struct& get_document(const Object* object)
+ {
+ assert(object);
+
+ return *static_cast<xml_document_struct*>(PUGI_IMPL_GETPAGE(object)->allocator);
+ }
+PUGI_IMPL_NS_END
+
+// Low-level DOM operations
+PUGI_IMPL_NS_BEGIN
+ inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc)
+ {
+ xml_memory_page* page;
+ void* memory = alloc.allocate_object(sizeof(xml_attribute_struct), page);
+ if (!memory) return NULL;
+
+ return new (memory) xml_attribute_struct(page);
+ }
+
+ inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type)
+ {
+ xml_memory_page* page;
+ void* memory = alloc.allocate_object(sizeof(xml_node_struct), page);
+ if (!memory) return NULL;
+
+ return new (memory) xml_node_struct(page, type);
+ }
+
+ inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc)
+ {
+ if (a->header & impl::xml_memory_page_name_allocated_mask)
+ alloc.deallocate_string(a->name);
+
+ if (a->header & impl::xml_memory_page_value_allocated_mask)
+ alloc.deallocate_string(a->value);
+
+ alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI_IMPL_GETPAGE(a));
+ }
+
+ inline void destroy_node(xml_node_struct* n, xml_allocator& alloc)
+ {
+ if (n->header & impl::xml_memory_page_name_allocated_mask)
+ alloc.deallocate_string(n->name);
+
+ if (n->header & impl::xml_memory_page_value_allocated_mask)
+ alloc.deallocate_string(n->value);
+
+ for (xml_attribute_struct* attr = n->first_attribute; attr; )
+ {
+ xml_attribute_struct* next = attr->next_attribute;
+
+ destroy_attribute(attr, alloc);
+
+ attr = next;
+ }
+
+ alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI_IMPL_GETPAGE(n));
+ }
+
+ inline void destroy_tree(xml_node_struct* n, xml_allocator& alloc)
+ {
+ xml_node_struct* cur = n;
+
+ while (cur->first_child)
+ cur = cur->first_child;
+
+ while (cur != n)
+ {
+ // loop invariant: cur is inside the subtree rooted at n, cur's subtree is destroyed
+ xml_node_struct* parent = cur->parent;
+ xml_node_struct* next = cur->next_sibling;
+
+ destroy_node(cur, alloc);
+
+ if (next)
+ {
+ cur = next;
+
+ while (cur->first_child)
+ cur = cur->first_child;
+ }
+ else
+ cur = parent;
+ }
+
+ destroy_node(n, alloc);
+ }
+
+ inline void append_node(xml_node_struct* child, xml_node_struct* node)
+ {
+ child->parent = node;
+
+ xml_node_struct* head = node->first_child;
+
+ if (head)
+ {
+ xml_node_struct* tail = head->prev_sibling_c;
+
+ tail->next_sibling = child;
+ child->prev_sibling_c = tail;
+ head->prev_sibling_c = child;
+ }
+ else
+ {
+ node->first_child = child;
+ child->prev_sibling_c = child;
+ }
+ }
+
+ inline void prepend_node(xml_node_struct* child, xml_node_struct* node)
+ {
+ child->parent = node;
+
+ xml_node_struct* head = node->first_child;
+
+ if (head)
+ {
+ child->prev_sibling_c = head->prev_sibling_c;
+ head->prev_sibling_c = child;
+ }
+ else
+ child->prev_sibling_c = child;
+
+ child->next_sibling = head;
+ node->first_child = child;
+ }
+
+ inline void insert_node_after(xml_node_struct* child, xml_node_struct* node)
+ {
+ xml_node_struct* parent = node->parent;
+
+ child->parent = parent;
+
+ xml_node_struct* next = node->next_sibling;
+
+ if (next)
+ next->prev_sibling_c = child;
+ else
+ parent->first_child->prev_sibling_c = child;
+
+ child->next_sibling = next;
+ child->prev_sibling_c = node;
+
+ node->next_sibling = child;
+ }
+
+ inline void insert_node_before(xml_node_struct* child, xml_node_struct* node)
+ {
+ xml_node_struct* parent = node->parent;
+
+ child->parent = parent;
+
+ xml_node_struct* prev = node->prev_sibling_c;
+
+ if (prev->next_sibling)
+ prev->next_sibling = child;
+ else
+ parent->first_child = child;
+
+ child->prev_sibling_c = prev;
+ child->next_sibling = node;
+
+ node->prev_sibling_c = child;
+ }
+
+ inline void remove_node(xml_node_struct* node)
+ {
+ xml_node_struct* parent = node->parent;
+
+ xml_node_struct* next = node->next_sibling;
+ xml_node_struct* prev = node->prev_sibling_c;
+
+ if (next)
+ next->prev_sibling_c = prev;
+ else
+ parent->first_child->prev_sibling_c = prev;
+
+ if (prev->next_sibling)
+ prev->next_sibling = next;
+ else
+ parent->first_child = next;
+
+ node->parent = NULL;
+ node->prev_sibling_c = NULL;
+ node->next_sibling = NULL;
+ }
+
+ inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node)
+ {
+ xml_attribute_struct* head = node->first_attribute;
+
+ if (head)
+ {
+ xml_attribute_struct* tail = head->prev_attribute_c;
+
+ tail->next_attribute = attr;
+ attr->prev_attribute_c = tail;
+ head->prev_attribute_c = attr;
+ }
+ else
+ {
+ node->first_attribute = attr;
+ attr->prev_attribute_c = attr;
+ }
+ }
+
+ inline void prepend_attribute(xml_attribute_struct* attr, xml_node_struct* node)
+ {
+ xml_attribute_struct* head = node->first_attribute;
+
+ if (head)
+ {
+ attr->prev_attribute_c = head->prev_attribute_c;
+ head->prev_attribute_c = attr;
+ }
+ else
+ attr->prev_attribute_c = attr;
+
+ attr->next_attribute = head;
+ node->first_attribute = attr;
+ }
+
+ inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node)
+ {
+ xml_attribute_struct* next = place->next_attribute;
+
+ if (next)
+ next->prev_attribute_c = attr;
+ else
+ node->first_attribute->prev_attribute_c = attr;
+
+ attr->next_attribute = next;
+ attr->prev_attribute_c = place;
+ place->next_attribute = attr;
+ }
+
+ inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node)
+ {
+ xml_attribute_struct* prev = place->prev_attribute_c;
+
+ if (prev->next_attribute)
+ prev->next_attribute = attr;
+ else
+ node->first_attribute = attr;
+
+ attr->prev_attribute_c = prev;
+ attr->next_attribute = place;
+ place->prev_attribute_c = attr;
+ }
+
+ inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node)
+ {
+ xml_attribute_struct* next = attr->next_attribute;
+ xml_attribute_struct* prev = attr->prev_attribute_c;
+
+ if (next)
+ next->prev_attribute_c = prev;
+ else
+ node->first_attribute->prev_attribute_c = prev;
+
+ if (prev->next_attribute)
+ prev->next_attribute = next;
+ else
+ node->first_attribute = next;
+
+ attr->prev_attribute_c = NULL;
+ attr->next_attribute = NULL;
+ }
+
+ PUGI_IMPL_FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element)
+ {
+ if (!alloc.reserve()) return NULL;
+
+ xml_node_struct* child = allocate_node(alloc, type);
+ if (!child) return NULL;
+
+ append_node(child, node);
+
+ return child;
+ }
+
+ PUGI_IMPL_FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc)
+ {
+ if (!alloc.reserve()) return NULL;
+
+ xml_attribute_struct* attr = allocate_attribute(alloc);
+ if (!attr) return NULL;
+
+ append_attribute(attr, node);
+
+ return attr;
+ }
+PUGI_IMPL_NS_END
+
+// Helper classes for code generation
+PUGI_IMPL_NS_BEGIN
+ struct opt_false
+ {
+ enum { value = 0 };
+ };
+
+ struct opt_true
+ {
+ enum { value = 1 };
+ };
+PUGI_IMPL_NS_END
+
+// Unicode utilities
+PUGI_IMPL_NS_BEGIN
+ inline uint16_t endian_swap(uint16_t value)
+ {
+ return static_cast<uint16_t>(((value & 0xff) << 8) | (value >> 8));
+ }
+
+ inline uint32_t endian_swap(uint32_t value)
+ {
+ return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24);
+ }
+
+ struct utf8_counter
+ {
+ typedef size_t value_type;
+
+ static value_type low(value_type result, uint32_t ch)
+ {
+ // U+0000..U+007F
+ if (ch < 0x80) return result + 1;
+ // U+0080..U+07FF
+ else if (ch < 0x800) return result + 2;
+ // U+0800..U+FFFF
+ else return result + 3;
+ }
+
+ static value_type high(value_type result, uint32_t)
+ {
+ // U+10000..U+10FFFF
+ return result + 4;
+ }
+ };
+
+ struct utf8_writer
+ {
+ typedef uint8_t* value_type;
+
+ static value_type low(value_type result, uint32_t ch)
+ {
+ // U+0000..U+007F
+ if (ch < 0x80)
+ {
+ *result = static_cast<uint8_t>(ch);
+ return result + 1;
+ }
+ // U+0080..U+07FF
+ else if (ch < 0x800)
+ {
+ result[0] = static_cast<uint8_t>(0xC0 | (ch >> 6));
+ result[1] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+ return result + 2;
+ }
+ // U+0800..U+FFFF
+ else
+ {
+ result[0] = static_cast<uint8_t>(0xE0 | (ch >> 12));
+ result[1] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F));
+ result[2] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+ return result + 3;
+ }
+ }
+
+ static value_type high(value_type result, uint32_t ch)
+ {
+ // U+10000..U+10FFFF
+ result[0] = static_cast<uint8_t>(0xF0 | (ch >> 18));
+ result[1] = static_cast<uint8_t>(0x80 | ((ch >> 12) & 0x3F));
+ result[2] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F));
+ result[3] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+ return result + 4;
+ }
+
+ static value_type any(value_type result, uint32_t ch)
+ {
+ return (ch < 0x10000) ? low(result, ch) : high(result, ch);
+ }
+ };
+
+ struct utf16_counter
+ {
+ typedef size_t value_type;
+
+ static value_type low(value_type result, uint32_t)
+ {
+ return result + 1;
+ }
+
+ static value_type high(value_type result, uint32_t)
+ {
+ return result + 2;
+ }
+ };
+
+ struct utf16_writer
+ {
+ typedef uint16_t* value_type;
+
+ static value_type low(value_type result, uint32_t ch)
+ {
+ *result = static_cast<uint16_t>(ch);
+
+ return result + 1;
+ }
+
+ static value_type high(value_type result, uint32_t ch)
+ {
+ uint32_t msh = (ch - 0x10000U) >> 10;
+ uint32_t lsh = (ch - 0x10000U) & 0x3ff;
+
+ result[0] = static_cast<uint16_t>(0xD800 + msh);
+ result[1] = static_cast<uint16_t>(0xDC00 + lsh);
+
+ return result + 2;
+ }
+
+ static value_type any(value_type result, uint32_t ch)
+ {
+ return (ch < 0x10000) ? low(result, ch) : high(result, ch);
+ }
+ };
+
+ struct utf32_counter
+ {
+ typedef size_t value_type;
+
+ static value_type low(value_type result, uint32_t)
+ {
+ return result + 1;
+ }
+
+ static value_type high(value_type result, uint32_t)
+ {
+ return result + 1;
+ }
+ };
+
+ struct utf32_writer
+ {
+ typedef uint32_t* value_type;
+
+ static value_type low(value_type result, uint32_t ch)
+ {
+ *result = ch;
+
+ return result + 1;
+ }
+
+ static value_type high(value_type result, uint32_t ch)
+ {
+ *result = ch;
+
+ return result + 1;
+ }
+
+ static value_type any(value_type result, uint32_t ch)
+ {
+ *result = ch;
+
+ return result + 1;
+ }
+ };
+
+ struct latin1_writer
+ {
+ typedef uint8_t* value_type;
+
+ static value_type low(value_type result, uint32_t ch)
+ {
+ *result = static_cast<uint8_t>(ch > 255 ? '?' : ch);
+
+ return result + 1;
+ }
+
+ static value_type high(value_type result, uint32_t ch)
+ {
+ (void)ch;
+
+ *result = '?';
+
+ return result + 1;
+ }
+ };
+
+ struct utf8_decoder
+ {
+ typedef uint8_t type;
+
+ template <typename Traits> static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits)
+ {
+ const uint8_t utf8_byte_mask = 0x3f;
+
+ while (size)
+ {
+ uint8_t lead = *data;
+
+ // 0xxxxxxx -> U+0000..U+007F
+ if (lead < 0x80)
+ {
+ result = Traits::low(result, lead);
+ data += 1;
+ size -= 1;
+
+ // process aligned single-byte (ascii) blocks
+ if ((reinterpret_cast<uintptr_t>(data) & 3) == 0)
+ {
+ // round-trip through void* to silence 'cast increases required alignment of target type' warnings
+ while (size >= 4 && (*static_cast<const uint32_t*>(static_cast<const void*>(data)) & 0x80808080) == 0)
+ {
+ result = Traits::low(result, data[0]);
+ result = Traits::low(result, data[1]);
+ result = Traits::low(result, data[2]);
+ result = Traits::low(result, data[3]);
+ data += 4;
+ size -= 4;
+ }
+ }
+ }
+ // 110xxxxx -> U+0080..U+07FF
+ else if (static_cast<unsigned int>(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80)
+ {
+ result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask));
+ data += 2;
+ size -= 2;
+ }
+ // 1110xxxx -> U+0800-U+FFFF
+ else if (static_cast<unsigned int>(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80)
+ {
+ result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask));
+ data += 3;
+ size -= 3;
+ }
+ // 11110xxx -> U+10000..U+10FFFF
+ else if (static_cast<unsigned int>(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80)
+ {
+ result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask));
+ data += 4;
+ size -= 4;
+ }
+ // 10xxxxxx or 11111xxx -> invalid
+ else
+ {
+ data += 1;
+ size -= 1;
+ }
+ }
+
+ return result;
+ }
+ };
+
+ template <typename opt_swap> struct utf16_decoder
+ {
+ typedef uint16_t type PUGI_IMPL_UNALIGNED;
+
+ template <typename Traits> static inline typename Traits::value_type process(const type* data, size_t size, typename Traits::value_type result, Traits)
+ {
+ while (size)
+ {
+ uint16_t lead = opt_swap::value ? endian_swap(*data) : *data;
+
+ // U+0000..U+D7FF
+ if (lead < 0xD800)
+ {
+ result = Traits::low(result, lead);
+ data += 1;
+ size -= 1;
+ }
+ // U+E000..U+FFFF
+ else if (static_cast<unsigned int>(lead - 0xE000) < 0x2000)
+ {
+ result = Traits::low(result, lead);
+ data += 1;
+ size -= 1;
+ }
+ // surrogate pair lead
+ else if (static_cast<unsigned int>(lead - 0xD800) < 0x400 && size >= 2)
+ {
+ uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1];
+
+ if (static_cast<unsigned int>(next - 0xDC00) < 0x400)
+ {
+ result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff));
+ data += 2;
+ size -= 2;
+ }
+ else
+ {
+ data += 1;
+ size -= 1;
+ }
+ }
+ else
+ {
+ data += 1;
+ size -= 1;
+ }
+ }
+
+ return result;
+ }
+ };
+
+ template <typename opt_swap> struct utf32_decoder
+ {
+ typedef uint32_t type PUGI_IMPL_UNALIGNED;
+
+ template <typename Traits> static inline typename Traits::value_type process(const type* data, size_t size, typename Traits::value_type result, Traits)
+ {
+ while (size)
+ {
+ uint32_t lead = opt_swap::value ? endian_swap(*data) : *data;
+
+ // U+0000..U+FFFF
+ if (lead < 0x10000)
+ {
+ result = Traits::low(result, lead);
+ data += 1;
+ size -= 1;
+ }
+ // U+10000..U+10FFFF
+ else
+ {
+ result = Traits::high(result, lead);
+ data += 1;
+ size -= 1;
+ }
+ }
+
+ return result;
+ }
+ };
+
+ struct latin1_decoder
+ {
+ typedef uint8_t type;
+
+ template <typename Traits> static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits)
+ {
+ while (size)
+ {
+ result = Traits::low(result, *data);
+ data += 1;
+ size -= 1;
+ }
+
+ return result;
+ }
+ };
+
+ template <size_t size> struct wchar_selector;
+
+ template <> struct wchar_selector<2>
+ {
+ typedef uint16_t type;
+ typedef utf16_counter counter;
+ typedef utf16_writer writer;
+ typedef utf16_decoder<opt_false> decoder;
+ };
+
+ template <> struct wchar_selector<4>
+ {
+ typedef uint32_t type;
+ typedef utf32_counter counter;
+ typedef utf32_writer writer;
+ typedef utf32_decoder<opt_false> decoder;
+ };
+
+ typedef wchar_selector<sizeof(wchar_t)>::counter wchar_counter;
+ typedef wchar_selector<sizeof(wchar_t)>::writer wchar_writer;
+
+ struct wchar_decoder
+ {
+ typedef wchar_t type;
+
+ template <typename Traits> static inline typename Traits::value_type process(const wchar_t* data, size_t size, typename Traits::value_type result, Traits traits)
+ {
+ typedef wchar_selector<sizeof(wchar_t)>::decoder decoder;
+
+ return decoder::process(reinterpret_cast<const typename decoder::type*>(data), size, result, traits);
+ }
+ };
+
+#ifdef PUGIXML_WCHAR_MODE
+ PUGI_IMPL_FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length)
+ {
+ for (size_t i = 0; i < length; ++i)
+ result[i] = static_cast<wchar_t>(endian_swap(static_cast<wchar_selector<sizeof(wchar_t)>::type>(data[i])));
+ }
+#endif
+PUGI_IMPL_NS_END
+
+PUGI_IMPL_NS_BEGIN
+ enum chartype_t
+ {
+ ct_parse_pcdata = 1, // \0, &, \r, <
+ ct_parse_attr = 2, // \0, &, \r, ', "
+ ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab
+ ct_space = 8, // \r, \n, space, tab
+ ct_parse_cdata = 16, // \0, ], >, \r
+ ct_parse_comment = 32, // \0, -, >, \r
+ ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, .
+ ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, :
+ };
+
+ static const unsigned char chartype_table[256] =
+ {
+ 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
+ 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63
+ 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95
+ 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127
+
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192
+ };
+
+ enum chartypex_t
+ {
+ ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, >
+ ctx_special_attr = 2, // Any symbol >= 0 and < 32, &, <, ", '
+ ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _
+ ctx_digit = 8, // 0-9
+ ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, .
+ };
+
+ static const unsigned char chartypex_table[256] =
+ {
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, // 0-15
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31
+ 0, 0, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 1, 0, // 48-63
+
+ 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95
+ 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127
+
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ };
+
+#ifdef PUGIXML_WCHAR_MODE
+ #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) ((static_cast<unsigned int>(c) < 128 ? table[static_cast<unsigned int>(c)] : table[128]) & (ct))
+#else
+ #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast<unsigned char>(c)] & (ct))
+#endif
+
+ #define PUGI_IMPL_IS_CHARTYPE(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartype_table)
+ #define PUGI_IMPL_IS_CHARTYPEX(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartypex_table)
+
+ PUGI_IMPL_FN bool is_little_endian()
+ {
+ unsigned int ui = 1;
+
+ return *reinterpret_cast<unsigned char*>(&ui) == 1;
+ }
+
+ PUGI_IMPL_FN xml_encoding get_wchar_encoding()
+ {
+ PUGI_IMPL_STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4);
+
+ if (sizeof(wchar_t) == 2)
+ return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+ else
+ return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+ }
+
+ PUGI_IMPL_FN bool parse_declaration_encoding(const uint8_t* data, size_t size, const uint8_t*& out_encoding, size_t& out_length)
+ {
+ #define PUGI_IMPL_SCANCHAR(ch) do { if (offset >= size || data[offset] != ch) return false; offset++; } while (0)
+ #define PUGI_IMPL_SCANCHARTYPE(ct) do { while (offset < size && PUGI_IMPL_IS_CHARTYPE(data[offset], ct)) offset++; } while (0)
+
+ // check if we have a non-empty XML declaration
+ if (size < 6 || !(((data[0] == '<') & (data[1] == '?') & (data[2] == 'x') & (data[3] == 'm') & (data[4] == 'l')) && PUGI_IMPL_IS_CHARTYPE(data[5], ct_space)))
+ return false;
+
+ // scan XML declaration until the encoding field
+ for (size_t i = 6; i + 1 < size; ++i)
+ {
+ // declaration can not contain ? in quoted values
+ if (data[i] == '?')
+ return false;
+
+ if (data[i] == 'e' && data[i + 1] == 'n')
+ {
+ size_t offset = i;
+
+ // encoding follows the version field which can't contain 'en' so this has to be the encoding if XML is well formed
+ PUGI_IMPL_SCANCHAR('e'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('c'); PUGI_IMPL_SCANCHAR('o');
+ PUGI_IMPL_SCANCHAR('d'); PUGI_IMPL_SCANCHAR('i'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('g');
+
+ // S? = S?
+ PUGI_IMPL_SCANCHARTYPE(ct_space);
+ PUGI_IMPL_SCANCHAR('=');
+ PUGI_IMPL_SCANCHARTYPE(ct_space);
+
+ // the only two valid delimiters are ' and "
+ uint8_t delimiter = (offset < size && data[offset] == '"') ? '"' : '\'';
+
+ PUGI_IMPL_SCANCHAR(delimiter);
+
+ size_t start = offset;
+
+ out_encoding = data + offset;
+
+ PUGI_IMPL_SCANCHARTYPE(ct_symbol);
+
+ out_length = offset - start;
+
+ PUGI_IMPL_SCANCHAR(delimiter);
+
+ return true;
+ }
+ }
+
+ return false;
+
+ #undef PUGI_IMPL_SCANCHAR
+ #undef PUGI_IMPL_SCANCHARTYPE
+ }
+
+ PUGI_IMPL_FN xml_encoding guess_buffer_encoding(const uint8_t* data, size_t size)
+ {
+ // skip encoding autodetection if input buffer is too small
+ if (size < 4) return encoding_utf8;
+
+ uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3];
+
+ // look for BOM in first few bytes
+ if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be;
+ if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le;
+ if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be;
+ if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le;
+ if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8;
+
+ // look for <, <? or <?xm in various encodings
+ if (d0 == 0 && d1 == 0 && d2 == 0 && d3 == 0x3c) return encoding_utf32_be;
+ if (d0 == 0x3c && d1 == 0 && d2 == 0 && d3 == 0) return encoding_utf32_le;
+ if (d0 == 0 && d1 == 0x3c && d2 == 0 && d3 == 0x3f) return encoding_utf16_be;
+ if (d0 == 0x3c && d1 == 0 && d2 == 0x3f && d3 == 0) return encoding_utf16_le;
+
+ // look for utf16 < followed by node name (this may fail, but is better than utf8 since it's zero terminated so early)
+ if (d0 == 0 && d1 == 0x3c) return encoding_utf16_be;
+ if (d0 == 0x3c && d1 == 0) return encoding_utf16_le;
+
+ // no known BOM detected; parse declaration
+ const uint8_t* enc = NULL;
+ size_t enc_length = 0;
+
+ if (d0 == 0x3c && d1 == 0x3f && d2 == 0x78 && d3 == 0x6d && parse_declaration_encoding(data, size, enc, enc_length))
+ {
+ // iso-8859-1 (case-insensitive)
+ if (enc_length == 10
+ && (enc[0] | ' ') == 'i' && (enc[1] | ' ') == 's' && (enc[2] | ' ') == 'o'
+ && enc[3] == '-' && enc[4] == '8' && enc[5] == '8' && enc[6] == '5' && enc[7] == '9'
+ && enc[8] == '-' && enc[9] == '1')
+ return encoding_latin1;
+
+ // latin1 (case-insensitive)
+ if (enc_length == 6
+ && (enc[0] | ' ') == 'l' && (enc[1] | ' ') == 'a' && (enc[2] | ' ') == 't'
+ && (enc[3] | ' ') == 'i' && (enc[4] | ' ') == 'n'
+ && enc[5] == '1')
+ return encoding_latin1;
+ }
+
+ return encoding_utf8;
+ }
+
+ PUGI_IMPL_FN xml_encoding get_buffer_encoding(xml_encoding encoding, const void* contents, size_t size)
+ {
+ // replace wchar encoding with utf implementation
+ if (encoding == encoding_wchar) return get_wchar_encoding();
+
+ // replace utf16 encoding with utf16 with specific endianness
+ if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ // replace utf32 encoding with utf32 with specific endianness
+ if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ // only do autodetection if no explicit encoding is requested
+ if (encoding != encoding_auto) return encoding;
+
+ // try to guess encoding (based on XML specification, Appendix F.1)
+ const uint8_t* data = static_cast<const uint8_t*>(contents);
+
+ return guess_buffer_encoding(data, size);
+ }
+
+ PUGI_IMPL_FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+ {
+ size_t length = size / sizeof(char_t);
+ assert(contents || length == 0);
+
+ if (is_mutable)
+ {
+ out_buffer = static_cast<char_t*>(const_cast<void*>(contents));
+ out_length = length;
+ }
+ else
+ {
+ char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!buffer) return false;
+
+ if (contents)
+ memcpy(buffer, contents, length * sizeof(char_t));
+
+ buffer[length] = 0;
+
+ out_buffer = buffer;
+ out_length = length + 1;
+ }
+
+ return true;
+ }
+
+#ifdef PUGIXML_WCHAR_MODE
+ PUGI_IMPL_FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re)
+ {
+ return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) ||
+ (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be);
+ }
+
+ PUGI_IMPL_FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+ {
+ const char_t* data = static_cast<const char_t*>(contents);
+ size_t length = size / sizeof(char_t);
+
+ if (is_mutable)
+ {
+ char_t* buffer = const_cast<char_t*>(data);
+
+ convert_wchar_endian_swap(buffer, data, length);
+
+ out_buffer = buffer;
+ out_length = length;
+ }
+ else
+ {
+ char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!buffer) return false;
+
+ convert_wchar_endian_swap(buffer, data, length);
+ buffer[length] = 0;
+
+ out_buffer = buffer;
+ out_length = length + 1;
+ }
+
+ return true;
+ }
+
+ template <typename D> PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D)
+ {
+ const typename D::type* data = static_cast<const typename D::type*>(contents);
+ size_t data_length = size / sizeof(typename D::type);
+
+ // first pass: get length in wchar_t units
+ size_t length = D::process(data, data_length, 0, wchar_counter());
+ if (static_cast<size_t>(-1) / sizeof(char_t) <= length) return false;
+
+ // allocate buffer of suitable length
+ char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!buffer) return false;
+
+ // second pass: convert input to wchar_t
+ wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer);
+ wchar_writer::value_type oend = D::process(data, data_length, obegin, wchar_writer());
+
+ assert(oend == obegin + length);
+ *oend = 0;
+
+ out_buffer = buffer;
+ out_length = length + 1;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
+ {
+ // get native encoding
+ xml_encoding wchar_encoding = get_wchar_encoding();
+
+ // fast path: no conversion required
+ if (encoding == wchar_encoding)
+ return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+ // only endian-swapping is required
+ if (need_endian_swap_utf(encoding, wchar_encoding))
+ return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable);
+
+ // source encoding is utf8
+ if (encoding == encoding_utf8)
+ return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder());
+
+ // source encoding is utf16
+ if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ return (native_encoding == encoding) ?
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) :
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>());
+ }
+
+ // source encoding is utf32
+ if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ return (native_encoding == encoding) ?
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) :
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>());
+ }
+
+ // source encoding is latin1
+ if (encoding == encoding_latin1)
+ return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder());
+
+ assert(false && "Invalid encoding"); // unreachable
+ return false;
+ }
+#else
+ template <typename D> PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D)
+ {
+ const typename D::type* data = static_cast<const typename D::type*>(contents);
+ size_t data_length = size / sizeof(typename D::type);
+
+ // first pass: get length in utf8 units
+ size_t length = D::process(data, data_length, 0, utf8_counter());
+
+ // allocate buffer of suitable length
+ char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!buffer) return false;
+
+ // second pass: convert input to utf8
+ uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer);
+ uint8_t* oend = D::process(data, data_length, obegin, utf8_writer());
+
+ assert(oend == obegin + length);
+ *oend = 0;
+
+ out_buffer = buffer;
+ out_length = length + 1;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size)
+ {
+ for (size_t i = 0; i < size; ++i)
+ if (data[i] > 127)
+ return i;
+
+ return size;
+ }
+
+ PUGI_IMPL_FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+ {
+ const uint8_t* data = static_cast<const uint8_t*>(contents);
+ size_t data_length = size;
+
+ // get size of prefix that does not need utf8 conversion
+ size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length);
+ assert(prefix_length <= data_length);
+
+ const uint8_t* postfix = data + prefix_length;
+ size_t postfix_length = data_length - prefix_length;
+
+ // if no conversion is needed, just return the original buffer
+ if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+ // first pass: get length in utf8 units
+ size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter());
+
+ // allocate buffer of suitable length
+ char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!buffer) return false;
+
+ // second pass: convert latin1 input to utf8
+ memcpy(buffer, data, prefix_length);
+
+ uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer);
+ uint8_t* oend = latin1_decoder::process(postfix, postfix_length, obegin + prefix_length, utf8_writer());
+
+ assert(oend == obegin + length);
+ *oend = 0;
+
+ out_buffer = buffer;
+ out_length = length + 1;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
+ {
+ // fast path: no conversion required
+ if (encoding == encoding_utf8)
+ return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+ // source encoding is utf16
+ if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ return (native_encoding == encoding) ?
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) :
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>());
+ }
+
+ // source encoding is utf32
+ if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ return (native_encoding == encoding) ?
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) :
+ convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>());
+ }
+
+ // source encoding is latin1
+ if (encoding == encoding_latin1)
+ return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable);
+
+ assert(false && "Invalid encoding"); // unreachable
+ return false;
+ }
+#endif
+
+ PUGI_IMPL_FN size_t as_utf8_begin(const wchar_t* str, size_t length)
+ {
+ // get length in utf8 characters
+ return wchar_decoder::process(str, length, 0, utf8_counter());
+ }
+
+ PUGI_IMPL_FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length)
+ {
+ // convert to utf8
+ uint8_t* begin = reinterpret_cast<uint8_t*>(buffer);
+ uint8_t* end = wchar_decoder::process(str, length, begin, utf8_writer());
+
+ assert(begin + size == end);
+ (void)!end;
+ (void)!size;
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN std::string as_utf8_impl(const wchar_t* str, size_t length)
+ {
+ // first pass: get length in utf8 characters
+ size_t size = as_utf8_begin(str, length);
+
+ // allocate resulting string
+ std::string result;
+ result.resize(size);
+
+ // second pass: convert to utf8
+ if (size > 0) as_utf8_end(&result[0], size, str, length);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN std::basic_string<wchar_t> as_wide_impl(const char* str, size_t size)
+ {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(str);
+
+ // first pass: get length in wchar_t units
+ size_t length = utf8_decoder::process(data, size, 0, wchar_counter());
+
+ // allocate resulting string
+ std::basic_string<wchar_t> result;
+ result.resize(length);
+
+ // second pass: convert to wchar_t
+ if (length > 0)
+ {
+ wchar_writer::value_type begin = reinterpret_cast<wchar_writer::value_type>(&result[0]);
+ wchar_writer::value_type end = utf8_decoder::process(data, size, begin, wchar_writer());
+
+ assert(begin + length == end);
+ (void)!end;
+ }
+
+ return result;
+ }
+#endif
+
+ template <typename Header>
+ inline bool strcpy_insitu_allow(size_t length, const Header& header, uintptr_t header_mask, char_t* target)
+ {
+ // never reuse shared memory
+ if (header & xml_memory_page_contents_shared_mask) return false;
+
+ size_t target_length = strlength(target);
+
+ // always reuse document buffer memory if possible
+ if ((header & header_mask) == 0) return target_length >= length;
+
+ // reuse heap memory if waste is not too great
+ const size_t reuse_threshold = 32;
+
+ return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2);
+ }
+
+ template <typename String, typename Header>
+ PUGI_IMPL_FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length)
+ {
+ assert((header & header_mask) == 0 || dest); // header bit indicates whether dest was previously allocated
+
+ if (source_length == 0)
+ {
+ // empty string and null pointer are equivalent, so just deallocate old memory
+ xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator;
+
+ if (header & header_mask) alloc->deallocate_string(dest);
+
+ // mark the string as not allocated
+ dest = NULL;
+ header &= ~header_mask;
+
+ return true;
+ }
+ else if (dest && strcpy_insitu_allow(source_length, header, header_mask, dest))
+ {
+ // we can reuse old buffer, so just copy the new data (including zero terminator)
+ memcpy(dest, source, source_length * sizeof(char_t));
+ dest[source_length] = 0;
+
+ return true;
+ }
+ else
+ {
+ xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator;
+
+ if (!alloc->reserve()) return false;
+
+ // allocate new buffer
+ char_t* buf = alloc->allocate_string(source_length + 1);
+ if (!buf) return false;
+
+ // copy the string (including zero terminator)
+ memcpy(buf, source, source_length * sizeof(char_t));
+ buf[source_length] = 0;
+
+ // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures)
+ if (header & header_mask) alloc->deallocate_string(dest);
+
+ // the string is now allocated, so set the flag
+ dest = buf;
+ header |= header_mask;
+
+ return true;
+ }
+ }
+
+ struct gap
+ {
+ char_t* end;
+ size_t size;
+
+ gap(): end(NULL), size(0)
+ {
+ }
+
+ // Push new gap, move s count bytes further (skipping the gap).
+ // Collapse previous gap.
+ void push(char_t*& s, size_t count)
+ {
+ if (end) // there was a gap already; collapse it
+ {
+ // Move [old_gap_end, new_gap_start) to [old_gap_start, ...)
+ assert(s >= end);
+ memmove(end - size, end, (s - end) * sizeof(char_t));
+ }
+
+ s += count; // end of current gap
+
+ // "merge" two gaps
+ end = s;
+ size += count;
+ }
+
+ // Collapse all gaps, return past-the-end pointer
+ char_t* flush(char_t* s)
+ {
+ if (end)
+ {
+ // Move [old_gap_end, current_pos) to [old_gap_start, ...)
+ assert(s >= end);
+ memmove(end - size, end, (s - end) * sizeof(char_t));
+
+ return s - size;
+ }
+ else return s;
+ }
+ };
+
+ PUGI_IMPL_FN char_t* strconv_escape(char_t* s, gap& g)
+ {
+ char_t* stre = s + 1;
+
+ switch (*stre)
+ {
+ case '#': // &#...
+ {
+ unsigned int ucsc = 0;
+
+ if (stre[1] == 'x') // &#x... (hex code)
+ {
+ stre += 2;
+
+ char_t ch = *stre;
+
+ if (ch == ';') return stre;
+
+ for (;;)
+ {
+ if (static_cast<unsigned int>(ch - '0') <= 9)
+ ucsc = 16 * ucsc + (ch - '0');
+ else if (static_cast<unsigned int>((ch | ' ') - 'a') <= 5)
+ ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10);
+ else if (ch == ';')
+ break;
+ else // cancel
+ return stre;
+
+ ch = *++stre;
+ }
+
+ ++stre;
+ }
+ else // &#... (dec code)
+ {
+ char_t ch = *++stre;
+
+ if (ch == ';') return stre;
+
+ for (;;)
+ {
+ if (static_cast<unsigned int>(ch - '0') <= 9)
+ ucsc = 10 * ucsc + (ch - '0');
+ else if (ch == ';')
+ break;
+ else // cancel
+ return stre;
+
+ ch = *++stre;
+ }
+
+ ++stre;
+ }
+
+ #ifdef PUGIXML_WCHAR_MODE
+ s = reinterpret_cast<char_t*>(wchar_writer::any(reinterpret_cast<wchar_writer::value_type>(s), ucsc));
+ #else
+ s = reinterpret_cast<char_t*>(utf8_writer::any(reinterpret_cast<uint8_t*>(s), ucsc));
+ #endif
+
+ g.push(s, stre - s);
+ return stre;
+ }
+
+ case 'a': // &a
+ {
+ ++stre;
+
+ if (*stre == 'm') // &am
+ {
+ if (*++stre == 'p' && *++stre == ';') // &amp;
+ {
+ *s++ = '&';
+ ++stre;
+
+ g.push(s, stre - s);
+ return stre;
+ }
+ }
+ else if (*stre == 'p') // &ap
+ {
+ if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // &apos;
+ {
+ *s++ = '\'';
+ ++stre;
+
+ g.push(s, stre - s);
+ return stre;
+ }
+ }
+ break;
+ }
+
+ case 'g': // &g
+ {
+ if (*++stre == 't' && *++stre == ';') // &gt;
+ {
+ *s++ = '>';
+ ++stre;
+
+ g.push(s, stre - s);
+ return stre;
+ }
+ break;
+ }
+
+ case 'l': // &l
+ {
+ if (*++stre == 't' && *++stre == ';') // &lt;
+ {
+ *s++ = '<';
+ ++stre;
+
+ g.push(s, stre - s);
+ return stre;
+ }
+ break;
+ }
+
+ case 'q': // &q
+ {
+ if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // &quot;
+ {
+ *s++ = '"';
+ ++stre;
+
+ g.push(s, stre - s);
+ return stre;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return stre;
+ }
+
+ // Parser utilities
+ #define PUGI_IMPL_ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e)))
+ #define PUGI_IMPL_SKIPWS() do { while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) ++s; } while (0)
+ #define PUGI_IMPL_OPTSET(OPT) ( optmsk & (OPT) )
+ #define PUGI_IMPL_PUSHNODE(TYPE) do { cursor = append_new_node(cursor, *alloc, TYPE); if (!cursor) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s); } while (0)
+ #define PUGI_IMPL_POPNODE() do { cursor = cursor->parent; } while (0)
+ #define PUGI_IMPL_SCANFOR(X) do { while (*s != 0 && !(X)) ++s; } while (0)
+ #define PUGI_IMPL_SCANWHILE(X) do { while (X) ++s; } while (0)
+ #define PUGI_IMPL_SCANWHILE_UNROLL(X) do { for (;;) { char_t ss = s[0]; if (PUGI_IMPL_UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 3; break; } s += 4; } } while (0)
+ #define PUGI_IMPL_ENDSEG() do { ch = *s; *s = 0; ++s; } while (0)
+ #define PUGI_IMPL_THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast<char_t*>(NULL)
+ #define PUGI_IMPL_CHECK_ERROR(err, m) do { if (*s == 0) PUGI_IMPL_THROW_ERROR(err, m); } while (0)
+
+ PUGI_IMPL_FN char_t* strconv_comment(char_t* s, char_t endch)
+ {
+ gap g;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_comment));
+
+ if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+ {
+ *s++ = '\n'; // replace first one with 0x0a
+
+ if (*s == '\n') g.push(s, 1);
+ }
+ else if (s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>')) // comment ends here
+ {
+ *g.flush(s) = 0;
+
+ return s + (s[2] == '>' ? 3 : 2);
+ }
+ else if (*s == 0)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+
+ PUGI_IMPL_FN char_t* strconv_cdata(char_t* s, char_t endch)
+ {
+ gap g;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_cdata));
+
+ if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+ {
+ *s++ = '\n'; // replace first one with 0x0a
+
+ if (*s == '\n') g.push(s, 1);
+ }
+ else if (s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')) // CDATA ends here
+ {
+ *g.flush(s) = 0;
+
+ return s + 1;
+ }
+ else if (*s == 0)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+
+ typedef char_t* (*strconv_pcdata_t)(char_t*);
+
+ template <typename opt_trim, typename opt_eol, typename opt_escape> struct strconv_pcdata_impl
+ {
+ static char_t* parse(char_t* s)
+ {
+ gap g;
+
+ char_t* begin = s;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_pcdata));
+
+ if (*s == '<') // PCDATA ends here
+ {
+ char_t* end = g.flush(s);
+
+ if (opt_trim::value)
+ while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space))
+ --end;
+
+ *end = 0;
+
+ return s + 1;
+ }
+ else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+ {
+ *s++ = '\n'; // replace first one with 0x0a
+
+ if (*s == '\n') g.push(s, 1);
+ }
+ else if (opt_escape::value && *s == '&')
+ {
+ s = strconv_escape(s, g);
+ }
+ else if (*s == 0)
+ {
+ char_t* end = g.flush(s);
+
+ if (opt_trim::value)
+ while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space))
+ --end;
+
+ *end = 0;
+
+ return s;
+ }
+ else ++s;
+ }
+ }
+ };
+
+ PUGI_IMPL_FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask)
+ {
+ PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800);
+
+ switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (trim eol escapes); this simultaneously checks 3 options from assertion above
+ {
+ case 0: return strconv_pcdata_impl<opt_false, opt_false, opt_false>::parse;
+ case 1: return strconv_pcdata_impl<opt_false, opt_false, opt_true>::parse;
+ case 2: return strconv_pcdata_impl<opt_false, opt_true, opt_false>::parse;
+ case 3: return strconv_pcdata_impl<opt_false, opt_true, opt_true>::parse;
+ case 4: return strconv_pcdata_impl<opt_true, opt_false, opt_false>::parse;
+ case 5: return strconv_pcdata_impl<opt_true, opt_false, opt_true>::parse;
+ case 6: return strconv_pcdata_impl<opt_true, opt_true, opt_false>::parse;
+ case 7: return strconv_pcdata_impl<opt_true, opt_true, opt_true>::parse;
+ default: assert(false); return NULL; // unreachable
+ }
+ }
+
+ typedef char_t* (*strconv_attribute_t)(char_t*, char_t);
+
+ template <typename opt_escape> struct strconv_attribute_impl
+ {
+ static char_t* parse_wnorm(char_t* s, char_t end_quote)
+ {
+ gap g;
+
+ // trim leading whitespaces
+ if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space))
+ {
+ char_t* str = s;
+
+ do ++str;
+ while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space));
+
+ g.push(s, str - s);
+ }
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space));
+
+ if (*s == end_quote)
+ {
+ char_t* str = g.flush(s);
+
+ do *str-- = 0;
+ while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space));
+
+ return s + 1;
+ }
+ else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space))
+ {
+ *s++ = ' ';
+
+ if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space))
+ {
+ char_t* str = s + 1;
+ while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)) ++str;
+
+ g.push(s, str - s);
+ }
+ }
+ else if (opt_escape::value && *s == '&')
+ {
+ s = strconv_escape(s, g);
+ }
+ else if (!*s)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+
+ static char_t* parse_wconv(char_t* s, char_t end_quote)
+ {
+ gap g;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws));
+
+ if (*s == end_quote)
+ {
+ *g.flush(s) = 0;
+
+ return s + 1;
+ }
+ else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space))
+ {
+ if (*s == '\r')
+ {
+ *s++ = ' ';
+
+ if (*s == '\n') g.push(s, 1);
+ }
+ else *s++ = ' ';
+ }
+ else if (opt_escape::value && *s == '&')
+ {
+ s = strconv_escape(s, g);
+ }
+ else if (!*s)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+
+ static char_t* parse_eol(char_t* s, char_t end_quote)
+ {
+ gap g;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr));
+
+ if (*s == end_quote)
+ {
+ *g.flush(s) = 0;
+
+ return s + 1;
+ }
+ else if (*s == '\r')
+ {
+ *s++ = '\n';
+
+ if (*s == '\n') g.push(s, 1);
+ }
+ else if (opt_escape::value && *s == '&')
+ {
+ s = strconv_escape(s, g);
+ }
+ else if (!*s)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+
+ static char_t* parse_simple(char_t* s, char_t end_quote)
+ {
+ gap g;
+
+ while (true)
+ {
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr));
+
+ if (*s == end_quote)
+ {
+ *g.flush(s) = 0;
+
+ return s + 1;
+ }
+ else if (opt_escape::value && *s == '&')
+ {
+ s = strconv_escape(s, g);
+ }
+ else if (!*s)
+ {
+ return NULL;
+ }
+ else ++s;
+ }
+ }
+ };
+
+ PUGI_IMPL_FN strconv_attribute_t get_strconv_attribute(unsigned int optmask)
+ {
+ PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80);
+
+ switch ((optmask >> 4) & 15) // get bitmask for flags (wnorm wconv eol escapes); this simultaneously checks 4 options from assertion above
+ {
+ case 0: return strconv_attribute_impl<opt_false>::parse_simple;
+ case 1: return strconv_attribute_impl<opt_true>::parse_simple;
+ case 2: return strconv_attribute_impl<opt_false>::parse_eol;
+ case 3: return strconv_attribute_impl<opt_true>::parse_eol;
+ case 4: return strconv_attribute_impl<opt_false>::parse_wconv;
+ case 5: return strconv_attribute_impl<opt_true>::parse_wconv;
+ case 6: return strconv_attribute_impl<opt_false>::parse_wconv;
+ case 7: return strconv_attribute_impl<opt_true>::parse_wconv;
+ case 8: return strconv_attribute_impl<opt_false>::parse_wnorm;
+ case 9: return strconv_attribute_impl<opt_true>::parse_wnorm;
+ case 10: return strconv_attribute_impl<opt_false>::parse_wnorm;
+ case 11: return strconv_attribute_impl<opt_true>::parse_wnorm;
+ case 12: return strconv_attribute_impl<opt_false>::parse_wnorm;
+ case 13: return strconv_attribute_impl<opt_true>::parse_wnorm;
+ case 14: return strconv_attribute_impl<opt_false>::parse_wnorm;
+ case 15: return strconv_attribute_impl<opt_true>::parse_wnorm;
+ default: assert(false); return NULL; // unreachable
+ }
+ }
+
+ inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0)
+ {
+ xml_parse_result result;
+ result.status = status;
+ result.offset = offset;
+
+ return result;
+ }
+
+ struct xml_parser
+ {
+ xml_allocator* alloc;
+ char_t* error_offset;
+ xml_parse_status error_status;
+
+ xml_parser(xml_allocator* alloc_): alloc(alloc_), error_offset(NULL), error_status(status_ok)
+ {
+ }
+
+ // DOCTYPE consists of nested sections of the following possible types:
+ // <!-- ... -->, <? ... ?>, "...", '...'
+ // <![...]]>
+ // <!...>
+ // First group can not contain nested groups
+ // Second group can contain nested groups of the same type
+ // Third group can contain all other groups
+ char_t* parse_doctype_primitive(char_t* s)
+ {
+ if (*s == '"' || *s == '\'')
+ {
+ // quoted string
+ char_t ch = *s++;
+ PUGI_IMPL_SCANFOR(*s == ch);
+ if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ s++;
+ }
+ else if (s[0] == '<' && s[1] == '?')
+ {
+ // <? ... ?>
+ s += 2;
+ PUGI_IMPL_SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype
+ if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ s += 2;
+ }
+ else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-')
+ {
+ s += 4;
+ PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype
+ if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ s += 3;
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ return s;
+ }
+
+ char_t* parse_doctype_ignore(char_t* s)
+ {
+ size_t depth = 0;
+
+ assert(s[0] == '<' && s[1] == '!' && s[2] == '[');
+ s += 3;
+
+ while (*s)
+ {
+ if (s[0] == '<' && s[1] == '!' && s[2] == '[')
+ {
+ // nested ignore section
+ s += 3;
+ depth++;
+ }
+ else if (s[0] == ']' && s[1] == ']' && s[2] == '>')
+ {
+ // ignore section end
+ s += 3;
+
+ if (depth == 0)
+ return s;
+
+ depth--;
+ }
+ else s++;
+ }
+
+ PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+ }
+
+ char_t* parse_doctype_group(char_t* s, char_t endch)
+ {
+ size_t depth = 0;
+
+ assert((s[0] == '<' || s[0] == 0) && s[1] == '!');
+ s += 2;
+
+ while (*s)
+ {
+ if (s[0] == '<' && s[1] == '!' && s[2] != '-')
+ {
+ if (s[2] == '[')
+ {
+ // ignore
+ s = parse_doctype_ignore(s);
+ if (!s) return s;
+ }
+ else
+ {
+ // some control group
+ s += 2;
+ depth++;
+ }
+ }
+ else if (s[0] == '<' || s[0] == '"' || s[0] == '\'')
+ {
+ // unknown tag (forbidden), or some primitive group
+ s = parse_doctype_primitive(s);
+ if (!s) return s;
+ }
+ else if (*s == '>')
+ {
+ if (depth == 0)
+ return s;
+
+ depth--;
+ s++;
+ }
+ else s++;
+ }
+
+ if (depth != 0 || endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ return s;
+ }
+
+ char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch)
+ {
+ // parse node contents, starting with exclamation mark
+ ++s;
+
+ if (*s == '-') // '<!-...'
+ {
+ ++s;
+
+ if (*s == '-') // '<!--...'
+ {
+ ++s;
+
+ if (PUGI_IMPL_OPTSET(parse_comments))
+ {
+ PUGI_IMPL_PUSHNODE(node_comment); // Append a new node on the tree.
+ cursor->value = s; // Save the offset.
+ }
+
+ if (PUGI_IMPL_OPTSET(parse_eol) && PUGI_IMPL_OPTSET(parse_comments))
+ {
+ s = strconv_comment(s, endch);
+
+ if (!s) PUGI_IMPL_THROW_ERROR(status_bad_comment, cursor->value);
+ }
+ else
+ {
+ // Scan for terminating '-->'.
+ PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>'));
+ PUGI_IMPL_CHECK_ERROR(status_bad_comment, s);
+
+ if (PUGI_IMPL_OPTSET(parse_comments))
+ *s = 0; // Zero-terminate this segment at the first terminating '-'.
+
+ s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'.
+ }
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_comment, s);
+ }
+ else if (*s == '[')
+ {
+ // '<![CDATA[...'
+ if (*++s=='C' && *++s=='D' && *++s=='A' && *++s=='T' && *++s=='A' && *++s == '[')
+ {
+ ++s;
+
+ if (PUGI_IMPL_OPTSET(parse_cdata))
+ {
+ PUGI_IMPL_PUSHNODE(node_cdata); // Append a new node on the tree.
+ cursor->value = s; // Save the offset.
+
+ if (PUGI_IMPL_OPTSET(parse_eol))
+ {
+ s = strconv_cdata(s, endch);
+
+ if (!s) PUGI_IMPL_THROW_ERROR(status_bad_cdata, cursor->value);
+ }
+ else
+ {
+ // Scan for terminating ']]>'.
+ PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>'));
+ PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s);
+
+ *s++ = 0; // Zero-terminate this segment.
+ }
+ }
+ else // Flagged for discard, but we still have to scan for the terminator.
+ {
+ // Scan for terminating ']]>'.
+ PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>'));
+ PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s);
+
+ ++s;
+ }
+
+ s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'.
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_cdata, s);
+ }
+ else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI_IMPL_ENDSWITH(s[6], 'E'))
+ {
+ s -= 2;
+
+ if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s);
+
+ char_t* mark = s + 9;
+
+ s = parse_doctype_group(s, endch);
+ if (!s) return s;
+
+ assert((*s == 0 && endch == '>') || *s == '>');
+ if (*s) *s++ = 0;
+
+ if (PUGI_IMPL_OPTSET(parse_doctype))
+ {
+ while (PUGI_IMPL_IS_CHARTYPE(*mark, ct_space)) ++mark;
+
+ PUGI_IMPL_PUSHNODE(node_doctype);
+
+ cursor->value = mark;
+ }
+ }
+ else if (*s == 0 && endch == '-') PUGI_IMPL_THROW_ERROR(status_bad_comment, s);
+ else if (*s == 0 && endch == '[') PUGI_IMPL_THROW_ERROR(status_bad_cdata, s);
+ else PUGI_IMPL_THROW_ERROR(status_unrecognized_tag, s);
+
+ return s;
+ }
+
+ char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch)
+ {
+ // load into registers
+ xml_node_struct* cursor = ref_cursor;
+ char_t ch = 0;
+
+ // parse node contents, starting with question mark
+ ++s;
+
+ // read PI target
+ char_t* target = s;
+
+ if (!PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_pi, s);
+
+ PUGI_IMPL_SCANWHILE(PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol));
+ PUGI_IMPL_CHECK_ERROR(status_bad_pi, s);
+
+ // determine node type; stricmp / strcasecmp is not portable
+ bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s;
+
+ if (declaration ? PUGI_IMPL_OPTSET(parse_declaration) : PUGI_IMPL_OPTSET(parse_pi))
+ {
+ if (declaration)
+ {
+ // disallow non top-level declarations
+ if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_pi, s);
+
+ PUGI_IMPL_PUSHNODE(node_declaration);
+ }
+ else
+ {
+ PUGI_IMPL_PUSHNODE(node_pi);
+ }
+
+ cursor->name = target;
+
+ PUGI_IMPL_ENDSEG();
+
+ // parse value/attributes
+ if (ch == '?')
+ {
+ // empty node
+ if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_pi, s);
+ s += (*s == '>');
+
+ PUGI_IMPL_POPNODE();
+ }
+ else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space))
+ {
+ PUGI_IMPL_SKIPWS();
+
+ // scan for tag end
+ char_t* value = s;
+
+ PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>'));
+ PUGI_IMPL_CHECK_ERROR(status_bad_pi, s);
+
+ if (declaration)
+ {
+ // replace ending ? with / so that 'element' terminates properly
+ *s = '/';
+
+ // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES
+ s = value;
+ }
+ else
+ {
+ // store value and step over >
+ cursor->value = value;
+
+ PUGI_IMPL_POPNODE();
+
+ PUGI_IMPL_ENDSEG();
+
+ s += (*s == '>');
+ }
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_pi, s);
+ }
+ else
+ {
+ // scan for tag end
+ PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>'));
+ PUGI_IMPL_CHECK_ERROR(status_bad_pi, s);
+
+ s += (s[1] == '>' ? 2 : 1);
+ }
+
+ // store from registers
+ ref_cursor = cursor;
+
+ return s;
+ }
+
+ char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch)
+ {
+ strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk);
+ strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk);
+
+ char_t ch = 0;
+ xml_node_struct* cursor = root;
+ char_t* mark = s;
+ char_t* merged_pcdata = s;
+
+ while (*s != 0)
+ {
+ if (*s == '<')
+ {
+ ++s;
+
+ LOC_TAG:
+ if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // '<#...'
+ {
+ PUGI_IMPL_PUSHNODE(node_element); // Append a new node to the tree.
+
+ cursor->name = s;
+
+ PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator.
+ PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over.
+
+ if (ch == '>')
+ {
+ // end of tag
+ }
+ else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space))
+ {
+ LOC_ATTRIBUTES:
+ while (true)
+ {
+ PUGI_IMPL_SKIPWS(); // Eat any whitespace.
+
+ if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // <... #...
+ {
+ xml_attribute_struct* a = append_new_attribute(cursor, *alloc); // Make space for this attribute.
+ if (!a) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s);
+
+ a->name = s; // Save the offset.
+
+ PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator.
+ PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over.
+
+ if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space))
+ {
+ PUGI_IMPL_SKIPWS(); // Eat any whitespace.
+
+ ch = *s;
+ ++s;
+ }
+
+ if (ch == '=') // '<... #=...'
+ {
+ PUGI_IMPL_SKIPWS(); // Eat any whitespace.
+
+ if (*s == '"' || *s == '\'') // '<... #="...'
+ {
+ ch = *s; // Save quote char to avoid breaking on "''" -or- '""'.
+ ++s; // Step over the quote.
+ a->value = s; // Save the offset.
+
+ s = strconv_attribute(s, ch);
+
+ if (!s) PUGI_IMPL_THROW_ERROR(status_bad_attribute, a->value);
+
+ // After this line the loop continues from the start;
+ // Whitespaces, / and > are ok, symbols and EOF are wrong,
+ // everything else will be detected
+ if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_attribute, s);
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s);
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s);
+ }
+ else if (*s == '/')
+ {
+ ++s;
+
+ if (*s == '>')
+ {
+ PUGI_IMPL_POPNODE();
+ s++;
+ break;
+ }
+ else if (*s == 0 && endch == '>')
+ {
+ PUGI_IMPL_POPNODE();
+ break;
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s);
+ }
+ else if (*s == '>')
+ {
+ ++s;
+
+ break;
+ }
+ else if (*s == 0 && endch == '>')
+ {
+ break;
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s);
+ }
+
+ // !!!
+ }
+ else if (ch == '/') // '<#.../'
+ {
+ if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_start_element, s);
+
+ PUGI_IMPL_POPNODE(); // Pop.
+
+ s += (*s == '>');
+ }
+ else if (ch == 0)
+ {
+ // we stepped over null terminator, backtrack & handle closing tag
+ --s;
+
+ if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_start_element, s);
+ }
+ else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s);
+ }
+ else if (*s == '/')
+ {
+ ++s;
+
+ mark = s;
+
+ char_t* name = cursor->name;
+ if (!name) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark);
+
+ while (PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol))
+ {
+ if (*s++ != *name++) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark);
+ }
+
+ if (*name)
+ {
+ if (*s == 0 && name[0] == endch && name[1] == 0) PUGI_IMPL_THROW_ERROR(status_bad_end_element, s);
+ else PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark);
+ }
+
+ PUGI_IMPL_POPNODE(); // Pop.
+
+ PUGI_IMPL_SKIPWS();
+
+ if (*s == 0)
+ {
+ if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s);
+ }
+ else
+ {
+ if (*s != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s);
+ ++s;
+ }
+ }
+ else if (*s == '?') // '<?...'
+ {
+ s = parse_question(s, cursor, optmsk, endch);
+ if (!s) return s;
+
+ assert(cursor);
+ if (PUGI_IMPL_NODETYPE(cursor) == node_declaration) goto LOC_ATTRIBUTES;
+ }
+ else if (*s == '!') // '<!...'
+ {
+ s = parse_exclamation(s, cursor, optmsk, endch);
+ if (!s) return s;
+ }
+ else if (*s == 0 && endch == '?') PUGI_IMPL_THROW_ERROR(status_bad_pi, s);
+ else PUGI_IMPL_THROW_ERROR(status_unrecognized_tag, s);
+ }
+ else
+ {
+ mark = s; // Save this offset while searching for a terminator.
+
+ PUGI_IMPL_SKIPWS(); // Eat whitespace if no genuine PCDATA here.
+
+ if (*s == '<' || !*s)
+ {
+ // We skipped some whitespace characters because otherwise we would take the tag branch instead of PCDATA one
+ assert(mark != s);
+
+ if (!PUGI_IMPL_OPTSET(parse_ws_pcdata | parse_ws_pcdata_single) || PUGI_IMPL_OPTSET(parse_trim_pcdata))
+ {
+ continue;
+ }
+ else if (PUGI_IMPL_OPTSET(parse_ws_pcdata_single))
+ {
+ if (s[0] != '<' || s[1] != '/' || cursor->first_child) continue;
+ }
+ }
+
+ if (!PUGI_IMPL_OPTSET(parse_trim_pcdata))
+ s = mark;
+
+ if (cursor->parent || PUGI_IMPL_OPTSET(parse_fragment))
+ {
+ char_t* parsed_pcdata = s;
+
+ s = strconv_pcdata(s);
+
+ if (PUGI_IMPL_OPTSET(parse_embed_pcdata) && cursor->parent && !cursor->first_child && !cursor->value)
+ {
+ cursor->value = parsed_pcdata; // Save the offset.
+ }
+ else if (PUGI_IMPL_OPTSET(parse_merge_pcdata) && cursor->first_child && PUGI_IMPL_NODETYPE(cursor->first_child->prev_sibling_c) == node_pcdata)
+ {
+ assert(merged_pcdata >= cursor->first_child->prev_sibling_c->value);
+
+ // Catch up to the end of last parsed value; only needed for the first fragment.
+ merged_pcdata += strlength(merged_pcdata);
+
+ size_t length = strlength(parsed_pcdata);
+
+ // Must use memmove instead of memcpy as this move may overlap
+ memmove(merged_pcdata, parsed_pcdata, (length + 1) * sizeof(char_t));
+ merged_pcdata += length;
+ }
+ else
+ {
+ xml_node_struct* prev_cursor = cursor;
+ PUGI_IMPL_PUSHNODE(node_pcdata); // Append a new node on the tree.
+
+ cursor->value = parsed_pcdata; // Save the offset.
+ merged_pcdata = parsed_pcdata; // Used for parse_merge_pcdata above, cheaper to save unconditionally
+
+ cursor = prev_cursor; // Pop since this is a standalone.
+ }
+
+ if (!*s) break;
+ }
+ else
+ {
+ PUGI_IMPL_SCANFOR(*s == '<'); // '...<'
+ if (!*s) break;
+
+ ++s;
+ }
+
+ // We're after '<'
+ goto LOC_TAG;
+ }
+ }
+
+ // check that last tag is closed
+ if (cursor != root) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, s);
+
+ return s;
+ }
+
+ #ifdef PUGIXML_WCHAR_MODE
+ static char_t* parse_skip_bom(char_t* s)
+ {
+ unsigned int bom = 0xfeff;
+ return (s[0] == static_cast<wchar_t>(bom)) ? s + 1 : s;
+ }
+ #else
+ static char_t* parse_skip_bom(char_t* s)
+ {
+ return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s;
+ }
+ #endif
+
+ static bool has_element_node_siblings(xml_node_struct* node)
+ {
+ while (node)
+ {
+ if (PUGI_IMPL_NODETYPE(node) == node_element) return true;
+
+ node = node->next_sibling;
+ }
+
+ return false;
+ }
+
+ static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk)
+ {
+ // early-out for empty documents
+ if (length == 0)
+ return make_parse_result(PUGI_IMPL_OPTSET(parse_fragment) ? status_ok : status_no_document_element);
+
+ // get last child of the root before parsing
+ xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : NULL;
+
+ // create parser on stack
+ xml_parser parser(static_cast<xml_allocator*>(xmldoc));
+
+ // save last character and make buffer zero-terminated (speeds up parsing)
+ char_t endch = buffer[length - 1];
+ buffer[length - 1] = 0;
+
+ // skip BOM to make sure it does not end up as part of parse output
+ char_t* buffer_data = parse_skip_bom(buffer);
+
+ // perform actual parsing
+ parser.parse_tree(buffer_data, root, optmsk, endch);
+
+ xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0);
+ assert(result.offset >= 0 && static_cast<size_t>(result.offset) <= length);
+
+ if (result)
+ {
+ // since we removed last character, we have to handle the only possible false positive (stray <)
+ if (endch == '<')
+ return make_parse_result(status_unrecognized_tag, length - 1);
+
+ // check if there are any element nodes parsed
+ xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child + 0;
+
+ if (!PUGI_IMPL_OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed))
+ return make_parse_result(status_no_document_element, length - 1);
+ }
+ else
+ {
+ // roll back offset if it occurs on a null terminator in the source buffer
+ if (result.offset > 0 && static_cast<size_t>(result.offset) == length - 1 && endch == 0)
+ result.offset--;
+ }
+
+ return result;
+ }
+ };
+
+ // Output facilities
+ PUGI_IMPL_FN xml_encoding get_write_native_encoding()
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ return get_wchar_encoding();
+ #else
+ return encoding_utf8;
+ #endif
+ }
+
+ PUGI_IMPL_FN xml_encoding get_write_encoding(xml_encoding encoding)
+ {
+ // replace wchar encoding with utf implementation
+ if (encoding == encoding_wchar) return get_wchar_encoding();
+
+ // replace utf16 encoding with utf16 with specific endianness
+ if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ // replace utf32 encoding with utf32 with specific endianness
+ if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ // only do autodetection if no explicit encoding is requested
+ if (encoding != encoding_auto) return encoding;
+
+ // assume utf8 encoding
+ return encoding_utf8;
+ }
+
+ template <typename D, typename T> PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T)
+ {
+ PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type));
+
+ typename T::value_type end = D::process(reinterpret_cast<const typename D::type*>(data), length, dest, T());
+
+ return static_cast<size_t>(end - dest) * sizeof(*dest);
+ }
+
+ template <typename D, typename T> PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap)
+ {
+ PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type));
+
+ typename T::value_type end = D::process(reinterpret_cast<const typename D::type*>(data), length, dest, T());
+
+ if (opt_swap)
+ {
+ for (typename T::value_type i = dest; i != end; ++i)
+ *i = endian_swap(*i);
+ }
+
+ return static_cast<size_t>(end - dest) * sizeof(*dest);
+ }
+
+#ifdef PUGIXML_WCHAR_MODE
+ PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length)
+ {
+ if (length < 1) return 0;
+
+ // discard last character if it's the lead of a surrogate pair
+ return (sizeof(wchar_t) == 2 && static_cast<unsigned int>(static_cast<uint16_t>(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length;
+ }
+
+ PUGI_IMPL_FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding)
+ {
+ // only endian-swapping is required
+ if (need_endian_swap_utf(encoding, get_wchar_encoding()))
+ {
+ convert_wchar_endian_swap(r_char, data, length);
+
+ return length * sizeof(char_t);
+ }
+
+ // convert to utf8
+ if (encoding == encoding_utf8)
+ return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), utf8_writer());
+
+ // convert to utf16
+ if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ return convert_buffer_output_generic(r_u16, data, length, wchar_decoder(), utf16_writer(), native_encoding != encoding);
+ }
+
+ // convert to utf32
+ if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ return convert_buffer_output_generic(r_u32, data, length, wchar_decoder(), utf32_writer(), native_encoding != encoding);
+ }
+
+ // convert to latin1
+ if (encoding == encoding_latin1)
+ return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), latin1_writer());
+
+ assert(false && "Invalid encoding"); // unreachable
+ return 0;
+ }
+#else
+ PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length)
+ {
+ if (length < 5) return 0;
+
+ for (size_t i = 1; i <= 4; ++i)
+ {
+ uint8_t ch = static_cast<uint8_t>(data[length - i]);
+
+ // either a standalone character or a leading one
+ if ((ch & 0xc0) != 0x80) return length - i;
+ }
+
+ // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk
+ return length;
+ }
+
+ PUGI_IMPL_FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding)
+ {
+ if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+ return convert_buffer_output_generic(r_u16, data, length, utf8_decoder(), utf16_writer(), native_encoding != encoding);
+ }
+
+ if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+ {
+ xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+ return convert_buffer_output_generic(r_u32, data, length, utf8_decoder(), utf32_writer(), native_encoding != encoding);
+ }
+
+ if (encoding == encoding_latin1)
+ return convert_buffer_output_generic(r_u8, data, length, utf8_decoder(), latin1_writer());
+
+ assert(false && "Invalid encoding"); // unreachable
+ return 0;
+ }
+#endif
+
+ class xml_buffered_writer
+ {
+ xml_buffered_writer(const xml_buffered_writer&);
+ xml_buffered_writer& operator=(const xml_buffered_writer&);
+
+ public:
+ xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding))
+ {
+ PUGI_IMPL_STATIC_ASSERT(bufcapacity >= 8);
+ }
+
+ size_t flush()
+ {
+ flush(buffer, bufsize);
+ bufsize = 0;
+ return 0;
+ }
+
+ void flush(const char_t* data, size_t size)
+ {
+ if (size == 0) return;
+
+ // fast path, just write data
+ if (encoding == get_write_native_encoding())
+ writer.write(data, size * sizeof(char_t));
+ else
+ {
+ // convert chunk
+ size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding);
+ assert(result <= sizeof(scratch));
+
+ // write data
+ writer.write(scratch.data_u8, result);
+ }
+ }
+
+ void write_direct(const char_t* data, size_t length)
+ {
+ // flush the remaining buffer contents
+ flush();
+
+ // handle large chunks
+ if (length > bufcapacity)
+ {
+ if (encoding == get_write_native_encoding())
+ {
+ // fast path, can just write data chunk
+ writer.write(data, length * sizeof(char_t));
+ return;
+ }
+
+ // need to convert in suitable chunks
+ while (length > bufcapacity)
+ {
+ // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer
+ // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary)
+ size_t chunk_size = get_valid_length(data, bufcapacity);
+ assert(chunk_size);
+
+ // convert chunk and write
+ flush(data, chunk_size);
+
+ // iterate
+ data += chunk_size;
+ length -= chunk_size;
+ }
+
+ // small tail is copied below
+ bufsize = 0;
+ }
+
+ memcpy(buffer + bufsize, data, length * sizeof(char_t));
+ bufsize += length;
+ }
+
+ void write_buffer(const char_t* data, size_t length)
+ {
+ size_t offset = bufsize;
+
+ if (offset + length <= bufcapacity)
+ {
+ memcpy(buffer + offset, data, length * sizeof(char_t));
+ bufsize = offset + length;
+ }
+ else
+ {
+ write_direct(data, length);
+ }
+ }
+
+ void write_string(const char_t* data)
+ {
+ // write the part of the string that fits in the buffer
+ size_t offset = bufsize;
+
+ while (*data && offset < bufcapacity)
+ buffer[offset++] = *data++;
+
+ // write the rest
+ if (offset < bufcapacity)
+ {
+ bufsize = offset;
+ }
+ else
+ {
+ // backtrack a bit if we have split the codepoint
+ size_t length = offset - bufsize;
+ size_t extra = length - get_valid_length(data - length, length);
+
+ bufsize = offset - extra;
+
+ write_direct(data - extra, strlength(data) + extra);
+ }
+ }
+
+ void write(char_t d0)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 1) offset = flush();
+
+ buffer[offset + 0] = d0;
+ bufsize = offset + 1;
+ }
+
+ void write(char_t d0, char_t d1)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 2) offset = flush();
+
+ buffer[offset + 0] = d0;
+ buffer[offset + 1] = d1;
+ bufsize = offset + 2;
+ }
+
+ void write(char_t d0, char_t d1, char_t d2)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 3) offset = flush();
+
+ buffer[offset + 0] = d0;
+ buffer[offset + 1] = d1;
+ buffer[offset + 2] = d2;
+ bufsize = offset + 3;
+ }
+
+ void write(char_t d0, char_t d1, char_t d2, char_t d3)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 4) offset = flush();
+
+ buffer[offset + 0] = d0;
+ buffer[offset + 1] = d1;
+ buffer[offset + 2] = d2;
+ buffer[offset + 3] = d3;
+ bufsize = offset + 4;
+ }
+
+ void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 5) offset = flush();
+
+ buffer[offset + 0] = d0;
+ buffer[offset + 1] = d1;
+ buffer[offset + 2] = d2;
+ buffer[offset + 3] = d3;
+ buffer[offset + 4] = d4;
+ bufsize = offset + 5;
+ }
+
+ void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5)
+ {
+ size_t offset = bufsize;
+ if (offset > bufcapacity - 6) offset = flush();
+
+ buffer[offset + 0] = d0;
+ buffer[offset + 1] = d1;
+ buffer[offset + 2] = d2;
+ buffer[offset + 3] = d3;
+ buffer[offset + 4] = d4;
+ buffer[offset + 5] = d5;
+ bufsize = offset + 6;
+ }
+
+ // utf8 maximum expansion: x4 (-> utf32)
+ // utf16 maximum expansion: x2 (-> utf32)
+ // utf32 maximum expansion: x1
+ enum
+ {
+ bufcapacitybytes =
+ #ifdef PUGIXML_MEMORY_OUTPUT_STACK
+ PUGIXML_MEMORY_OUTPUT_STACK
+ #else
+ 10240
+ #endif
+ ,
+ bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4)
+ };
+
+ char_t buffer[bufcapacity];
+
+ union
+ {
+ uint8_t data_u8[4 * bufcapacity];
+ uint16_t data_u16[2 * bufcapacity];
+ uint32_t data_u32[bufcapacity];
+ char_t data_char[bufcapacity];
+ } scratch;
+
+ xml_writer& writer;
+ size_t bufsize;
+ xml_encoding encoding;
+ };
+
+ PUGI_IMPL_FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags)
+ {
+ while (*s)
+ {
+ const char_t* prev = s;
+
+ // While *s is a usual symbol
+ PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPEX(ss, type));
+
+ writer.write_buffer(prev, static_cast<size_t>(s - prev));
+
+ switch (*s)
+ {
+ case 0: break;
+ case '&':
+ writer.write('&', 'a', 'm', 'p', ';');
+ ++s;
+ break;
+ case '<':
+ writer.write('&', 'l', 't', ';');
+ ++s;
+ break;
+ case '>':
+ writer.write('&', 'g', 't', ';');
+ ++s;
+ break;
+ case '"':
+ if (flags & format_attribute_single_quote)
+ writer.write('"');
+ else
+ writer.write('&', 'q', 'u', 'o', 't', ';');
+ ++s;
+ break;
+ case '\'':
+ if (flags & format_attribute_single_quote)
+ writer.write('&', 'a', 'p', 'o', 's', ';');
+ else
+ writer.write('\'');
+ ++s;
+ break;
+ default: // s is not a usual symbol
+ {
+ unsigned int ch = static_cast<unsigned int>(*s++);
+ assert(ch < 32);
+
+ if (!(flags & format_skip_control_chars))
+ writer.write('&', '#', static_cast<char_t>((ch / 10) + '0'), static_cast<char_t>((ch % 10) + '0'), ';');
+ }
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags)
+ {
+ if (flags & format_no_escapes)
+ writer.write_string(s);
+ else
+ text_output_escaped(writer, s, type, flags);
+ }
+
+ PUGI_IMPL_FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s)
+ {
+ do
+ {
+ writer.write('<', '!', '[', 'C', 'D');
+ writer.write('A', 'T', 'A', '[');
+
+ const char_t* prev = s;
+
+ // look for ]]> sequence - we can't output it as is since it terminates CDATA
+ while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s;
+
+ // skip ]] if we stopped at ]]>, > will go to the next CDATA section
+ if (*s) s += 2;
+
+ writer.write_buffer(prev, static_cast<size_t>(s - prev));
+
+ writer.write(']', ']', '>');
+ }
+ while (*s);
+ }
+
+ PUGI_IMPL_FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth)
+ {
+ switch (indent_length)
+ {
+ case 1:
+ {
+ for (unsigned int i = 0; i < depth; ++i)
+ writer.write(indent[0]);
+ break;
+ }
+
+ case 2:
+ {
+ for (unsigned int i = 0; i < depth; ++i)
+ writer.write(indent[0], indent[1]);
+ break;
+ }
+
+ case 3:
+ {
+ for (unsigned int i = 0; i < depth; ++i)
+ writer.write(indent[0], indent[1], indent[2]);
+ break;
+ }
+
+ case 4:
+ {
+ for (unsigned int i = 0; i < depth; ++i)
+ writer.write(indent[0], indent[1], indent[2], indent[3]);
+ break;
+ }
+
+ default:
+ {
+ for (unsigned int i = 0; i < depth; ++i)
+ writer.write_buffer(indent, indent_length);
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void node_output_comment(xml_buffered_writer& writer, const char_t* s)
+ {
+ writer.write('<', '!', '-', '-');
+
+ while (*s)
+ {
+ const char_t* prev = s;
+
+ // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body
+ while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s;
+
+ writer.write_buffer(prev, static_cast<size_t>(s - prev));
+
+ if (*s)
+ {
+ assert(*s == '-');
+
+ writer.write('-', ' ');
+ ++s;
+ }
+ }
+
+ writer.write('-', '-', '>');
+ }
+
+ PUGI_IMPL_FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s)
+ {
+ while (*s)
+ {
+ const char_t* prev = s;
+
+ // look for ?> sequence - we can't output it since ?> terminates PI
+ while (*s && !(s[0] == '?' && s[1] == '>')) ++s;
+
+ writer.write_buffer(prev, static_cast<size_t>(s - prev));
+
+ if (*s)
+ {
+ assert(s[0] == '?' && s[1] == '>');
+
+ writer.write('?', ' ', '>');
+ s += 2;
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth)
+ {
+ const char_t* default_name = PUGIXML_TEXT(":anonymous");
+ const char_t enquotation_char = (flags & format_attribute_single_quote) ? '\'' : '"';
+
+ for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute)
+ {
+ if ((flags & (format_indent_attributes | format_raw)) == format_indent_attributes)
+ {
+ writer.write('\n');
+
+ text_output_indent(writer, indent, indent_length, depth + 1);
+ }
+ else
+ {
+ writer.write(' ');
+ }
+
+ writer.write_string(a->name ? a->name + 0 : default_name);
+ writer.write('=', enquotation_char);
+
+ if (a->value)
+ text_output(writer, a->value, ctx_special_attr, flags);
+
+ writer.write(enquotation_char);
+ }
+ }
+
+ PUGI_IMPL_FN bool node_output_empty(xml_node_struct* node)
+ {
+ xml_node_struct* child = node->first_child;
+
+ return !child || (!child->next_sibling && PUGI_IMPL_NODETYPE(child) == node_pcdata && (!child->value || !child->value[0]));
+ }
+
+ PUGI_IMPL_FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth)
+ {
+ const char_t* default_name = PUGIXML_TEXT(":anonymous");
+ const char_t* name = node->name ? node->name + 0 : default_name;
+
+ writer.write('<');
+ writer.write_string(name);
+
+ if (node->first_attribute)
+ node_output_attributes(writer, node, indent, indent_length, flags, depth);
+
+ // element nodes can have value if parse_embed_pcdata was used
+ if (!node->value)
+ {
+ if (!node->first_child || node_output_empty(node))
+ {
+ if (flags & format_no_empty_element_tags)
+ {
+ writer.write('>', '<', '/');
+ writer.write_string(name);
+ writer.write('>');
+
+ return false;
+ }
+ else
+ {
+ if ((flags & format_raw) == 0)
+ writer.write(' ');
+
+ writer.write('/', '>');
+
+ return false;
+ }
+ }
+ else
+ {
+ writer.write('>');
+
+ return true;
+ }
+ }
+ else
+ {
+ writer.write('>');
+
+ text_output(writer, node->value, ctx_special_pcdata, flags);
+
+ if (!node->first_child)
+ {
+ writer.write('<', '/');
+ writer.write_string(name);
+ writer.write('>');
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node)
+ {
+ const char_t* default_name = PUGIXML_TEXT(":anonymous");
+ const char_t* name = node->name ? node->name + 0 : default_name;
+
+ writer.write('<', '/');
+ writer.write_string(name);
+ writer.write('>');
+ }
+
+ PUGI_IMPL_FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags)
+ {
+ const char_t* default_name = PUGIXML_TEXT(":anonymous");
+
+ switch (PUGI_IMPL_NODETYPE(node))
+ {
+ case node_pcdata:
+ text_output(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""), ctx_special_pcdata, flags);
+ break;
+
+ case node_cdata:
+ text_output_cdata(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""));
+ break;
+
+ case node_comment:
+ node_output_comment(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""));
+ break;
+
+ case node_pi:
+ writer.write('<', '?');
+ writer.write_string(node->name ? node->name + 0 : default_name);
+
+ if (node->value)
+ {
+ writer.write(' ');
+ node_output_pi_value(writer, node->value);
+ }
+
+ writer.write('?', '>');
+ break;
+
+ case node_declaration:
+ writer.write('<', '?');
+ writer.write_string(node->name ? node->name + 0 : default_name);
+ node_output_attributes(writer, node, PUGIXML_TEXT(""), 0, flags | format_raw, 0);
+ writer.write('?', '>');
+ break;
+
+ case node_doctype:
+ writer.write('<', '!', 'D', 'O', 'C');
+ writer.write('T', 'Y', 'P', 'E');
+
+ if (node->value)
+ {
+ writer.write(' ');
+ writer.write_string(node->value);
+ }
+
+ writer.write('>');
+ break;
+
+ default:
+ assert(false && "Invalid node type"); // unreachable
+ }
+ }
+
+ enum indent_flags_t
+ {
+ indent_newline = 1,
+ indent_indent = 2
+ };
+
+ PUGI_IMPL_FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth)
+ {
+ size_t indent_length = ((flags & (format_indent | format_indent_attributes)) && (flags & format_raw) == 0) ? strlength(indent) : 0;
+ unsigned int indent_flags = indent_indent;
+
+ xml_node_struct* node = root;
+
+ do
+ {
+ assert(node);
+
+ // begin writing current node
+ if (PUGI_IMPL_NODETYPE(node) == node_pcdata || PUGI_IMPL_NODETYPE(node) == node_cdata)
+ {
+ node_output_simple(writer, node, flags);
+
+ indent_flags = 0;
+ }
+ else
+ {
+ if ((indent_flags & indent_newline) && (flags & format_raw) == 0)
+ writer.write('\n');
+
+ if ((indent_flags & indent_indent) && indent_length)
+ text_output_indent(writer, indent, indent_length, depth);
+
+ if (PUGI_IMPL_NODETYPE(node) == node_element)
+ {
+ indent_flags = indent_newline | indent_indent;
+
+ if (node_output_start(writer, node, indent, indent_length, flags, depth))
+ {
+ // element nodes can have value if parse_embed_pcdata was used
+ if (node->value)
+ indent_flags = 0;
+
+ node = node->first_child;
+ depth++;
+ continue;
+ }
+ }
+ else if (PUGI_IMPL_NODETYPE(node) == node_document)
+ {
+ indent_flags = indent_indent;
+
+ if (node->first_child)
+ {
+ node = node->first_child;
+ continue;
+ }
+ }
+ else
+ {
+ node_output_simple(writer, node, flags);
+
+ indent_flags = indent_newline | indent_indent;
+ }
+ }
+
+ // continue to the next node
+ while (node != root)
+ {
+ if (node->next_sibling)
+ {
+ node = node->next_sibling;
+ break;
+ }
+
+ node = node->parent;
+
+ // write closing node
+ if (PUGI_IMPL_NODETYPE(node) == node_element)
+ {
+ depth--;
+
+ if ((indent_flags & indent_newline) && (flags & format_raw) == 0)
+ writer.write('\n');
+
+ if ((indent_flags & indent_indent) && indent_length)
+ text_output_indent(writer, indent, indent_length, depth);
+
+ node_output_end(writer, node);
+
+ indent_flags = indent_newline | indent_indent;
+ }
+ }
+ }
+ while (node != root);
+
+ if ((indent_flags & indent_newline) && (flags & format_raw) == 0)
+ writer.write('\n');
+ }
+
+ PUGI_IMPL_FN bool has_declaration(xml_node_struct* node)
+ {
+ for (xml_node_struct* child = node->first_child; child; child = child->next_sibling)
+ {
+ xml_node_type type = PUGI_IMPL_NODETYPE(child);
+
+ if (type == node_declaration) return true;
+ if (type == node_element) return false;
+ }
+
+ return false;
+ }
+
+ PUGI_IMPL_FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node)
+ {
+ for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute)
+ if (a == attr)
+ return true;
+
+ return false;
+ }
+
+ PUGI_IMPL_FN bool allow_insert_attribute(xml_node_type parent)
+ {
+ return parent == node_element || parent == node_declaration;
+ }
+
+ PUGI_IMPL_FN bool allow_insert_child(xml_node_type parent, xml_node_type child)
+ {
+ if (parent != node_document && parent != node_element) return false;
+ if (child == node_document || child == node_null) return false;
+ if (parent != node_document && (child == node_declaration || child == node_doctype)) return false;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool allow_move(xml_node parent, xml_node child)
+ {
+ // check that child can be a child of parent
+ if (!allow_insert_child(parent.type(), child.type()))
+ return false;
+
+ // check that node is not moved between documents
+ if (parent.root() != child.root())
+ return false;
+
+ // check that new parent is not in the child subtree
+ xml_node cur = parent;
+
+ while (cur)
+ {
+ if (cur == child)
+ return false;
+
+ cur = cur.parent();
+ }
+
+ return true;
+ }
+
+ template <typename String, typename Header>
+ PUGI_IMPL_FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc)
+ {
+ assert(!dest && (header & header_mask) == 0); // copies are performed into fresh nodes
+
+ if (source)
+ {
+ if (alloc && (source_header & header_mask) == 0)
+ {
+ dest = source;
+
+ // since strcpy_insitu can reuse document buffer memory we need to mark both source and dest as shared
+ header |= xml_memory_page_contents_shared_mask;
+ source_header |= xml_memory_page_contents_shared_mask;
+ }
+ else
+ {
+ // if strcpy_insitu fails (out of memory) we just leave the destination name/value empty
+ (void)strcpy_insitu(dest, header, header_mask, source, strlength(source));
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc)
+ {
+ node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc);
+ node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc);
+
+ for (xml_attribute_struct* sa = sn->first_attribute; sa; sa = sa->next_attribute)
+ {
+ xml_attribute_struct* da = append_new_attribute(dn, get_allocator(dn));
+
+ if (da)
+ {
+ node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc);
+ node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc);
+ }
+ }
+ }
+
+ PUGI_IMPL_FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn)
+ {
+ xml_allocator& alloc = get_allocator(dn);
+ xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : NULL;
+
+ node_copy_contents(dn, sn, shared_alloc);
+
+ xml_node_struct* dit = dn;
+ xml_node_struct* sit = sn->first_child;
+
+ while (sit && sit != sn)
+ {
+ // loop invariant: dit is inside the subtree rooted at dn
+ assert(dit);
+
+ // when a tree is copied into one of the descendants, we need to skip that subtree to avoid an infinite loop
+ if (sit != dn)
+ {
+ xml_node_struct* copy = append_new_node(dit, alloc, PUGI_IMPL_NODETYPE(sit));
+
+ if (copy)
+ {
+ node_copy_contents(copy, sit, shared_alloc);
+
+ if (sit->first_child)
+ {
+ dit = copy;
+ sit = sit->first_child;
+ continue;
+ }
+ }
+ }
+
+ // continue to the next node
+ do
+ {
+ if (sit->next_sibling)
+ {
+ sit = sit->next_sibling;
+ break;
+ }
+
+ sit = sit->parent;
+ dit = dit->parent;
+
+ // loop invariant: dit is inside the subtree rooted at dn while sit is inside sn
+ assert(sit == sn || dit);
+ }
+ while (sit != sn);
+ }
+
+ assert(!sit || dit == dn->parent);
+ }
+
+ PUGI_IMPL_FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa)
+ {
+ xml_allocator& alloc = get_allocator(da);
+ xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : NULL;
+
+ node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc);
+ node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc);
+ }
+
+ inline bool is_text_node(xml_node_struct* node)
+ {
+ xml_node_type type = PUGI_IMPL_NODETYPE(node);
+
+ return type == node_pcdata || type == node_cdata;
+ }
+
+ // get value with conversion functions
+ template <typename U> PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW U string_to_integer(const char_t* value, U minv, U maxv)
+ {
+ U result = 0;
+ const char_t* s = value;
+
+ while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space))
+ s++;
+
+ bool negative = (*s == '-');
+
+ s += (*s == '+' || *s == '-');
+
+ bool overflow = false;
+
+ if (s[0] == '0' && (s[1] | ' ') == 'x')
+ {
+ s += 2;
+
+ // since overflow detection relies on length of the sequence skip leading zeros
+ while (*s == '0')
+ s++;
+
+ const char_t* start = s;
+
+ for (;;)
+ {
+ if (static_cast<unsigned>(*s - '0') < 10)
+ result = result * 16 + (*s - '0');
+ else if (static_cast<unsigned>((*s | ' ') - 'a') < 6)
+ result = result * 16 + ((*s | ' ') - 'a' + 10);
+ else
+ break;
+
+ s++;
+ }
+
+ size_t digits = static_cast<size_t>(s - start);
+
+ overflow = digits > sizeof(U) * 2;
+ }
+ else
+ {
+ // since overflow detection relies on length of the sequence skip leading zeros
+ while (*s == '0')
+ s++;
+
+ const char_t* start = s;
+
+ for (;;)
+ {
+ if (static_cast<unsigned>(*s - '0') < 10)
+ result = result * 10 + (*s - '0');
+ else
+ break;
+
+ s++;
+ }
+
+ size_t digits = static_cast<size_t>(s - start);
+
+ PUGI_IMPL_STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2);
+
+ const size_t max_digits10 = sizeof(U) == 8 ? 20 : sizeof(U) == 4 ? 10 : 5;
+ const char_t max_lead = sizeof(U) == 8 ? '1' : sizeof(U) == 4 ? '4' : '6';
+ const size_t high_bit = sizeof(U) * 8 - 1;
+
+ overflow = digits >= max_digits10 && !(digits == max_digits10 && (*start < max_lead || (*start == max_lead && result >> high_bit)));
+ }
+
+ if (negative)
+ {
+ // Workaround for crayc++ CC-3059: Expected no overflow in routine.
+ #ifdef _CRAYC
+ return (overflow || result > ~minv + 1) ? minv : ~result + 1;
+ #else
+ return (overflow || result > 0 - minv) ? minv : 0 - result;
+ #endif
+ }
+ else
+ return (overflow || result > maxv) ? maxv : result;
+ }
+
+ PUGI_IMPL_FN int get_value_int(const char_t* value)
+ {
+ return string_to_integer<unsigned int>(value, static_cast<unsigned int>(INT_MIN), INT_MAX);
+ }
+
+ PUGI_IMPL_FN unsigned int get_value_uint(const char_t* value)
+ {
+ return string_to_integer<unsigned int>(value, 0, UINT_MAX);
+ }
+
+ PUGI_IMPL_FN double get_value_double(const char_t* value)
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ return wcstod(value, NULL);
+ #elif defined(PUGIXML_CHARCONV_FLOAT)
+ while (PUGI_IMPL_IS_CHARTYPE(*value, ct_space))
+ value++;
+ if (*value == '+')
+ value++;
+ // return code is intentionally ignored to let libc++/MSVC STL correctly handle underflow/overflow
+ double result = 0.0;
+ std::from_chars(value, value + strlen(value), result);
+ return result;
+ #else
+ return strtod(value, NULL);
+ #endif
+ }
+
+ PUGI_IMPL_FN float get_value_float(const char_t* value)
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ return static_cast<float>(wcstod(value, NULL));
+ #elif defined(PUGIXML_CHARCONV_FLOAT)
+ while (PUGI_IMPL_IS_CHARTYPE(*value, ct_space))
+ value++;
+ if (*value == '+')
+ value++;
+ // return code is intentionally ignored to let libc++/MSVC STL correctly handle underflow/overflow
+ float result = 0.0f;
+ std::from_chars(value, value + strlen(value), result);
+ return result;
+ #else
+ return static_cast<float>(strtod(value, NULL));
+ #endif
+ }
+
+ PUGI_IMPL_FN bool get_value_bool(const char_t* value)
+ {
+ // only look at first char
+ char_t first = *value;
+
+ // 1*, t* (true), T* (True), y* (yes), Y* (YES)
+ return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y');
+ }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN long long get_value_llong(const char_t* value)
+ {
+ return string_to_integer<unsigned long long>(value, static_cast<unsigned long long>(LLONG_MIN), LLONG_MAX);
+ }
+
+ PUGI_IMPL_FN unsigned long long get_value_ullong(const char_t* value)
+ {
+ return string_to_integer<unsigned long long>(value, 0, ULLONG_MAX);
+ }
+#endif
+
+ template <typename U> PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative)
+ {
+ char_t* result = end - 1;
+ U rest = negative ? 0 - value : value;
+
+ do
+ {
+ *result-- = static_cast<char_t>('0' + (rest % 10));
+ rest /= 10;
+ }
+ while (rest);
+
+ assert(result >= begin);
+ (void)begin;
+
+ *result = '-';
+
+ return result + !negative;
+ }
+
+ // set value with conversion functions
+ template <typename String, typename Header>
+ PUGI_IMPL_FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf)
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ char_t wbuf[128];
+ assert(strlen(buf) < sizeof(wbuf) / sizeof(wbuf[0]));
+
+ size_t offset = 0;
+ for (; buf[offset]; ++offset) wbuf[offset] = buf[offset];
+
+ return strcpy_insitu(dest, header, header_mask, wbuf, offset);
+ #else
+ return strcpy_insitu(dest, header, header_mask, buf, strlen(buf));
+ #endif
+ }
+
+ template <typename U, typename String, typename Header>
+ PUGI_IMPL_FN bool set_value_integer(String& dest, Header& header, uintptr_t header_mask, U value, bool negative)
+ {
+ char_t buf[64];
+ char_t* end = buf + sizeof(buf) / sizeof(buf[0]);
+ char_t* begin = integer_to_string(buf, end, value, negative);
+
+ return strcpy_insitu(dest, header, header_mask, begin, end - begin);
+ }
+
+ template <typename String, typename Header>
+ PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value, int precision)
+ {
+ char buf[128];
+ #ifdef PUGIXML_CHARCONV_FLOAT
+ std::to_chars_result result = std::to_chars(buf, buf + sizeof(buf) - 1, value, std::chars_format::general, precision);
+ if (result.ec != std::errc()) return false;
+ *result.ptr = '\0';
+ #else
+ PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, double(value));
+ #endif
+ return set_value_ascii(dest, header, header_mask, buf);
+ }
+
+ template <typename String, typename Header>
+ PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value, int precision)
+ {
+ char buf[128];
+ #ifdef PUGIXML_CHARCONV_FLOAT
+ std::to_chars_result result = std::to_chars(buf, buf + sizeof(buf) - 1, value, std::chars_format::general, precision);
+ if (result.ec != std::errc()) return false;
+ *result.ptr = '\0';
+ #else
+ PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, value);
+ #endif
+
+ return set_value_ascii(dest, header, header_mask, buf);
+ }
+
+ template <typename String, typename Header>
+ PUGI_IMPL_FN bool set_value_bool(String& dest, Header& header, uintptr_t header_mask, bool value)
+ {
+ return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5);
+ }
+
+ PUGI_IMPL_FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer)
+ {
+ // check input buffer
+ if (!contents && size) return make_parse_result(status_io_error);
+
+ // get actual encoding
+ xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size);
+
+ // if convert_buffer below throws bad_alloc, we still need to deallocate contents if we own it
+ auto_deleter<void> contents_guard(own ? contents : NULL, xml_memory::deallocate);
+
+ // early-out for empty documents to avoid buffer allocation overhead
+ if (size == 0)
+ {
+ xml_parse_result result = make_parse_result((options & parse_fragment) ? status_ok : status_no_document_element);
+ result.encoding = buffer_encoding;
+ return result;
+ }
+
+ // get private buffer
+ char_t* buffer = NULL;
+ size_t length = 0;
+
+ if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory);
+
+ // after this we either deallocate contents (below) or hold on to it via doc->buffer, so we don't need to guard it
+ contents_guard.release();
+
+ // delete original buffer if we performed a conversion
+ if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents);
+
+ // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
+ if (own || buffer != contents) *out_buffer = buffer;
+
+ // store buffer for offset_debug
+ doc->buffer = buffer;
+
+ // parse
+ xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options);
+
+ // remember encoding
+ res.encoding = buffer_encoding;
+
+ return res;
+ }
+
+ template <typename T> PUGI_IMPL_FN xml_parse_status convert_file_size(T length, size_t& out_result)
+ {
+ // check for I/O errors
+ if (length < 0) return status_io_error;
+
+ // check for overflow
+ size_t result = static_cast<size_t>(length);
+
+ if (static_cast<T>(result) != length) return status_out_of_memory;
+
+ out_result = result;
+ return status_ok;
+ }
+
+ // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick
+ PUGI_IMPL_FN xml_parse_status get_file_size(FILE* file, size_t& out_result)
+ {
+ #if defined(__linux__) || defined(__APPLE__)
+ // this simultaneously retrieves the file size and file mode (to guard against loading non-files)
+ struct stat st;
+ if (fstat(fileno(file), &st) != 0) return status_io_error;
+
+ // anything that's not a regular file doesn't have a coherent length
+ if (!S_ISREG(st.st_mode)) return status_io_error;
+
+ xml_parse_status status = convert_file_size(st.st_size, out_result);
+ #elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400
+ // there are 64-bit versions of fseek/ftell, let's use them
+ _fseeki64(file, 0, SEEK_END);
+ __int64 length = _ftelli64(file);
+ _fseeki64(file, 0, SEEK_SET);
+
+ xml_parse_status status = convert_file_size(length, out_result);
+ #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))
+ // there are 64-bit versions of fseek/ftell, let's use them
+ fseeko64(file, 0, SEEK_END);
+ off64_t length = ftello64(file);
+ fseeko64(file, 0, SEEK_SET);
+
+ xml_parse_status status = convert_file_size(length, out_result);
+ #else
+ // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway.
+ fseek(file, 0, SEEK_END);
+ long length = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ xml_parse_status status = convert_file_size(length, out_result);
+ #endif
+
+ return status;
+ }
+
+ // This function assumes that buffer has extra sizeof(char_t) writable bytes after size
+ PUGI_IMPL_FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding)
+ {
+ // We only need to zero-terminate if encoding conversion does not do it for us
+ #ifdef PUGIXML_WCHAR_MODE
+ xml_encoding wchar_encoding = get_wchar_encoding();
+
+ if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding))
+ {
+ size_t length = size / sizeof(char_t);
+
+ static_cast<char_t*>(buffer)[length] = 0;
+ return (length + 1) * sizeof(char_t);
+ }
+ #else
+ if (encoding == encoding_utf8)
+ {
+ static_cast<char*>(buffer)[size] = 0;
+ return size + 1;
+ }
+ #endif
+
+ return size;
+ }
+
+ PUGI_IMPL_FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer)
+ {
+ if (!file) return make_parse_result(status_file_not_found);
+
+ // get file size (can result in I/O errors)
+ size_t size = 0;
+ xml_parse_status size_status = get_file_size(file, size);
+ if (size_status != status_ok) return make_parse_result(size_status);
+
+ size_t max_suffix_size = sizeof(char_t);
+ if (static_cast<size_t>(-1) - max_suffix_size < size) return make_parse_result(status_out_of_memory);
+
+ // allocate buffer for the whole file
+ char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size));
+ if (!contents) return make_parse_result(status_out_of_memory);
+
+ // read file in memory
+ size_t read_size = fread(contents, 1, size, file);
+
+ if (read_size != size)
+ {
+ xml_memory::deallocate(contents);
+ return make_parse_result(status_io_error);
+ }
+
+ xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size);
+
+ return load_buffer_impl(doc, doc, contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding, true, true, out_buffer);
+ }
+
+ PUGI_IMPL_FN void close_file(FILE* file)
+ {
+ fclose(file);
+ }
+
+#ifndef PUGIXML_NO_STL
+ template <typename T> struct xml_stream_chunk
+ {
+ static xml_stream_chunk* create()
+ {
+ void* memory = xml_memory::allocate(sizeof(xml_stream_chunk));
+ if (!memory) return NULL;
+
+ return new (memory) xml_stream_chunk();
+ }
+
+ static void destroy(xml_stream_chunk* chunk)
+ {
+ // free chunk chain
+ while (chunk)
+ {
+ xml_stream_chunk* next_ = chunk->next;
+
+ xml_memory::deallocate(chunk);
+
+ chunk = next_;
+ }
+ }
+
+ xml_stream_chunk(): next(NULL), size(0)
+ {
+ }
+
+ xml_stream_chunk* next;
+ size_t size;
+
+ T data[xml_memory_page_size / sizeof(T)];
+ };
+
+ template <typename T> PUGI_IMPL_FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
+ {
+ auto_deleter<xml_stream_chunk<T> > chunks(NULL, xml_stream_chunk<T>::destroy);
+
+ // read file to a chunk list
+ size_t total = 0;
+ xml_stream_chunk<T>* last = NULL;
+
+ while (!stream.eof())
+ {
+ // allocate new chunk
+ xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create();
+ if (!chunk) return status_out_of_memory;
+
+ // append chunk to list
+ if (last) last = last->next = chunk;
+ else chunks.data = last = chunk;
+
+ // read data to chunk
+ stream.read(chunk->data, static_cast<std::streamsize>(sizeof(chunk->data) / sizeof(T)));
+ chunk->size = static_cast<size_t>(stream.gcount()) * sizeof(T);
+
+ // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors
+ if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error;
+
+ // guard against huge files (chunk size is small enough to make this overflow check work)
+ if (total + chunk->size < total) return status_out_of_memory;
+ total += chunk->size;
+ }
+
+ size_t max_suffix_size = sizeof(char_t);
+ if (static_cast<size_t>(-1) - max_suffix_size < total) return status_out_of_memory;
+
+ // copy chunk list to a contiguous buffer
+ char* buffer = static_cast<char*>(xml_memory::allocate(total + max_suffix_size));
+ if (!buffer) return status_out_of_memory;
+
+ char* write = buffer;
+
+ for (xml_stream_chunk<T>* chunk = chunks.data; chunk; chunk = chunk->next)
+ {
+ assert(write + chunk->size <= buffer + total);
+ memcpy(write, chunk->data, chunk->size);
+ write += chunk->size;
+ }
+
+ assert(write == buffer + total);
+
+ // return buffer
+ *out_buffer = buffer;
+ *out_size = total;
+
+ return status_ok;
+ }
+
+ template <typename T> PUGI_IMPL_FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
+ {
+ // get length of remaining data in stream
+ typename std::basic_istream<T>::pos_type pos = stream.tellg();
+ stream.seekg(0, std::ios::end);
+ std::streamoff length = stream.tellg() - pos;
+ stream.seekg(pos);
+
+ if (stream.fail() || pos < 0) return status_io_error;
+
+ // guard against huge files
+ size_t read_length = static_cast<size_t>(length);
+
+ if (static_cast<std::streamsize>(read_length) != length || length < 0) return status_out_of_memory;
+
+ size_t max_suffix_size = sizeof(char_t);
+ if ((static_cast<size_t>(-1) - max_suffix_size) / sizeof(T) < read_length) return status_out_of_memory;
+
+ // read stream data into memory (guard against stream exceptions with buffer holder)
+ auto_deleter<void> buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate);
+ if (!buffer.data) return status_out_of_memory;
+
+ stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length));
+
+ // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors
+ if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error;
+
+ // return buffer
+ size_t actual_length = static_cast<size_t>(stream.gcount());
+ assert(actual_length <= read_length);
+
+ *out_buffer = buffer.release();
+ *out_size = actual_length * sizeof(T);
+
+ return status_ok;
+ }
+
+ template <typename T> PUGI_IMPL_FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream<T>& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer)
+ {
+ void* buffer = NULL;
+ size_t size = 0;
+ xml_parse_status status = status_ok;
+
+ // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits)
+ if (stream.fail()) return make_parse_result(status_io_error);
+
+ // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory)
+ if (stream.tellg() < 0)
+ {
+ stream.clear(); // clear error flags that could be set by a failing tellg
+ status = load_stream_data_noseek(stream, &buffer, &size);
+ }
+ else
+ status = load_stream_data_seek(stream, &buffer, &size);
+
+ if (status != status_ok) return make_parse_result(status);
+
+ xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size);
+
+ return load_buffer_impl(doc, doc, buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding, true, true, out_buffer);
+ }
+#endif
+
+#if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)))
+ PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+ {
+#ifdef PUGIXML_NO_STL
+ // ensure these symbols are consistently referenced to avoid 'unreferenced function' warnings
+ // note that generally these functions are used in STL builds, but PUGIXML_NO_STL leaves the only usage in convert_path_heap
+ (void)&as_utf8_begin;
+ (void)&as_utf8_end;
+ (void)&strlength_wide;
+#endif
+
+#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400
+ FILE* file = NULL;
+ return _wfopen_s(&file, path, mode) == 0 ? file : NULL;
+#else
+ return _wfopen(path, mode);
+#endif
+ }
+#else
+ PUGI_IMPL_FN char* convert_path_heap(const wchar_t* str)
+ {
+ assert(str);
+
+ // first pass: get length in utf8 characters
+ size_t length = strlength_wide(str);
+ size_t size = as_utf8_begin(str, length);
+
+ // allocate resulting string
+ char* result = static_cast<char*>(xml_memory::allocate(size + 1));
+ if (!result) return NULL;
+
+ // second pass: convert to utf8
+ as_utf8_end(result, size, str, length);
+
+ // zero-terminate
+ result[size] = 0;
+
+ return result;
+ }
+
+ PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+ {
+ // there is no standard function to open wide paths, so our best bet is to try utf8 path
+ char* path_utf8 = convert_path_heap(path);
+ if (!path_utf8) return NULL;
+
+ // convert mode to ASCII (we mirror _wfopen interface)
+ char mode_ascii[4] = {0};
+ for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast<char>(mode[i]);
+
+ // try to open the utf8 path
+ FILE* result = fopen(path_utf8, mode_ascii);
+
+ // free dummy buffer
+ xml_memory::deallocate(path_utf8);
+
+ return result;
+ }
+#endif
+
+ PUGI_IMPL_FN FILE* open_file(const char* path, const char* mode)
+ {
+#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400
+ FILE* file = NULL;
+ return fopen_s(&file, path, mode) == 0 ? file : NULL;
+#else
+ return fopen(path, mode);
+#endif
+ }
+
+ PUGI_IMPL_FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding)
+ {
+ if (!file) return false;
+
+ xml_writer_file writer(file);
+ doc.save(writer, indent, flags, encoding);
+
+ return fflush(file) == 0 && ferror(file) == 0;
+ }
+
+ struct name_null_sentry
+ {
+ xml_node_struct* node;
+ char_t* name;
+
+ name_null_sentry(xml_node_struct* node_): node(node_), name(node_->name)
+ {
+ node->name = NULL;
+ }
+
+ ~name_null_sentry()
+ {
+ node->name = name;
+ }
+ };
+PUGI_IMPL_NS_END
+
+namespace pugi
+{
+ PUGI_IMPL_FN xml_writer::~xml_writer()
+ {
+ }
+
+ PUGI_IMPL_FN xml_writer_file::xml_writer_file(void* file_): file(file_)
+ {
+ }
+
+ PUGI_IMPL_FN void xml_writer_file::write(const void* data, size_t size)
+ {
+ size_t result = fwrite(data, 1, size, static_cast<FILE*>(file));
+ (void)!result; // unfortunately we can't do proper error handling here
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream<char>& stream): narrow_stream(&stream), wide_stream(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream<wchar_t>& stream): narrow_stream(NULL), wide_stream(&stream)
+ {
+ }
+
+ PUGI_IMPL_FN void xml_writer_stream::write(const void* data, size_t size)
+ {
+ if (narrow_stream)
+ {
+ assert(!wide_stream);
+ narrow_stream->write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
+ }
+ else
+ {
+ assert(wide_stream);
+ assert(size % sizeof(wchar_t) == 0);
+
+ wide_stream->write(reinterpret_cast<const wchar_t*>(data), static_cast<std::streamsize>(size / sizeof(wchar_t)));
+ }
+ }
+#endif
+
+ PUGI_IMPL_FN xml_tree_walker::xml_tree_walker(): _depth(0)
+ {
+ }
+
+ PUGI_IMPL_FN xml_tree_walker::~xml_tree_walker()
+ {
+ }
+
+ PUGI_IMPL_FN int xml_tree_walker::depth() const
+ {
+ return _depth;
+ }
+
+ PUGI_IMPL_FN bool xml_tree_walker::begin(xml_node&)
+ {
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xml_tree_walker::end(xml_node&)
+ {
+ return true;
+ }
+
+ PUGI_IMPL_FN xml_attribute::xml_attribute(): _attr(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr)
+ {
+ }
+
+ PUGI_IMPL_FN static void unspecified_bool_xml_attribute(xml_attribute***)
+ {
+ }
+
+ PUGI_IMPL_FN xml_attribute::operator xml_attribute::unspecified_bool_type() const
+ {
+ return _attr ? unspecified_bool_xml_attribute : NULL;
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator!() const
+ {
+ return !_attr;
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator==(const xml_attribute& r) const
+ {
+ return (_attr == r._attr);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator!=(const xml_attribute& r) const
+ {
+ return (_attr != r._attr);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator<(const xml_attribute& r) const
+ {
+ return (_attr < r._attr);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator>(const xml_attribute& r) const
+ {
+ return (_attr > r._attr);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator<=(const xml_attribute& r) const
+ {
+ return (_attr <= r._attr);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::operator>=(const xml_attribute& r) const
+ {
+ return (_attr >= r._attr);
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_attribute::next_attribute() const
+ {
+ if (!_attr) return xml_attribute();
+ return xml_attribute(_attr->next_attribute);
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_attribute::previous_attribute() const
+ {
+ if (!_attr) return xml_attribute();
+ xml_attribute_struct* prev = _attr->prev_attribute_c;
+ return prev->next_attribute ? xml_attribute(prev) : xml_attribute();
+ }
+
+ PUGI_IMPL_FN const char_t* xml_attribute::as_string(const char_t* def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? value : def;
+ }
+
+ PUGI_IMPL_FN int xml_attribute::as_int(int def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_int(value) : def;
+ }
+
+ PUGI_IMPL_FN unsigned int xml_attribute::as_uint(unsigned int def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_uint(value) : def;
+ }
+
+ PUGI_IMPL_FN double xml_attribute::as_double(double def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_double(value) : def;
+ }
+
+ PUGI_IMPL_FN float xml_attribute::as_float(float def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_float(value) : def;
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::as_bool(bool def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_bool(value) : def;
+ }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN long long xml_attribute::as_llong(long long def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_llong(value) : def;
+ }
+
+ PUGI_IMPL_FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const
+ {
+ if (!_attr) return def;
+ const char_t* value = _attr->value;
+ return value ? impl::get_value_ullong(value) : def;
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_attribute::empty() const
+ {
+ return !_attr;
+ }
+
+ PUGI_IMPL_FN const char_t* xml_attribute::name() const
+ {
+ if (!_attr) return PUGIXML_TEXT("");
+ const char_t* name = _attr->name;
+ return name ? name : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const char_t* xml_attribute::value() const
+ {
+ if (!_attr) return PUGIXML_TEXT("");
+ const char_t* value = _attr->value;
+ return value ? value : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN size_t xml_attribute::hash_value() const
+ {
+ return reinterpret_cast<uintptr_t>(_attr) / sizeof(xml_attribute_struct);
+ }
+
+ PUGI_IMPL_FN xml_attribute_struct* xml_attribute::internal_object() const
+ {
+ return _attr;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(const char_t* rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(int rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned int rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(double rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(float rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(bool rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(string_view_t rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+#endif
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long long rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long long rhs)
+ {
+ set_value(rhs);
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs));
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs, size_t size)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, size);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_attribute::set_name(string_view_t rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.size());
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs));
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs, size_t size)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, size);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_attribute::set_value(string_view_t rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size());
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(int rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned int>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(unsigned int rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned int>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(long rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned long>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned long>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(double rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(double rhs, int precision)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(float rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(float rhs, int precision)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(bool rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_bool(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+ }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN bool xml_attribute::set_value(long long rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned long long>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0);
+ }
+
+ PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long long rhs)
+ {
+ if (!_attr) return false;
+
+ return impl::set_value_integer<unsigned long long>(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false);
+ }
+#endif
+
+#ifdef __BORLANDC__
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs)
+ {
+ return (bool)lhs && rhs;
+ }
+
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs)
+ {
+ return (bool)lhs || rhs;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node::xml_node(): _root(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN xml_node::xml_node(xml_node_struct* p): _root(p)
+ {
+ }
+
+ PUGI_IMPL_FN static void unspecified_bool_xml_node(xml_node***)
+ {
+ }
+
+ PUGI_IMPL_FN xml_node::operator xml_node::unspecified_bool_type() const
+ {
+ return _root ? unspecified_bool_xml_node : NULL;
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator!() const
+ {
+ return !_root;
+ }
+
+ PUGI_IMPL_FN xml_node::iterator xml_node::begin() const
+ {
+ return iterator(_root ? _root->first_child + 0 : NULL, _root);
+ }
+
+ PUGI_IMPL_FN xml_node::iterator xml_node::end() const
+ {
+ return iterator(NULL, _root);
+ }
+
+ PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_begin() const
+ {
+ return attribute_iterator(_root ? _root->first_attribute + 0 : NULL, _root);
+ }
+
+ PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_end() const
+ {
+ return attribute_iterator(NULL, _root);
+ }
+
+ PUGI_IMPL_FN xml_object_range<xml_node_iterator> xml_node::children() const
+ {
+ return xml_object_range<xml_node_iterator>(begin(), end());
+ }
+
+ PUGI_IMPL_FN xml_object_range<xml_named_node_iterator> xml_node::children(const char_t* name_) const
+ {
+ return xml_object_range<xml_named_node_iterator>(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(NULL, _root, name_));
+ }
+
+ PUGI_IMPL_FN xml_object_range<xml_attribute_iterator> xml_node::attributes() const
+ {
+ return xml_object_range<xml_attribute_iterator>(attributes_begin(), attributes_end());
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator==(const xml_node& r) const
+ {
+ return (_root == r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator!=(const xml_node& r) const
+ {
+ return (_root != r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator<(const xml_node& r) const
+ {
+ return (_root < r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator>(const xml_node& r) const
+ {
+ return (_root > r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator<=(const xml_node& r) const
+ {
+ return (_root <= r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::operator>=(const xml_node& r) const
+ {
+ return (_root >= r._root);
+ }
+
+ PUGI_IMPL_FN bool xml_node::empty() const
+ {
+ return !_root;
+ }
+
+ PUGI_IMPL_FN const char_t* xml_node::name() const
+ {
+ if (!_root) return PUGIXML_TEXT("");
+ const char_t* name = _root->name;
+ return name ? name : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN xml_node_type xml_node::type() const
+ {
+ return _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+ }
+
+ PUGI_IMPL_FN const char_t* xml_node::value() const
+ {
+ if (!_root) return PUGIXML_TEXT("");
+ const char_t* value = _root->value;
+ return value ? value : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::child(const char_t* name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_) const
+ {
+ if (!_root) return xml_attribute();
+
+ for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ return xml_attribute(i);
+ }
+
+ return xml_attribute();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::next_sibling(const char_t* name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::next_sibling() const
+ {
+ return _root ? xml_node(_root->next_sibling) : xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::previous_sibling(const char_t* name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_node xml_node::child(string_view_t name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::stringview_equal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::attribute(string_view_t name_) const
+ {
+ if (!_root) return xml_attribute();
+
+ for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::stringview_equal(name_, iname))
+ return xml_attribute(i);
+ }
+
+ return xml_attribute();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::next_sibling(string_view_t name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::stringview_equal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::previous_sibling(string_view_t name_) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::stringview_equal(name_, iname))
+ return xml_node(i);
+ }
+
+ return xml_node();
+ }
+#endif
+
+ PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const
+ {
+ xml_attribute_struct* hint = hint_._attr;
+
+ // if hint is not an attribute of node, behavior is not defined
+ assert(!hint || (_root && impl::is_attribute_of(hint, _root)));
+
+ if (!_root) return xml_attribute();
+
+ // optimistically search from hint up until the end
+ for (xml_attribute_struct* i = hint; i; i = i->next_attribute)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ {
+ // update hint to maximize efficiency of searching for consecutive attributes
+ hint_._attr = i->next_attribute;
+
+ return xml_attribute(i);
+ }
+ }
+
+ // wrap around and search from the first attribute until the hint
+ // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails
+ for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute)
+ {
+ const char_t* jname = j->name;
+ if (jname && impl::strequal(name_, jname))
+ {
+ // update hint to maximize efficiency of searching for consecutive attributes
+ hint_._attr = j->next_attribute;
+
+ return xml_attribute(j);
+ }
+ }
+
+ return xml_attribute();
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_attribute xml_node::attribute(string_view_t name_, xml_attribute& hint_) const
+ {
+ xml_attribute_struct* hint = hint_._attr;
+
+ // if hint is not an attribute of node, behavior is not defined
+ assert(!hint || (_root && impl::is_attribute_of(hint, _root)));
+
+ if (!_root) return xml_attribute();
+
+ // optimistically search from hint up until the end
+ for (xml_attribute_struct* i = hint; i; i = i->next_attribute)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::stringview_equal(name_, iname))
+ {
+ // update hint to maximize efficiency of searching for consecutive attributes
+ hint_._attr = i->next_attribute;
+
+ return xml_attribute(i);
+ }
+ }
+
+ // wrap around and search from the first attribute until the hint
+ // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails
+ for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute)
+ {
+ const char_t* jname = j->name;
+ if (jname && impl::stringview_equal(name_, jname))
+ {
+ // update hint to maximize efficiency of searching for consecutive attributes
+ hint_._attr = j->next_attribute;
+
+ return xml_attribute(j);
+ }
+ }
+
+ return xml_attribute();
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node xml_node::previous_sibling() const
+ {
+ if (!_root) return xml_node();
+ xml_node_struct* prev = _root->prev_sibling_c;
+ return prev->next_sibling ? xml_node(prev) : xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::parent() const
+ {
+ return _root ? xml_node(_root->parent) : xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::root() const
+ {
+ return _root ? xml_node(&impl::get_document(_root)) : xml_node();
+ }
+
+ PUGI_IMPL_FN xml_text xml_node::text() const
+ {
+ return xml_text(_root);
+ }
+
+ PUGI_IMPL_FN const char_t* xml_node::child_value() const
+ {
+ if (!_root) return PUGIXML_TEXT("");
+
+ // element nodes can have value if parse_embed_pcdata was used
+ if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value)
+ return _root->value;
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ {
+ const char_t* ivalue = i->value;
+ if (impl::is_text_node(i) && ivalue)
+ return ivalue;
+ }
+
+ return PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const char_t* xml_node::child_value(const char_t* name_) const
+ {
+ return child(name_).child_value();
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::first_attribute() const
+ {
+ if (!_root) return xml_attribute();
+ return xml_attribute(_root->first_attribute);
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::last_attribute() const
+ {
+ if (!_root) return xml_attribute();
+ xml_attribute_struct* first = _root->first_attribute;
+ return first ? xml_attribute(first->prev_attribute_c) : xml_attribute();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::first_child() const
+ {
+ if (!_root) return xml_node();
+ return xml_node(_root->first_child);
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::last_child() const
+ {
+ if (!_root) return xml_node();
+ xml_node_struct* first = _root->first_child;
+ return first ? xml_node(first->prev_sibling_c) : xml_node();
+ }
+
+ PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_element && type_ != node_pi && type_ != node_declaration)
+ return false;
+
+ return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs));
+ }
+
+ PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs, size_t size)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_element && type_ != node_pi && type_ != node_declaration)
+ return false;
+
+ return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, size);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_node::set_name(string_view_t rhs)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_element && type_ != node_pi && type_ != node_declaration)
+ return false;
+
+ return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.size());
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype)
+ return false;
+
+ return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs));
+ }
+
+ PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs, size_t size)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype)
+ return false;
+
+ return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, size);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_node::set_value(string_view_t rhs)
+ {
+ xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
+
+ if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype)
+ return false;
+
+ return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size());
+ }
+#endif
+
+ PUGI_IMPL_FN xml_attribute xml_node::append_attribute(const char_t* name_)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::append_attribute(a._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::prepend_attribute(const char_t* name_)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::prepend_attribute(a._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_after(a._attr, attr._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_before(a._attr, attr._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_attribute xml_node::append_attribute(string_view_t name_)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::append_attribute(a._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::prepend_attribute(string_view_t name_)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::prepend_attribute(a._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_after(string_view_t name_, const xml_attribute& attr)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_after(a._attr, attr._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_before(string_view_t name_, const xml_attribute& attr)
+ {
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_before(a._attr, attr._attr, _root);
+
+ a.set_name(name_);
+
+ return a;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_attribute xml_node::ensure_attribute(const char_t* name_)
+ {
+ xml_attribute result = attribute(name_);
+
+ return result ? result : append_attribute(name_);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_attribute xml_node::ensure_attribute(string_view_t name_)
+ {
+ xml_attribute result = attribute(name_);
+
+ return result ? result : append_attribute(name_);
+ }
+#endif
+
+ PUGI_IMPL_FN xml_attribute xml_node::append_copy(const xml_attribute& proto)
+ {
+ if (!proto) return xml_attribute();
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::append_attribute(a._attr, _root);
+ impl::node_copy_attribute(a._attr, proto._attr);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto)
+ {
+ if (!proto) return xml_attribute();
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::prepend_attribute(a._attr, _root);
+ impl::node_copy_attribute(a._attr, proto._attr);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr)
+ {
+ if (!proto) return xml_attribute();
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_after(a._attr, attr._attr, _root);
+ impl::node_copy_attribute(a._attr, proto._attr);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr)
+ {
+ if (!proto) return xml_attribute();
+ if (!impl::allow_insert_attribute(type())) return xml_attribute();
+ if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_attribute();
+
+ xml_attribute a(impl::allocate_attribute(alloc));
+ if (!a) return xml_attribute();
+
+ impl::insert_attribute_before(a._attr, attr._attr, _root);
+ impl::node_copy_attribute(a._attr, proto._attr);
+
+ return a;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::append_child(xml_node_type type_)
+ {
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::append_node(n._root, _root);
+
+ if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::prepend_child(xml_node_type type_)
+ {
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::prepend_node(n._root, _root);
+
+ if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node)
+ {
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::insert_node_before(n._root, node._root);
+
+ if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node)
+ {
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::insert_node_after(n._root, node._root);
+
+ if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::append_child(const char_t* name_)
+ {
+ xml_node result = append_child(node_element);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::prepend_child(const char_t* name_)
+ {
+ xml_node result = prepend_child(node_element);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node)
+ {
+ xml_node result = insert_child_after(node_element, node);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node)
+ {
+ xml_node result = insert_child_before(node_element, node);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_node xml_node::append_child(string_view_t name_)
+ {
+ xml_node result = append_child(node_element);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::prepend_child(string_view_t name_)
+ {
+ xml_node result = prepend_child(node_element);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_after(string_view_t name_, const xml_node& node)
+ {
+ xml_node result = insert_child_after(node_element, node);
+
+ result.set_name(name_);
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_child_before(string_view_t name_, const xml_node& node)
+ {
+ xml_node result = insert_child_before(node_element, node);
+
+ result.set_name(name_);
+
+ return result;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node xml_node::ensure_child(const char_t* name_)
+ {
+ xml_node result = child(name_);
+
+ return result ? result : append_child(name_);
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_node xml_node::ensure_child(string_view_t name_)
+ {
+ xml_node result = child(name_);
+
+ return result ? result : append_child(name_);
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node xml_node::append_copy(const xml_node& proto)
+ {
+ xml_node_type type_ = proto.type();
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::append_node(n._root, _root);
+ impl::node_copy_tree(n._root, proto._root);
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::prepend_copy(const xml_node& proto)
+ {
+ xml_node_type type_ = proto.type();
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::prepend_node(n._root, _root);
+ impl::node_copy_tree(n._root, proto._root);
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node)
+ {
+ xml_node_type type_ = proto.type();
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::insert_node_after(n._root, node._root);
+ impl::node_copy_tree(n._root, proto._root);
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node)
+ {
+ xml_node_type type_ = proto.type();
+ if (!impl::allow_insert_child(type(), type_)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ xml_node n(impl::allocate_node(alloc, type_));
+ if (!n) return xml_node();
+
+ impl::insert_node_before(n._root, node._root);
+ impl::node_copy_tree(n._root, proto._root);
+
+ return n;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::append_move(const xml_node& moved)
+ {
+ if (!impl::allow_move(*this, moved)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers
+ impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask;
+
+ impl::remove_node(moved._root);
+ impl::append_node(moved._root, _root);
+
+ return moved;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::prepend_move(const xml_node& moved)
+ {
+ if (!impl::allow_move(*this, moved)) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers
+ impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask;
+
+ impl::remove_node(moved._root);
+ impl::prepend_node(moved._root, _root);
+
+ return moved;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node)
+ {
+ if (!impl::allow_move(*this, moved)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+ if (moved._root == node._root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers
+ impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask;
+
+ impl::remove_node(moved._root);
+ impl::insert_node_after(moved._root, node._root);
+
+ return moved;
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node)
+ {
+ if (!impl::allow_move(*this, moved)) return xml_node();
+ if (!node._root || node._root->parent != _root) return xml_node();
+ if (moved._root == node._root) return xml_node();
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return xml_node();
+
+ // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers
+ impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask;
+
+ impl::remove_node(moved._root);
+ impl::insert_node_before(moved._root, node._root);
+
+ return moved;
+ }
+
+ PUGI_IMPL_FN bool xml_node::remove_attribute(const char_t* name_)
+ {
+ return remove_attribute(attribute(name_));
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_node::remove_attribute(string_view_t name_)
+ {
+ return remove_attribute(attribute(name_));
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_node::remove_attribute(const xml_attribute& a)
+ {
+ if (!_root || !a._attr) return false;
+ if (!impl::is_attribute_of(a._attr, _root)) return false;
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return false;
+
+ impl::remove_attribute(a._attr, _root);
+ impl::destroy_attribute(a._attr, alloc);
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xml_node::remove_attributes()
+ {
+ if (!_root) return false;
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return false;
+
+ for (xml_attribute_struct* attr = _root->first_attribute; attr; )
+ {
+ xml_attribute_struct* next = attr->next_attribute;
+
+ impl::destroy_attribute(attr, alloc);
+
+ attr = next;
+ }
+
+ _root->first_attribute = NULL;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xml_node::remove_child(const char_t* name_)
+ {
+ return remove_child(child(name_));
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_node::remove_child(string_view_t name_)
+ {
+ return remove_child(child(name_));
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_node::remove_child(const xml_node& n)
+ {
+ if (!_root || !n._root || n._root->parent != _root) return false;
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return false;
+
+ impl::remove_node(n._root);
+ impl::destroy_tree(n._root, alloc);
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xml_node::remove_children()
+ {
+ if (!_root) return false;
+
+ impl::xml_allocator& alloc = impl::get_allocator(_root);
+ if (!alloc.reserve()) return false;
+
+ for (xml_node_struct* cur = _root->first_child; cur; )
+ {
+ xml_node_struct* next = cur->next_sibling;
+
+ impl::destroy_tree(cur, alloc);
+
+ cur = next;
+ }
+
+ _root->first_child = NULL;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding)
+ {
+ // append_buffer is only valid for elements/documents
+ if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root);
+
+ // append buffer can not merge PCDATA into existing PCDATA nodes
+ if ((options & parse_merge_pcdata) != 0 && last_child().type() == node_pcdata) return impl::make_parse_result(status_append_invalid_root);
+
+ // get document node
+ impl::xml_document_struct* doc = &impl::get_document(_root);
+
+ // disable document_buffer_order optimization since in a document with multiple buffers comparing buffer pointers does not make sense
+ doc->header |= impl::xml_memory_page_contents_shared_mask;
+
+ // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later)
+ impl::xml_memory_page* page = NULL;
+ impl::xml_extra_buffer* extra = static_cast<impl::xml_extra_buffer*>(doc->allocate_memory(sizeof(impl::xml_extra_buffer) + sizeof(void*), page));
+ (void)page;
+
+ if (!extra) return impl::make_parse_result(status_out_of_memory);
+
+ #ifdef PUGIXML_COMPACT
+ // align the memory block to a pointer boundary; this is required for compact mode where memory allocations are only 4b aligned
+ // note that this requires up to sizeof(void*)-1 additional memory, which the allocation above takes into account
+ extra = reinterpret_cast<impl::xml_extra_buffer*>((reinterpret_cast<uintptr_t>(extra) + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1));
+ #endif
+
+ // add extra buffer to the list
+ extra->buffer = NULL;
+ extra->next = doc->extra_buffers;
+ doc->extra_buffers = extra;
+
+ // name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level
+ impl::name_null_sentry sentry(_root);
+
+ return impl::load_buffer_impl(doc, _root, const_cast<void*>(contents), size, options, encoding, false, false, &extra->buffer);
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ {
+ const char_t* iname = i->name;
+ if (iname && impl::strequal(name_, iname))
+ {
+ for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute)
+ {
+ const char_t* aname = a->name;
+ if (aname && impl::strequal(attr_name, aname))
+ {
+ const char_t* avalue = a->value;
+ if (impl::strequal(attr_value, avalue ? avalue : PUGIXML_TEXT("")))
+ return xml_node(i);
+ }
+ }
+ }
+ }
+
+ return xml_node();
+ }
+
+ PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute)
+ {
+ const char_t* aname = a->name;
+ if (aname && impl::strequal(attr_name, aname))
+ {
+ const char_t* avalue = a->value;
+ if (impl::strequal(attr_value, avalue ? avalue : PUGIXML_TEXT("")))
+ return xml_node(i);
+ }
+ }
+
+ return xml_node();
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN string_t xml_node::path(char_t delimiter) const
+ {
+ if (!_root) return string_t();
+
+ size_t offset = 0;
+
+ for (xml_node_struct* i = _root; i; i = i->parent)
+ {
+ const char_t* iname = i->name;
+ offset += (i != _root);
+ offset += iname ? impl::strlength(iname) : 0;
+ }
+
+ string_t result;
+ result.resize(offset);
+
+ for (xml_node_struct* j = _root; j; j = j->parent)
+ {
+ if (j != _root)
+ result[--offset] = delimiter;
+
+ const char_t* jname = j->name;
+ if (jname)
+ {
+ size_t length = impl::strlength(jname);
+
+ offset -= length;
+ memcpy(&result[offset], jname, length * sizeof(char_t));
+ }
+ }
+
+ assert(offset == 0);
+
+ return result;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const
+ {
+ assert(delimiter != 0);
+
+ xml_node context = path_[0] == delimiter ? root() : *this;
+
+ if (!context._root) return xml_node();
+
+ const char_t* path_segment = path_;
+
+ while (*path_segment == delimiter) ++path_segment;
+
+ const char_t* path_segment_end = path_segment;
+
+ while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end;
+
+ if (path_segment == path_segment_end) return context;
+
+ const char_t* next_segment = path_segment_end;
+
+ while (*next_segment == delimiter) ++next_segment;
+
+ if (*path_segment == '.' && path_segment + 1 == path_segment_end)
+ return context.first_element_by_path(next_segment, delimiter);
+ else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end)
+ return context.parent().first_element_by_path(next_segment, delimiter);
+ else
+ {
+ for (xml_node_struct* j = context._root->first_child; j; j = j->next_sibling)
+ {
+ const char_t* jname = j->name;
+ if (jname && impl::strequalrange(jname, path_segment, static_cast<size_t>(path_segment_end - path_segment)))
+ {
+ xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter);
+
+ if (subsearch) return subsearch;
+ }
+ }
+
+ return xml_node();
+ }
+ }
+
+ PUGI_IMPL_FN bool xml_node::traverse(xml_tree_walker& walker)
+ {
+ walker._depth = -1;
+
+ xml_node arg_begin(_root);
+ if (!walker.begin(arg_begin)) return false;
+
+ xml_node_struct* cur = _root ? _root->first_child + 0 : NULL;
+
+ if (cur)
+ {
+ ++walker._depth;
+
+ do
+ {
+ xml_node arg_for_each(cur);
+ if (!walker.for_each(arg_for_each))
+ return false;
+
+ if (cur->first_child)
+ {
+ ++walker._depth;
+ cur = cur->first_child;
+ }
+ else if (cur->next_sibling)
+ cur = cur->next_sibling;
+ else
+ {
+ while (!cur->next_sibling && cur != _root && cur->parent)
+ {
+ --walker._depth;
+ cur = cur->parent;
+ }
+
+ if (cur != _root)
+ cur = cur->next_sibling;
+ }
+ }
+ while (cur && cur != _root);
+ }
+
+ assert(walker._depth == -1);
+
+ xml_node arg_end(_root);
+ return walker.end(arg_end);
+ }
+
+ PUGI_IMPL_FN size_t xml_node::hash_value() const
+ {
+ return reinterpret_cast<uintptr_t>(_root) / sizeof(xml_node_struct);
+ }
+
+ PUGI_IMPL_FN xml_node_struct* xml_node::internal_object() const
+ {
+ return _root;
+ }
+
+ PUGI_IMPL_FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const
+ {
+ if (!_root) return;
+
+ impl::xml_buffered_writer buffered_writer(writer, encoding);
+
+ impl::node_output(buffered_writer, _root, indent, flags, depth);
+
+ buffered_writer.flush();
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN void xml_node::print(std::basic_ostream<char>& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const
+ {
+ xml_writer_stream writer(stream);
+
+ print(writer, indent, flags, encoding, depth);
+ }
+
+ PUGI_IMPL_FN void xml_node::print(std::basic_ostream<wchar_t>& stream, const char_t* indent, unsigned int flags, unsigned int depth) const
+ {
+ xml_writer_stream writer(stream);
+
+ print(writer, indent, flags, encoding_wchar, depth);
+ }
+#endif
+
+ PUGI_IMPL_FN ptrdiff_t xml_node::offset_debug() const
+ {
+ if (!_root) return -1;
+
+ impl::xml_document_struct& doc = impl::get_document(_root);
+
+ // we can determine the offset reliably only if there is exactly once parse buffer
+ if (!doc.buffer || doc.extra_buffers) return -1;
+
+ switch (type())
+ {
+ case node_document:
+ return 0;
+
+ case node_element:
+ case node_declaration:
+ case node_pi:
+ return _root->name && (_root->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0 ? _root->name - doc.buffer : -1;
+
+ case node_pcdata:
+ case node_cdata:
+ case node_comment:
+ case node_doctype:
+ return _root->value && (_root->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0 ? _root->value - doc.buffer : -1;
+
+ default:
+ assert(false && "Invalid node type"); // unreachable
+ return -1;
+ }
+ }
+
+#ifdef __BORLANDC__
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs)
+ {
+ return (bool)lhs && rhs;
+ }
+
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs)
+ {
+ return (bool)lhs || rhs;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_text::xml_text(xml_node_struct* root): _root(root)
+ {
+ }
+
+ PUGI_IMPL_FN xml_node_struct* xml_text::_data() const
+ {
+ if (!_root || impl::is_text_node(_root)) return _root;
+
+ // element nodes can have value if parse_embed_pcdata was used
+ if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value)
+ return _root;
+
+ for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling)
+ if (impl::is_text_node(node))
+ return node;
+
+ return NULL;
+ }
+
+ PUGI_IMPL_FN xml_node_struct* xml_text::_data_new()
+ {
+ xml_node_struct* d = _data();
+ if (d) return d;
+
+ return xml_node(_root).append_child(node_pcdata).internal_object();
+ }
+
+ PUGI_IMPL_FN xml_text::xml_text(): _root(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN static void unspecified_bool_xml_text(xml_text***)
+ {
+ }
+
+ PUGI_IMPL_FN xml_text::operator xml_text::unspecified_bool_type() const
+ {
+ return _data() ? unspecified_bool_xml_text : NULL;
+ }
+
+ PUGI_IMPL_FN bool xml_text::operator!() const
+ {
+ return !_data();
+ }
+
+ PUGI_IMPL_FN bool xml_text::empty() const
+ {
+ return _data() == NULL;
+ }
+
+ PUGI_IMPL_FN const char_t* xml_text::get() const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return PUGIXML_TEXT("");
+ const char_t* value = d->value;
+ return value ? value : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const char_t* xml_text::as_string(const char_t* def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? value : def;
+ }
+
+ PUGI_IMPL_FN int xml_text::as_int(int def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_int(value) : def;
+ }
+
+ PUGI_IMPL_FN unsigned int xml_text::as_uint(unsigned int def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_uint(value) : def;
+ }
+
+ PUGI_IMPL_FN double xml_text::as_double(double def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_double(value) : def;
+ }
+
+ PUGI_IMPL_FN float xml_text::as_float(float def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_float(value) : def;
+ }
+
+ PUGI_IMPL_FN bool xml_text::as_bool(bool def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_bool(value) : def;
+ }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN long long xml_text::as_llong(long long def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_llong(value) : def;
+ }
+
+ PUGI_IMPL_FN unsigned long long xml_text::as_ullong(unsigned long long def) const
+ {
+ xml_node_struct* d = _data();
+ if (!d) return def;
+ const char_t* value = d->value;
+ return value ? impl::get_value_ullong(value) : def;
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_text::set(const char_t* rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(const char_t* rhs, size_t size)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, size) : false;
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN bool xml_text::set(string_view_t rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size()) : false;
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_text::set(int rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned int>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(unsigned int rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned int>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(long rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned long>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(unsigned long rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned long>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(float rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(float rhs, int precision)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(double rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(double rhs, int precision)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(bool rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_bool(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+ }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN bool xml_text::set(long long rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned long long>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false;
+ }
+
+ PUGI_IMPL_FN bool xml_text::set(unsigned long long rhs)
+ {
+ xml_node_struct* dn = _data_new();
+
+ return dn ? impl::set_value_integer<unsigned long long>(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(const char_t* rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(int rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned int rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(long rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(double rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(float rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(bool rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ PUGI_IMPL_FN xml_text& xml_text::operator=(string_view_t rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+#endif
+
+#ifdef PUGIXML_HAS_LONG_LONG
+ PUGI_IMPL_FN xml_text& xml_text::operator=(long long rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long long rhs)
+ {
+ set(rhs);
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node xml_text::data() const
+ {
+ return xml_node(_data());
+ }
+
+#ifdef __BORLANDC__
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs)
+ {
+ return (bool)lhs && rhs;
+ }
+
+ PUGI_IMPL_FN bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs)
+ {
+ return (bool)lhs || rhs;
+ }
+#endif
+
+ PUGI_IMPL_FN xml_node_iterator::xml_node_iterator()
+ {
+ }
+
+ PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent())
+ {
+ }
+
+ PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent)
+ {
+ }
+
+ PUGI_IMPL_FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const
+ {
+ return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const
+ {
+ return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN xml_node& xml_node_iterator::operator*() const
+ {
+ assert(_wrap._root);
+ return _wrap;
+ }
+
+ PUGI_IMPL_FN xml_node* xml_node_iterator::operator->() const
+ {
+ assert(_wrap._root);
+#if defined(__BORLANDC__) && __BORLANDC__ <= 0x0670 // XE5
+ return const_cast<xml_node*>(&_wrap);
+#else
+ return &_wrap;
+#endif
+ }
+
+ PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator++()
+ {
+ assert(_wrap._root);
+ _wrap._root = _wrap._root->next_sibling;
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator++(int)
+ {
+ xml_node_iterator temp = *this;
+ ++*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator--()
+ {
+ _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child();
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator--(int)
+ {
+ xml_node_iterator temp = *this;
+ --*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator()
+ {
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent)
+ {
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent)
+ {
+ }
+
+ PUGI_IMPL_FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const
+ {
+ return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const
+ {
+ return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN xml_attribute& xml_attribute_iterator::operator*() const
+ {
+ assert(_wrap._attr);
+ return _wrap;
+ }
+
+ PUGI_IMPL_FN xml_attribute* xml_attribute_iterator::operator->() const
+ {
+ assert(_wrap._attr);
+#if defined(__BORLANDC__) && __BORLANDC__ <= 0x0670 // XE5
+ return const_cast<xml_attribute*>(&_wrap);
+#else
+ return &_wrap;
+#endif
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator++()
+ {
+ assert(_wrap._attr);
+ _wrap._attr = _wrap._attr->next_attribute;
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator++(int)
+ {
+ xml_attribute_iterator temp = *this;
+ ++*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator--()
+ {
+ _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute();
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator--(int)
+ {
+ xml_attribute_iterator temp = *this;
+ --*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(): _name(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name)
+ {
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name)
+ {
+ }
+
+ PUGI_IMPL_FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const
+ {
+ return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const
+ {
+ return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root;
+ }
+
+ PUGI_IMPL_FN xml_node& xml_named_node_iterator::operator*() const
+ {
+ assert(_wrap._root);
+ return _wrap;
+ }
+
+ PUGI_IMPL_FN xml_node* xml_named_node_iterator::operator->() const
+ {
+ assert(_wrap._root);
+#if defined(__BORLANDC__) && __BORLANDC__ <= 0x0670 // XE5
+ return const_cast<xml_node*>(&_wrap);
+#else
+ return &_wrap;
+#endif
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator++()
+ {
+ assert(_wrap._root);
+ _wrap = _wrap.next_sibling(_name);
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator++(int)
+ {
+ xml_named_node_iterator temp = *this;
+ ++*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator--()
+ {
+ if (_wrap._root)
+ _wrap = _wrap.previous_sibling(_name);
+ else
+ {
+ _wrap = _parent.last_child();
+
+ if (!impl::strequal(_wrap.name(), _name))
+ _wrap = _wrap.previous_sibling(_name);
+ }
+
+ return *this;
+ }
+
+ PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator--(int)
+ {
+ xml_named_node_iterator temp = *this;
+ --*this;
+ return temp;
+ }
+
+ PUGI_IMPL_FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto)
+ {
+ }
+
+ PUGI_IMPL_FN xml_parse_result::operator bool() const
+ {
+ return status == status_ok;
+ }
+
+ PUGI_IMPL_FN const char* xml_parse_result::description() const
+ {
+ switch (status)
+ {
+ case status_ok: return "No error";
+
+ case status_file_not_found: return "File was not found";
+ case status_io_error: return "Error reading from file/stream";
+ case status_out_of_memory: return "Could not allocate memory";
+ case status_internal_error: return "Internal error occurred";
+
+ case status_unrecognized_tag: return "Could not determine tag type";
+
+ case status_bad_pi: return "Error parsing document declaration/processing instruction";
+ case status_bad_comment: return "Error parsing comment";
+ case status_bad_cdata: return "Error parsing CDATA section";
+ case status_bad_doctype: return "Error parsing document type declaration";
+ case status_bad_pcdata: return "Error parsing PCDATA section";
+ case status_bad_start_element: return "Error parsing start element tag";
+ case status_bad_attribute: return "Error parsing element attribute";
+ case status_bad_end_element: return "Error parsing end element tag";
+ case status_end_element_mismatch: return "Start-end tags mismatch";
+
+ case status_append_invalid_root: return "Unable to append nodes: root is not an element or document";
+
+ case status_no_document_element: return "No document element found";
+
+ default: return "Unknown error";
+ }
+ }
+
+ PUGI_IMPL_FN xml_document::xml_document(): _buffer(NULL)
+ {
+ _create();
+ }
+
+ PUGI_IMPL_FN xml_document::~xml_document()
+ {
+ _destroy();
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(NULL)
+ {
+ _create();
+ _move(rhs);
+ }
+
+ PUGI_IMPL_FN xml_document& xml_document::operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT
+ {
+ if (this == &rhs) return *this;
+
+ _destroy();
+ _create();
+ _move(rhs);
+
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN void xml_document::reset()
+ {
+ _destroy();
+ _create();
+ }
+
+ PUGI_IMPL_FN void xml_document::reset(const xml_document& proto)
+ {
+ reset();
+
+ impl::node_copy_tree(_root, proto._root);
+ }
+
+ PUGI_IMPL_FN void xml_document::_create()
+ {
+ assert(!_root);
+
+ #ifdef PUGIXML_COMPACT
+ // space for page marker for the first page (uint32_t), rounded up to pointer size; assumes pointers are at least 32-bit
+ const size_t page_offset = sizeof(void*);
+ #else
+ const size_t page_offset = 0;
+ #endif
+
+ // initialize sentinel page
+ PUGI_IMPL_STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + page_offset <= sizeof(_memory));
+
+ // prepare page structure
+ impl::xml_memory_page* page = impl::xml_memory_page::construct(_memory);
+ assert(page);
+
+ page->busy_size = impl::xml_memory_page_size;
+
+ // setup first page marker
+ #ifdef PUGIXML_COMPACT
+ // round-trip through void* to avoid 'cast increases required alignment of target type' warning
+ page->compact_page_marker = reinterpret_cast<uint32_t*>(static_cast<void*>(reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page)));
+ *page->compact_page_marker = sizeof(impl::xml_memory_page);
+ #endif
+
+ // allocate new root
+ _root = new (reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page);
+ _root->prev_sibling_c = _root;
+
+ // setup sentinel page
+ page->allocator = static_cast<impl::xml_document_struct*>(_root);
+
+ // setup hash table pointer in allocator
+ #ifdef PUGIXML_COMPACT
+ page->allocator->_hash = &static_cast<impl::xml_document_struct*>(_root)->hash;
+ #endif
+
+ // verify the document allocation
+ assert(reinterpret_cast<char*>(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory));
+ }
+
+ PUGI_IMPL_FN void xml_document::_destroy()
+ {
+ assert(_root);
+
+ // destroy static storage
+ if (_buffer)
+ {
+ impl::xml_memory::deallocate(_buffer);
+ _buffer = NULL;
+ }
+
+ // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator)
+ for (impl::xml_extra_buffer* extra = static_cast<impl::xml_document_struct*>(_root)->extra_buffers; extra; extra = extra->next)
+ {
+ if (extra->buffer) impl::xml_memory::deallocate(extra->buffer);
+ }
+
+ // destroy dynamic storage, leave sentinel page (it's in static memory)
+ impl::xml_memory_page* root_page = PUGI_IMPL_GETPAGE(_root);
+ assert(root_page && !root_page->prev);
+ assert(reinterpret_cast<char*>(root_page) >= _memory && reinterpret_cast<char*>(root_page) < _memory + sizeof(_memory));
+
+ for (impl::xml_memory_page* page = root_page->next; page; )
+ {
+ impl::xml_memory_page* next = page->next;
+
+ impl::xml_allocator::deallocate_page(page);
+
+ page = next;
+ }
+
+ #ifdef PUGIXML_COMPACT
+ // destroy hash table
+ static_cast<impl::xml_document_struct*>(_root)->hash.clear();
+ #endif
+
+ _root = NULL;
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN void xml_document::_move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT
+ {
+ impl::xml_document_struct* doc = static_cast<impl::xml_document_struct*>(_root);
+ impl::xml_document_struct* other = static_cast<impl::xml_document_struct*>(rhs._root);
+
+ // save first child pointer for later; this needs hash access
+ xml_node_struct* other_first_child = other->first_child;
+
+ #ifdef PUGIXML_COMPACT
+ // reserve space for the hash table up front; this is the only operation that can fail
+ // if it does, we have no choice but to throw (if we have exceptions)
+ if (other_first_child)
+ {
+ size_t other_children = 0;
+ for (xml_node_struct* node = other_first_child; node; node = node->next_sibling)
+ other_children++;
+
+ // in compact mode, each pointer assignment could result in a hash table request
+ // during move, we have to relocate document first_child and parents of all children
+ // normally there's just one child and its parent has a pointerless encoding but
+ // we assume the worst here
+ if (!other->_hash->reserve(other_children + 1))
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return;
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+ }
+ #endif
+
+ // move allocation state
+ // note that other->_root may point to the embedded document page, in which case we should keep original (empty) state
+ if (other->_root != PUGI_IMPL_GETPAGE(other))
+ {
+ doc->_root = other->_root;
+ doc->_busy_size = other->_busy_size;
+ }
+
+ // move buffer state
+ doc->buffer = other->buffer;
+ doc->extra_buffers = other->extra_buffers;
+ _buffer = rhs._buffer;
+
+ #ifdef PUGIXML_COMPACT
+ // move compact hash; note that the hash table can have pointers to other but they will be "inactive", similarly to nodes removed with remove_child
+ doc->hash = other->hash;
+ doc->_hash = &doc->hash;
+
+ // make sure we don't access other hash up until the end when we reinitialize other document
+ other->_hash = NULL;
+ #endif
+
+ // move page structure
+ impl::xml_memory_page* doc_page = PUGI_IMPL_GETPAGE(doc);
+ assert(doc_page && !doc_page->prev && !doc_page->next);
+
+ impl::xml_memory_page* other_page = PUGI_IMPL_GETPAGE(other);
+ assert(other_page && !other_page->prev);
+
+ // relink pages since root page is embedded into xml_document
+ if (impl::xml_memory_page* page = other_page->next)
+ {
+ assert(page->prev == other_page);
+
+ page->prev = doc_page;
+
+ doc_page->next = page;
+ other_page->next = NULL;
+ }
+
+ // make sure pages point to the correct document state
+ for (impl::xml_memory_page* page = doc_page->next; page; page = page->next)
+ {
+ assert(page->allocator == other);
+
+ page->allocator = doc;
+
+ #ifdef PUGIXML_COMPACT
+ // this automatically migrates most children between documents and prevents ->parent assignment from allocating
+ if (page->compact_shared_parent == other)
+ page->compact_shared_parent = doc;
+ #endif
+ }
+
+ // move tree structure
+ assert(!doc->first_child);
+
+ doc->first_child = other_first_child;
+
+ for (xml_node_struct* node = other_first_child; node; node = node->next_sibling)
+ {
+ #ifdef PUGIXML_COMPACT
+ // most children will have migrated when we reassigned compact_shared_parent
+ assert(node->parent == other || node->parent == doc);
+
+ node->parent = doc;
+ #else
+ assert(node->parent == other);
+ node->parent = doc;
+ #endif
+ }
+
+ // reset other document
+ new (other) impl::xml_document_struct(other_page);
+ rhs._buffer = NULL;
+ }
+#endif
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream<char>& stream, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ return impl::load_stream_impl(static_cast<impl::xml_document_struct*>(_root), stream, options, encoding, &_buffer);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream<wchar_t>& stream, unsigned int options)
+ {
+ reset();
+
+ return impl::load_stream_impl(static_cast<impl::xml_document_struct*>(_root), stream, options, encoding_wchar, &_buffer);
+ }
+#endif
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options)
+ {
+ // Force native encoding (skip autodetection)
+ #ifdef PUGIXML_WCHAR_MODE
+ xml_encoding encoding = encoding_wchar;
+ #else
+ xml_encoding encoding = encoding_utf8;
+ #endif
+
+ return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options)
+ {
+ return load_string(contents, options);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ using impl::auto_deleter; // MSVC7 workaround
+ auto_deleter<FILE> file(impl::open_file(path_, "rb"), impl::close_file);
+
+ return impl::load_file_impl(static_cast<impl::xml_document_struct*>(_root), file.data, options, encoding, &_buffer);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ using impl::auto_deleter; // MSVC7 workaround
+ auto_deleter<FILE> file(impl::open_file_wide(path_, L"rb"), impl::close_file);
+
+ return impl::load_file_impl(static_cast<impl::xml_document_struct*>(_root), file.data, options, encoding, &_buffer);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, const_cast<void*>(contents), size, options, encoding, false, false, &_buffer);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, false, &_buffer);
+ }
+
+ PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding)
+ {
+ reset();
+
+ return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, true, &_buffer);
+ }
+
+ PUGI_IMPL_FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+ {
+ impl::xml_buffered_writer buffered_writer(writer, encoding);
+
+ if ((flags & format_write_bom) && buffered_writer.encoding != encoding_latin1)
+ {
+ // BOM always represents the codepoint U+FEFF, so just write it in native encoding
+ #ifdef PUGIXML_WCHAR_MODE
+ unsigned int bom = 0xfeff;
+ buffered_writer.write(static_cast<wchar_t>(bom));
+ #else
+ buffered_writer.write('\xef', '\xbb', '\xbf');
+ #endif
+ }
+
+ if (!(flags & format_no_declaration) && !impl::has_declaration(_root))
+ {
+ buffered_writer.write_string(PUGIXML_TEXT("<?xml version=\"1.0\""));
+ if (buffered_writer.encoding == encoding_latin1) buffered_writer.write_string(PUGIXML_TEXT(" encoding=\"ISO-8859-1\""));
+ buffered_writer.write('?', '>');
+ if (!(flags & format_raw)) buffered_writer.write('\n');
+ }
+
+ impl::node_output(buffered_writer, _root, indent, flags, 0);
+
+ buffered_writer.flush();
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN void xml_document::save(std::basic_ostream<char>& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+ {
+ xml_writer_stream writer(stream);
+
+ save(writer, indent, flags, encoding);
+ }
+
+ PUGI_IMPL_FN void xml_document::save(std::basic_ostream<wchar_t>& stream, const char_t* indent, unsigned int flags) const
+ {
+ xml_writer_stream writer(stream);
+
+ save(writer, indent, flags, encoding_wchar);
+ }
+#endif
+
+ PUGI_IMPL_FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+ {
+ using impl::auto_deleter; // MSVC7 workaround
+ auto_deleter<FILE> file(impl::open_file(path_, (flags & format_save_file_text) ? "w" : "wb"), impl::close_file);
+
+ return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0;
+ }
+
+ PUGI_IMPL_FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+ {
+ using impl::auto_deleter; // MSVC7 workaround
+ auto_deleter<FILE> file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), impl::close_file);
+
+ return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0;
+ }
+
+ PUGI_IMPL_FN xml_node xml_document::document_element() const
+ {
+ assert(_root);
+
+ for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+ if (PUGI_IMPL_NODETYPE(i) == node_element)
+ return xml_node(i);
+
+ return xml_node();
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str)
+ {
+ assert(str);
+
+ return impl::as_utf8_impl(str, impl::strlength_wide(str));
+ }
+
+ PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t>& str)
+ {
+ return impl::as_utf8_impl(str.c_str(), str.size());
+ }
+
+ PUGI_IMPL_FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const char* str)
+ {
+ assert(str);
+
+ return impl::as_wide_impl(str, strlen(str));
+ }
+
+ PUGI_IMPL_FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const std::string& str)
+ {
+ return impl::as_wide_impl(str.c_str(), str.size());
+ }
+#endif
+
+ PUGI_IMPL_FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate)
+ {
+ impl::xml_memory::allocate = allocate;
+ impl::xml_memory::deallocate = deallocate;
+ }
+
+ PUGI_IMPL_FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function()
+ {
+ return impl::xml_memory::allocate;
+ }
+
+ PUGI_IMPL_FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function()
+ {
+ return impl::xml_memory::deallocate;
+ }
+}
+
+#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC))
+namespace std
+{
+ // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier)
+ PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+
+ PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+
+ PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+}
+#endif
+
+#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC)
+namespace std
+{
+ // Workarounds for (non-standard) iterator category detection
+ PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+
+ PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+
+ PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&)
+ {
+ return std::bidirectional_iterator_tag();
+ }
+}
+#endif
+
+#ifndef PUGIXML_NO_XPATH
+// STL replacements
+PUGI_IMPL_NS_BEGIN
+ struct equal_to
+ {
+ template <typename T> bool operator()(const T& lhs, const T& rhs) const
+ {
+ return lhs == rhs;
+ }
+ };
+
+ struct not_equal_to
+ {
+ template <typename T> bool operator()(const T& lhs, const T& rhs) const
+ {
+ return lhs != rhs;
+ }
+ };
+
+ struct less
+ {
+ template <typename T> bool operator()(const T& lhs, const T& rhs) const
+ {
+ return lhs < rhs;
+ }
+ };
+
+ struct less_equal
+ {
+ template <typename T> bool operator()(const T& lhs, const T& rhs) const
+ {
+ return lhs <= rhs;
+ }
+ };
+
+ template <typename T> inline void swap(T& lhs, T& rhs)
+ {
+ T temp = lhs;
+ lhs = rhs;
+ rhs = temp;
+ }
+
+ template <typename I, typename Pred> PUGI_IMPL_FN I min_element(I begin, I end, const Pred& pred)
+ {
+ assert(begin != end);
+
+ I result = begin;
+
+ for (I it = begin + 1; it != end; ++it)
+ if (pred(*it, *result))
+ result = it;
+
+ return result;
+ }
+
+ template <typename I> PUGI_IMPL_FN void reverse(I begin, I end)
+ {
+ while (end - begin > 1)
+ swap(*begin++, *--end);
+ }
+
+ template <typename I> PUGI_IMPL_FN I unique(I begin, I end)
+ {
+ // fast skip head
+ while (end - begin > 1 && *begin != *(begin + 1))
+ begin++;
+
+ if (begin == end)
+ return begin;
+
+ // last written element
+ I write = begin++;
+
+ // merge unique elements
+ while (begin != end)
+ {
+ if (*begin != *write)
+ *++write = *begin++;
+ else
+ begin++;
+ }
+
+ // past-the-end (write points to live element)
+ return write + 1;
+ }
+
+ template <typename T, typename Pred> PUGI_IMPL_FN void insertion_sort(T* begin, T* end, const Pred& pred)
+ {
+ if (begin == end)
+ return;
+
+ for (T* it = begin + 1; it != end; ++it)
+ {
+ T val = *it;
+ T* hole = it;
+
+ // move hole backwards
+ while (hole > begin && pred(val, *(hole - 1)))
+ {
+ *hole = *(hole - 1);
+ hole--;
+ }
+
+ // fill hole with element
+ *hole = val;
+ }
+ }
+
+ template <typename I, typename Pred> inline I median3(I first, I middle, I last, const Pred& pred)
+ {
+ if (pred(*middle, *first))
+ swap(middle, first);
+ if (pred(*last, *middle))
+ swap(last, middle);
+ if (pred(*middle, *first))
+ swap(middle, first);
+
+ return middle;
+ }
+
+ template <typename T, typename Pred> PUGI_IMPL_FN void partition3(T* begin, T* end, T pivot, const Pred& pred, T** out_eqbeg, T** out_eqend)
+ {
+ // invariant: array is split into 4 groups: = < ? > (each variable denotes the boundary between the groups)
+ T* eq = begin;
+ T* lt = begin;
+ T* gt = end;
+
+ while (lt < gt)
+ {
+ if (pred(*lt, pivot))
+ lt++;
+ else if (*lt == pivot)
+ swap(*eq++, *lt++);
+ else
+ swap(*lt, *--gt);
+ }
+
+ // we now have just 3 groups: = < >; move equal elements to the middle
+ T* eqbeg = gt;
+
+ for (T* it = begin; it != eq; ++it)
+ swap(*it, *--eqbeg);
+
+ *out_eqbeg = eqbeg;
+ *out_eqend = gt;
+ }
+
+ template <typename I, typename Pred> PUGI_IMPL_FN void sort(I begin, I end, const Pred& pred)
+ {
+ // sort large chunks
+ while (end - begin > 16)
+ {
+ // find median element
+ I middle = begin + (end - begin) / 2;
+ I median = median3(begin, middle, end - 1, pred);
+
+ // partition in three chunks (< = >)
+ I eqbeg, eqend;
+ partition3(begin, end, *median, pred, &eqbeg, &eqend);
+
+ // loop on larger half
+ if (eqbeg - begin > end - eqend)
+ {
+ sort(eqend, end, pred);
+ end = eqbeg;
+ }
+ else
+ {
+ sort(begin, eqbeg, pred);
+ begin = eqend;
+ }
+ }
+
+ // insertion sort small chunk
+ insertion_sort(begin, end, pred);
+ }
+
+ PUGI_IMPL_FN bool hash_insert(const void** table, size_t size, const void* key)
+ {
+ assert(key);
+
+ unsigned int h = static_cast<unsigned int>(reinterpret_cast<uintptr_t>(key));
+
+ // MurmurHash3 32-bit finalizer
+ h ^= h >> 16;
+ h *= 0x85ebca6bu;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35u;
+ h ^= h >> 16;
+
+ size_t hashmod = size - 1;
+ size_t bucket = h & hashmod;
+
+ for (size_t probe = 0; probe <= hashmod; ++probe)
+ {
+ if (table[bucket] == NULL)
+ {
+ table[bucket] = key;
+ return true;
+ }
+
+ if (table[bucket] == key)
+ return false;
+
+ // hash collision, quadratic probing
+ bucket = (bucket + probe + 1) & hashmod;
+ }
+
+ assert(false && "Hash table is full"); // unreachable
+ return false;
+ }
+PUGI_IMPL_NS_END
+
+// Allocator used for AST and evaluation stacks
+PUGI_IMPL_NS_BEGIN
+ static const size_t xpath_memory_page_size =
+ #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE
+ PUGIXML_MEMORY_XPATH_PAGE_SIZE
+ #else
+ 4096
+ #endif
+ ;
+
+ static const uintptr_t xpath_memory_block_alignment = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*);
+
+ struct xpath_memory_block
+ {
+ xpath_memory_block* next;
+ size_t capacity;
+
+ union
+ {
+ char data[xpath_memory_page_size];
+ double alignment;
+ };
+ };
+
+ struct xpath_allocator
+ {
+ xpath_memory_block* _root;
+ size_t _root_size;
+ bool* _error;
+
+ xpath_allocator(xpath_memory_block* root, bool* error = NULL): _root(root), _root_size(0), _error(error)
+ {
+ }
+
+ void* allocate(size_t size)
+ {
+ // round size up to block alignment boundary
+ size = (size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1);
+
+ if (_root_size + size <= _root->capacity)
+ {
+ void* buf = &_root->data[0] + _root_size;
+ _root_size += size;
+ return buf;
+ }
+ else
+ {
+ // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests
+ size_t block_capacity_base = sizeof(_root->data);
+ size_t block_capacity_req = size + block_capacity_base / 4;
+ size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req;
+
+ size_t block_size = block_capacity + offsetof(xpath_memory_block, data);
+
+ xpath_memory_block* block = static_cast<xpath_memory_block*>(xml_memory::allocate(block_size));
+ if (!block)
+ {
+ if (_error) *_error = true;
+ return NULL;
+ }
+
+ block->next = _root;
+ block->capacity = block_capacity;
+
+ _root = block;
+ _root_size = size;
+
+ return block->data;
+ }
+ }
+
+ void* reallocate(void* ptr, size_t old_size, size_t new_size)
+ {
+ // round size up to block alignment boundary
+ old_size = (old_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1);
+ new_size = (new_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1);
+
+ // we can only reallocate the last object
+ assert(ptr == NULL || static_cast<char*>(ptr) + old_size == &_root->data[0] + _root_size);
+
+ // try to reallocate the object inplace
+ if (ptr && _root_size - old_size + new_size <= _root->capacity)
+ {
+ _root_size = _root_size - old_size + new_size;
+ return ptr;
+ }
+
+ // allocate a new block
+ void* result = allocate(new_size);
+ if (!result) return NULL;
+
+ // we have a new block
+ if (ptr)
+ {
+ // copy old data (we only support growing)
+ assert(new_size >= old_size);
+ memcpy(result, ptr, old_size);
+
+ // free the previous page if it had no other objects
+ assert(_root->data == result);
+ assert(_root->next);
+
+ if (_root->next->data == ptr)
+ {
+ // deallocate the whole page, unless it was the first one
+ xpath_memory_block* next = _root->next->next;
+
+ if (next)
+ {
+ xml_memory::deallocate(_root->next);
+ _root->next = next;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ void revert(const xpath_allocator& state)
+ {
+ // free all new pages
+ xpath_memory_block* cur = _root;
+
+ while (cur != state._root)
+ {
+ xpath_memory_block* next = cur->next;
+
+ xml_memory::deallocate(cur);
+
+ cur = next;
+ }
+
+ // restore state
+ _root = state._root;
+ _root_size = state._root_size;
+ }
+
+ void release()
+ {
+ xpath_memory_block* cur = _root;
+ assert(cur);
+
+ while (cur->next)
+ {
+ xpath_memory_block* next = cur->next;
+
+ xml_memory::deallocate(cur);
+
+ cur = next;
+ }
+ }
+ };
+
+ struct xpath_allocator_capture
+ {
+ xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc)
+ {
+ }
+
+ ~xpath_allocator_capture()
+ {
+ _target->revert(_state);
+ }
+
+ xpath_allocator* _target;
+ xpath_allocator _state;
+ };
+
+ struct xpath_stack
+ {
+ xpath_allocator* result;
+ xpath_allocator* temp;
+ };
+
+ struct xpath_stack_data
+ {
+ xpath_memory_block blocks[2];
+ xpath_allocator result;
+ xpath_allocator temp;
+ xpath_stack stack;
+ bool oom;
+
+ xpath_stack_data(): result(blocks + 0, &oom), temp(blocks + 1, &oom), oom(false)
+ {
+ blocks[0].next = blocks[1].next = NULL;
+ blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data);
+
+ stack.result = &result;
+ stack.temp = &temp;
+ }
+
+ ~xpath_stack_data()
+ {
+ result.release();
+ temp.release();
+ }
+ };
+PUGI_IMPL_NS_END
+
+// String class
+PUGI_IMPL_NS_BEGIN
+ class xpath_string
+ {
+ const char_t* _buffer;
+ bool _uses_heap;
+ size_t _length_heap;
+
+ static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc)
+ {
+ char_t* result = static_cast<char_t*>(alloc->allocate((length + 1) * sizeof(char_t)));
+ if (!result) return NULL;
+
+ memcpy(result, string, length * sizeof(char_t));
+ result[length] = 0;
+
+ return result;
+ }
+
+ xpath_string(const char_t* buffer, bool uses_heap_, size_t length_heap): _buffer(buffer), _uses_heap(uses_heap_), _length_heap(length_heap)
+ {
+ }
+
+ public:
+ static xpath_string from_const(const char_t* str)
+ {
+ return xpath_string(str, false, 0);
+ }
+
+ static xpath_string from_heap_preallocated(const char_t* begin, const char_t* end)
+ {
+ assert(begin <= end && *end == 0);
+
+ return xpath_string(begin, true, static_cast<size_t>(end - begin));
+ }
+
+ static xpath_string from_heap(const char_t* begin, const char_t* end, xpath_allocator* alloc)
+ {
+ assert(begin <= end);
+
+ if (begin == end)
+ return xpath_string();
+
+ size_t length = static_cast<size_t>(end - begin);
+ const char_t* data = duplicate_string(begin, length, alloc);
+
+ return data ? xpath_string(data, true, length) : xpath_string();
+ }
+
+ xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false), _length_heap(0)
+ {
+ }
+
+ void append(const xpath_string& o, xpath_allocator* alloc)
+ {
+ // skip empty sources
+ if (!*o._buffer) return;
+
+ // fast append for constant empty target and constant source
+ if (!*_buffer && !_uses_heap && !o._uses_heap)
+ {
+ _buffer = o._buffer;
+ }
+ else
+ {
+ // need to make heap copy
+ size_t target_length = length();
+ size_t source_length = o.length();
+ size_t result_length = target_length + source_length;
+
+ // allocate new buffer
+ char_t* result = static_cast<char_t*>(alloc->reallocate(_uses_heap ? const_cast<char_t*>(_buffer) : NULL, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t)));
+ if (!result) return;
+
+ // append first string to the new buffer in case there was no reallocation
+ if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t));
+
+ // append second string to the new buffer
+ memcpy(result + target_length, o._buffer, source_length * sizeof(char_t));
+ result[result_length] = 0;
+
+ // finalize
+ _buffer = result;
+ _uses_heap = true;
+ _length_heap = result_length;
+ }
+ }
+
+ const char_t* c_str() const
+ {
+ return _buffer;
+ }
+
+ size_t length() const
+ {
+ return _uses_heap ? _length_heap : strlength(_buffer);
+ }
+
+ char_t* data(xpath_allocator* alloc)
+ {
+ // make private heap copy
+ if (!_uses_heap)
+ {
+ size_t length_ = strlength(_buffer);
+ const char_t* data_ = duplicate_string(_buffer, length_, alloc);
+
+ if (!data_) return NULL;
+
+ _buffer = data_;
+ _uses_heap = true;
+ _length_heap = length_;
+ }
+
+ return const_cast<char_t*>(_buffer);
+ }
+
+ bool empty() const
+ {
+ return *_buffer == 0;
+ }
+
+ bool operator==(const xpath_string& o) const
+ {
+ return strequal(_buffer, o._buffer);
+ }
+
+ bool operator!=(const xpath_string& o) const
+ {
+ return !strequal(_buffer, o._buffer);
+ }
+
+ bool uses_heap() const
+ {
+ return _uses_heap;
+ }
+ };
+PUGI_IMPL_NS_END
+
+PUGI_IMPL_NS_BEGIN
+ PUGI_IMPL_FN bool starts_with(const char_t* string, const char_t* pattern)
+ {
+ while (*pattern && *string == *pattern)
+ {
+ string++;
+ pattern++;
+ }
+
+ return *pattern == 0;
+ }
+
+ PUGI_IMPL_FN const char_t* find_char(const char_t* s, char_t c)
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ return wcschr(s, c);
+ #else
+ return strchr(s, c);
+ #endif
+ }
+
+ PUGI_IMPL_FN const char_t* find_substring(const char_t* s, const char_t* p)
+ {
+ #ifdef PUGIXML_WCHAR_MODE
+ // MSVC6 wcsstr bug workaround (if s is empty it always returns 0)
+ return (*p == 0) ? s : wcsstr(s, p);
+ #else
+ return strstr(s, p);
+ #endif
+ }
+
+ // Converts symbol to lower case, if it is an ASCII one
+ PUGI_IMPL_FN char_t tolower_ascii(char_t ch)
+ {
+ return static_cast<unsigned int>(ch - 'A') < 26 ? static_cast<char_t>(ch | ' ') : ch;
+ }
+
+ PUGI_IMPL_FN xpath_string string_value_element(xml_node_struct* n, xpath_allocator* alloc)
+ {
+ xpath_string result;
+
+ // element nodes can have value if parse_embed_pcdata was used
+ if (n->value)
+ result.append(xpath_string::from_const(n->value), alloc);
+
+ xml_node_struct* cur = n->first_child;
+
+ while (cur && cur != n)
+ {
+ xml_node_type type = PUGI_IMPL_NODETYPE(cur);
+
+ if ((type == node_pcdata || type == node_cdata) && cur->value)
+ result.append(xpath_string::from_const(cur->value), alloc);
+
+ if (cur->first_child)
+ cur = cur->first_child;
+ else if (cur->next_sibling)
+ cur = cur->next_sibling;
+ else
+ {
+ while (!cur->next_sibling && cur != n)
+ cur = cur->parent;
+
+ if (cur != n) cur = cur->next_sibling;
+ }
+ }
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc)
+ {
+ if (na.attribute())
+ return xpath_string::from_const(na.attribute().value());
+ else
+ {
+ xml_node n = na.node();
+
+ switch (n.type())
+ {
+ case node_pcdata:
+ case node_cdata:
+ case node_comment:
+ case node_pi:
+ return xpath_string::from_const(n.value());
+
+ case node_document:
+ case node_element:
+ return string_value_element(n.internal_object(), alloc);
+
+ default:
+ return xpath_string();
+ }
+ }
+ }
+
+ PUGI_IMPL_FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn)
+ {
+ assert(ln->parent == rn->parent);
+
+ // there is no common ancestor (the shared parent is null), nodes are from different documents
+ if (!ln->parent) return ln < rn;
+
+ // determine sibling order
+ xml_node_struct* ls = ln;
+ xml_node_struct* rs = rn;
+
+ while (ls && rs)
+ {
+ if (ls == rn) return true;
+ if (rs == ln) return false;
+
+ ls = ls->next_sibling;
+ rs = rs->next_sibling;
+ }
+
+ // if rn sibling chain ended ln must be before rn
+ return !rs;
+ }
+
+ PUGI_IMPL_FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn)
+ {
+ // find common ancestor at the same depth, if any
+ xml_node_struct* lp = ln;
+ xml_node_struct* rp = rn;
+
+ while (lp && rp && lp->parent != rp->parent)
+ {
+ lp = lp->parent;
+ rp = rp->parent;
+ }
+
+ // parents are the same!
+ if (lp && rp) return node_is_before_sibling(lp, rp);
+
+ // nodes are at different depths, need to normalize heights
+ bool left_higher = !lp;
+
+ while (lp)
+ {
+ lp = lp->parent;
+ ln = ln->parent;
+ }
+
+ while (rp)
+ {
+ rp = rp->parent;
+ rn = rn->parent;
+ }
+
+ // one node is the ancestor of the other
+ if (ln == rn) return left_higher;
+
+ // find common ancestor... again
+ while (ln->parent != rn->parent)
+ {
+ ln = ln->parent;
+ rn = rn->parent;
+ }
+
+ return node_is_before_sibling(ln, rn);
+ }
+
+ PUGI_IMPL_FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node)
+ {
+ while (node && node != parent) node = node->parent;
+
+ return parent && node == parent;
+ }
+
+ PUGI_IMPL_FN const void* document_buffer_order(const xpath_node& xnode)
+ {
+ xml_node_struct* node = xnode.node().internal_object();
+
+ if (node)
+ {
+ if ((get_document(node).header & xml_memory_page_contents_shared_mask) == 0)
+ {
+ if (node->name && (node->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return node->name;
+ if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value;
+ }
+
+ return NULL;
+ }
+
+ xml_attribute_struct* attr = xnode.attribute().internal_object();
+
+ if (attr)
+ {
+ if ((get_document(attr).header & xml_memory_page_contents_shared_mask) == 0)
+ {
+ if ((attr->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return attr->name;
+ if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value;
+ }
+
+ return NULL;
+ }
+
+ return NULL;
+ }
+
+ struct document_order_comparator
+ {
+ bool operator()(const xpath_node& lhs, const xpath_node& rhs) const
+ {
+ // optimized document order based check
+ const void* lo = document_buffer_order(lhs);
+ const void* ro = document_buffer_order(rhs);
+
+ if (lo && ro) return lo < ro;
+
+ // slow comparison
+ xml_node ln = lhs.node(), rn = rhs.node();
+
+ // compare attributes
+ if (lhs.attribute() && rhs.attribute())
+ {
+ // shared parent
+ if (lhs.parent() == rhs.parent())
+ {
+ // determine sibling order
+ for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute())
+ if (a == rhs.attribute())
+ return true;
+
+ return false;
+ }
+
+ // compare attribute parents
+ ln = lhs.parent();
+ rn = rhs.parent();
+ }
+ else if (lhs.attribute())
+ {
+ // attributes go after the parent element
+ if (lhs.parent() == rhs.node()) return false;
+
+ ln = lhs.parent();
+ }
+ else if (rhs.attribute())
+ {
+ // attributes go after the parent element
+ if (rhs.parent() == lhs.node()) return true;
+
+ rn = rhs.parent();
+ }
+
+ if (ln == rn) return false;
+
+ if (!ln || !rn) return ln < rn;
+
+ return node_is_before(ln.internal_object(), rn.internal_object());
+ }
+ };
+
+ PUGI_IMPL_FN double gen_nan()
+ {
+ #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24))
+ PUGI_IMPL_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t));
+ typedef uint32_t UI; // BCC5 workaround
+ union { float f; UI i; } u;
+ u.i = 0x7fc00000;
+ return double(u.f);
+ #else
+ // fallback
+ const volatile double zero = 0.0;
+ return zero / zero;
+ #endif
+ }
+
+ PUGI_IMPL_FN bool is_nan(double value)
+ {
+ #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__)
+ return !!_isnan(value);
+ #elif defined(fpclassify) && defined(FP_NAN)
+ return fpclassify(value) == FP_NAN;
+ #else
+ // fallback
+ const volatile double v = value;
+ return v != v;
+ #endif
+ }
+
+ PUGI_IMPL_FN const char_t* convert_number_to_string_special(double value)
+ {
+ #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__)
+ if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0;
+ if (_isnan(value)) return PUGIXML_TEXT("NaN");
+ return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+ #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO)
+ switch (fpclassify(value))
+ {
+ case FP_NAN:
+ return PUGIXML_TEXT("NaN");
+
+ case FP_INFINITE:
+ return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+
+ case FP_ZERO:
+ return PUGIXML_TEXT("0");
+
+ default:
+ return 0;
+ }
+ #else
+ // fallback
+ const volatile double v = value;
+
+ if (v == 0) return PUGIXML_TEXT("0");
+ if (v != v) return PUGIXML_TEXT("NaN");
+ if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+ return NULL;
+ #endif
+ }
+
+ PUGI_IMPL_FN bool convert_number_to_boolean(double value)
+ {
+ return (value != 0 && !is_nan(value));
+ }
+
+ PUGI_IMPL_FN void truncate_zeros(char* begin, char* end)
+ {
+ while (begin != end && end[-1] == '0') end--;
+
+ *end = 0;
+ }
+
+ // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent
+#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400
+ PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent)
+ {
+ // get base values
+ int sign, exponent;
+ _ecvt_s(buffer, sizeof(buffer), value, DBL_DIG + 1, &exponent, &sign);
+
+ // truncate redundant zeros
+ truncate_zeros(buffer, buffer + strlen(buffer));
+
+ // fill results
+ *out_mantissa = buffer;
+ *out_exponent = exponent;
+ }
+#else
+ PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent)
+ {
+ #ifdef PUGIXML_CHARCONV_FLOAT
+ std::to_chars_result res = std::to_chars(buffer, buffer + sizeof(buffer) - 1, value, std::chars_format::scientific, DBL_DIG);
+ assert(res.ec == std::errc());
+ *res.ptr = '\0';
+ #else
+ // get a scientific notation value with IEEE DBL_DIG decimals
+ PUGI_IMPL_SNPRINTF(buffer, "%.*e", DBL_DIG, value);
+ #endif
+ // get the exponent (possibly negative)
+ char* exponent_string = strchr(buffer, 'e');
+ assert(exponent_string);
+
+ char *s = exponent_string + 1;
+ bool isneg = *s++ == '-';
+ int exponent = 0;
+ while (*s) exponent = exponent * 10 + (*s++ - '0');
+ exponent = isneg ? -exponent : exponent;
+
+ // extract mantissa string: skip sign
+ char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer;
+ assert(mantissa[0] != '0' && (mantissa[1] == '.' || mantissa[1] == ','));
+
+ // divide mantissa by 10 to eliminate integer part
+ mantissa[1] = mantissa[0];
+ mantissa++;
+ exponent++;
+
+ // remove extra mantissa digits and zero-terminate mantissa
+ truncate_zeros(mantissa, exponent_string);
+
+ // fill results
+ *out_mantissa = mantissa;
+ *out_exponent = exponent;
+ }
+#endif
+
+ PUGI_IMPL_FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc)
+ {
+ // try special number conversion
+ const char_t* special = convert_number_to_string_special(value);
+ if (special) return xpath_string::from_const(special);
+
+ // get mantissa + exponent form
+ char mantissa_buffer[32];
+
+ char* mantissa;
+ int exponent;
+ convert_number_to_mantissa_exponent(value, mantissa_buffer, &mantissa, &exponent);
+
+ // allocate a buffer of suitable length for the number
+ size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4;
+ char_t* result = static_cast<char_t*>(alloc->allocate(sizeof(char_t) * result_size));
+ if (!result) return xpath_string();
+
+ // make the number!
+ char_t* s = result;
+
+ // sign
+ if (value < 0) *s++ = '-';
+
+ // integer part
+ if (exponent <= 0)
+ {
+ *s++ = '0';
+ }
+ else
+ {
+ while (exponent > 0)
+ {
+ assert(*mantissa == 0 || static_cast<unsigned int>(*mantissa - '0') <= 9);
+ *s++ = *mantissa ? *mantissa++ : '0';
+ exponent--;
+ }
+ }
+
+ // fractional part
+ if (*mantissa)
+ {
+ // decimal point
+ *s++ = '.';
+
+ // extra zeroes from negative exponent
+ while (exponent < 0)
+ {
+ *s++ = '0';
+ exponent++;
+ }
+
+ // extra mantissa digits
+ while (*mantissa)
+ {
+ assert(static_cast<unsigned int>(*mantissa - '0') <= 9);
+ *s++ = *mantissa++;
+ }
+ }
+
+ // zero-terminate
+ assert(s < result + result_size);
+ *s = 0;
+
+ return xpath_string::from_heap_preallocated(result, s);
+ }
+
+ PUGI_IMPL_FN bool check_string_to_number_format(const char_t* string)
+ {
+ // parse sign
+ if (*string == '-') ++string;
+
+ if (!*string) return false;
+
+ // if there is no integer part, there should be a decimal part with at least one digit
+ if (!PUGI_IMPL_IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI_IMPL_IS_CHARTYPEX(string[1], ctx_digit))) return false;
+
+ // parse integer part
+ while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string;
+
+ // parse decimal part
+ if (*string == '.')
+ {
+ ++string;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string;
+ }
+
+ // parse trailing whitespace
+ while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string;
+
+ return *string == 0;
+ }
+
+ PUGI_IMPL_FN double convert_string_to_number(const char_t* string)
+ {
+ // parse leading whitespace
+ while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string;
+
+ // check string format
+ if (!check_string_to_number_format(string)) return gen_nan();
+
+ // parse string
+ #ifdef PUGIXML_WCHAR_MODE
+ return wcstod(string, NULL);
+ #elif defined(PUGIXML_CHARCONV_FLOAT)
+ // return code is intentionally ignored to let libc++/MSVC STL correctly handle underflow/overflow
+ double result = 0.0;
+ std::from_chars(string, string + strlen(string), result);
+ return result;
+ #else
+ return strtod(string, NULL);
+ #endif
+ }
+
+ PUGI_IMPL_FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result)
+ {
+ size_t length = static_cast<size_t>(end - begin);
+ char_t* scratch = buffer;
+
+ if (length >= sizeof(buffer) / sizeof(buffer[0]))
+ {
+ // need to make dummy on-heap copy
+ scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!scratch) return false;
+ }
+
+ // copy string to zero-terminated buffer and perform conversion
+ memcpy(scratch, begin, length * sizeof(char_t));
+ scratch[length] = 0;
+
+ *out_result = convert_string_to_number(scratch);
+
+ // free dummy buffer
+ if (scratch != buffer) xml_memory::deallocate(scratch);
+
+ return true;
+ }
+
+ PUGI_IMPL_FN double round_nearest(double value)
+ {
+ return floor(value + 0.5);
+ }
+
+ PUGI_IMPL_FN double round_nearest_nzero(double value)
+ {
+ // same as round_nearest, but returns -0 for [-0.5, -0]
+ // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0)
+ return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5);
+ }
+
+ PUGI_IMPL_FN const char_t* qualified_name(const xpath_node& node)
+ {
+ return node.attribute() ? node.attribute().name() : node.node().name();
+ }
+
+ PUGI_IMPL_FN const char_t* local_name(const xpath_node& node)
+ {
+ const char_t* name = qualified_name(node);
+ const char_t* p = find_char(name, ':');
+
+ return p ? p + 1 : name;
+ }
+
+ struct namespace_uri_predicate
+ {
+ const char_t* prefix;
+ size_t prefix_length;
+
+ namespace_uri_predicate(const char_t* name)
+ {
+ const char_t* pos = find_char(name, ':');
+
+ prefix = pos ? name : NULL;
+ prefix_length = pos ? static_cast<size_t>(pos - name) : 0;
+ }
+
+ bool operator()(xml_attribute a) const
+ {
+ const char_t* name = a.name();
+
+ if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false;
+
+ return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0;
+ }
+ };
+
+ PUGI_IMPL_FN const char_t* namespace_uri(xml_node node)
+ {
+ namespace_uri_predicate pred = node.name();
+
+ xml_node p = node;
+
+ while (p)
+ {
+ xml_attribute a = p.find_attribute(pred);
+
+ if (a) return a.value();
+
+ p = p.parent();
+ }
+
+ return PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const char_t* namespace_uri(xml_attribute attr, xml_node parent)
+ {
+ namespace_uri_predicate pred = attr.name();
+
+ // Default namespace does not apply to attributes
+ if (!pred.prefix) return PUGIXML_TEXT("");
+
+ xml_node p = parent;
+
+ while (p)
+ {
+ xml_attribute a = p.find_attribute(pred);
+
+ if (a) return a.value();
+
+ p = p.parent();
+ }
+
+ return PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const char_t* namespace_uri(const xpath_node& node)
+ {
+ return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node());
+ }
+
+ PUGI_IMPL_FN char_t* normalize_space(char_t* buffer)
+ {
+ char_t* write = buffer;
+
+ for (char_t* it = buffer; *it; )
+ {
+ char_t ch = *it++;
+
+ if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space))
+ {
+ // replace whitespace sequence with single space
+ while (PUGI_IMPL_IS_CHARTYPE(*it, ct_space)) it++;
+
+ // avoid leading spaces
+ if (write != buffer) *write++ = ' ';
+ }
+ else *write++ = ch;
+ }
+
+ // remove trailing space
+ if (write != buffer && PUGI_IMPL_IS_CHARTYPE(write[-1], ct_space)) write--;
+
+ // zero-terminate
+ *write = 0;
+
+ return write;
+ }
+
+ PUGI_IMPL_FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length)
+ {
+ char_t* write = buffer;
+
+ while (*buffer)
+ {
+ PUGI_IMPL_DMC_VOLATILE char_t ch = *buffer++;
+
+ const char_t* pos = find_char(from, ch);
+
+ if (!pos)
+ *write++ = ch; // do not process
+ else if (static_cast<size_t>(pos - from) < to_length)
+ *write++ = to[pos - from]; // replace
+ }
+
+ // zero-terminate
+ *write = 0;
+
+ return write;
+ }
+
+ PUGI_IMPL_FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to)
+ {
+ unsigned char table[128] = {0};
+
+ while (*from)
+ {
+ unsigned int fc = static_cast<unsigned int>(*from);
+ unsigned int tc = static_cast<unsigned int>(*to);
+
+ if (fc >= 128 || tc >= 128)
+ return NULL;
+
+ // code=128 means "skip character"
+ if (!table[fc])
+ table[fc] = static_cast<unsigned char>(tc ? tc : 128);
+
+ from++;
+ if (tc) to++;
+ }
+
+ for (int i = 0; i < 128; ++i)
+ if (!table[i])
+ table[i] = static_cast<unsigned char>(i);
+
+ void* result = alloc->allocate(sizeof(table));
+ if (!result) return NULL;
+
+ memcpy(result, table, sizeof(table));
+
+ return static_cast<unsigned char*>(result);
+ }
+
+ PUGI_IMPL_FN char_t* translate_table(char_t* buffer, const unsigned char* table)
+ {
+ char_t* write = buffer;
+
+ while (*buffer)
+ {
+ char_t ch = *buffer++;
+ unsigned int index = static_cast<unsigned int>(ch);
+
+ if (index < 128)
+ {
+ unsigned char code = table[index];
+
+ // code=128 means "skip character" (table size is 128 so 128 can be a special value)
+ // this code skips these characters without extra branches
+ *write = static_cast<char_t>(code);
+ write += 1 - (code >> 7);
+ }
+ else
+ {
+ *write++ = ch;
+ }
+ }
+
+ // zero-terminate
+ *write = 0;
+
+ return write;
+ }
+
+ inline bool is_xpath_attribute(const char_t* name)
+ {
+ return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':'));
+ }
+
+ struct xpath_variable_boolean: xpath_variable
+ {
+ xpath_variable_boolean(): xpath_variable(xpath_type_boolean), value(false)
+ {
+ }
+
+ bool value;
+ char_t name[1];
+ };
+
+ struct xpath_variable_number: xpath_variable
+ {
+ xpath_variable_number(): xpath_variable(xpath_type_number), value(0)
+ {
+ }
+
+ double value;
+ char_t name[1];
+ };
+
+ struct xpath_variable_string: xpath_variable
+ {
+ xpath_variable_string(): xpath_variable(xpath_type_string), value(NULL)
+ {
+ }
+
+ ~xpath_variable_string()
+ {
+ if (value) xml_memory::deallocate(value);
+ }
+
+ char_t* value;
+ char_t name[1];
+ };
+
+ struct xpath_variable_node_set: xpath_variable
+ {
+ xpath_variable_node_set(): xpath_variable(xpath_type_node_set)
+ {
+ }
+
+ xpath_node_set value;
+ char_t name[1];
+ };
+
+ PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash_string(const char_t* str)
+ {
+ // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time)
+ unsigned int result = 0;
+
+ while (*str)
+ {
+ result += static_cast<unsigned int>(*str++);
+ result += result << 10;
+ result ^= result >> 6;
+ }
+
+ result += result << 3;
+ result ^= result >> 11;
+ result += result << 15;
+
+ return result;
+ }
+
+ template <typename T> PUGI_IMPL_FN T* new_xpath_variable(const char_t* name)
+ {
+ size_t length = strlength(name);
+ if (length == 0) return NULL; // empty variable names are invalid
+
+ // we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters
+ void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t));
+ if (!memory) return NULL;
+
+ T* result = new (memory) T();
+
+ memcpy(result->name, name, (length + 1) * sizeof(char_t));
+
+ return result;
+ }
+
+ PUGI_IMPL_FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name)
+ {
+ switch (type)
+ {
+ case xpath_type_node_set:
+ return new_xpath_variable<xpath_variable_node_set>(name);
+
+ case xpath_type_number:
+ return new_xpath_variable<xpath_variable_number>(name);
+
+ case xpath_type_string:
+ return new_xpath_variable<xpath_variable_string>(name);
+
+ case xpath_type_boolean:
+ return new_xpath_variable<xpath_variable_boolean>(name);
+
+ default:
+ return NULL;
+ }
+ }
+
+ template <typename T> PUGI_IMPL_FN void delete_xpath_variable(T* var)
+ {
+ var->~T();
+ xml_memory::deallocate(var);
+ }
+
+ PUGI_IMPL_FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var)
+ {
+ switch (type)
+ {
+ case xpath_type_node_set:
+ delete_xpath_variable(static_cast<xpath_variable_node_set*>(var));
+ break;
+
+ case xpath_type_number:
+ delete_xpath_variable(static_cast<xpath_variable_number*>(var));
+ break;
+
+ case xpath_type_string:
+ delete_xpath_variable(static_cast<xpath_variable_string*>(var));
+ break;
+
+ case xpath_type_boolean:
+ delete_xpath_variable(static_cast<xpath_variable_boolean*>(var));
+ break;
+
+ default:
+ assert(false && "Invalid variable type"); // unreachable
+ }
+ }
+
+ PUGI_IMPL_FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs)
+ {
+ assert(lhs->type() == rhs->type());
+
+ switch (rhs->type())
+ {
+ case xpath_type_node_set:
+ return lhs->set(static_cast<const xpath_variable_node_set*>(rhs)->value);
+
+ case xpath_type_number:
+ return lhs->set(static_cast<const xpath_variable_number*>(rhs)->value);
+
+ case xpath_type_string:
+ {
+ const char_t* value = static_cast<const xpath_variable_string*>(rhs)->value;
+ assert(!static_cast<xpath_variable_string*>(lhs)->value); // null copy is a no-op
+ return !value || lhs->set(value);
+ }
+
+ case xpath_type_boolean:
+ return lhs->set(static_cast<const xpath_variable_boolean*>(rhs)->value);
+
+ default:
+ assert(false && "Invalid variable type"); // unreachable
+ return false;
+ }
+ }
+
+ PUGI_IMPL_FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result)
+ {
+ size_t length = static_cast<size_t>(end - begin);
+ char_t* scratch = buffer;
+
+ if (length >= sizeof(buffer) / sizeof(buffer[0]))
+ {
+ // need to make dummy on-heap copy
+ scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+ if (!scratch) return false;
+ }
+
+ // copy string to zero-terminated buffer and perform lookup
+ memcpy(scratch, begin, length * sizeof(char_t));
+ scratch[length] = 0;
+
+ *out_result = set->get(scratch);
+
+ // free dummy buffer
+ if (scratch != buffer) xml_memory::deallocate(scratch);
+
+ return true;
+ }
+PUGI_IMPL_NS_END
+
+// Internal node set class
+PUGI_IMPL_NS_BEGIN
+ PUGI_IMPL_FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end)
+ {
+ if (end - begin < 2)
+ return xpath_node_set::type_sorted;
+
+ document_order_comparator cmp;
+
+ bool first = cmp(begin[0], begin[1]);
+
+ for (const xpath_node* it = begin + 1; it + 1 < end; ++it)
+ if (cmp(it[0], it[1]) != first)
+ return xpath_node_set::type_unsorted;
+
+ return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse;
+ }
+
+ PUGI_IMPL_FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev)
+ {
+ xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted;
+
+ if (type == xpath_node_set::type_unsorted)
+ {
+ xpath_node_set::type_t sorted = xpath_get_order(begin, end);
+
+ if (sorted == xpath_node_set::type_unsorted)
+ {
+ sort(begin, end, document_order_comparator());
+
+ type = xpath_node_set::type_sorted;
+ }
+ else
+ type = sorted;
+ }
+
+ if (type != order) reverse(begin, end);
+
+ return order;
+ }
+
+ PUGI_IMPL_FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type)
+ {
+ if (begin == end) return xpath_node();
+
+ switch (type)
+ {
+ case xpath_node_set::type_sorted:
+ return *begin;
+
+ case xpath_node_set::type_sorted_reverse:
+ return *(end - 1);
+
+ case xpath_node_set::type_unsorted:
+ return *min_element(begin, end, document_order_comparator());
+
+ default:
+ assert(false && "Invalid node set type"); // unreachable
+ return xpath_node();
+ }
+ }
+
+ class xpath_node_set_raw
+ {
+ xpath_node_set::type_t _type;
+
+ xpath_node* _begin;
+ xpath_node* _end;
+ xpath_node* _eos;
+
+ public:
+ xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(NULL), _end(NULL), _eos(NULL)
+ {
+ }
+
+ xpath_node* begin() const
+ {
+ return _begin;
+ }
+
+ xpath_node* end() const
+ {
+ return _end;
+ }
+
+ bool empty() const
+ {
+ return _begin == _end;
+ }
+
+ size_t size() const
+ {
+ return static_cast<size_t>(_end - _begin);
+ }
+
+ xpath_node first() const
+ {
+ return xpath_first(_begin, _end, _type);
+ }
+
+ void push_back_grow(const xpath_node& node, xpath_allocator* alloc);
+
+ void push_back(const xpath_node& node, xpath_allocator* alloc)
+ {
+ if (_end != _eos)
+ *_end++ = node;
+ else
+ push_back_grow(node, alloc);
+ }
+
+ void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc)
+ {
+ if (begin_ == end_) return;
+
+ size_t size_ = static_cast<size_t>(_end - _begin);
+ size_t capacity = static_cast<size_t>(_eos - _begin);
+ size_t count = static_cast<size_t>(end_ - begin_);
+
+ if (size_ + count > capacity)
+ {
+ // reallocate the old array or allocate a new one
+ xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node)));
+ if (!data) return;
+
+ // finalize
+ _begin = data;
+ _end = data + size_;
+ _eos = data + size_ + count;
+ }
+
+ memcpy(_end, begin_, count * sizeof(xpath_node));
+ _end += count;
+ }
+
+ void sort_do()
+ {
+ _type = xpath_sort(_begin, _end, _type, false);
+ }
+
+ void truncate(xpath_node* pos)
+ {
+ assert(_begin <= pos && pos <= _end);
+
+ _end = pos;
+ }
+
+ void remove_duplicates(xpath_allocator* alloc)
+ {
+ if (_type == xpath_node_set::type_unsorted && _end - _begin > 2)
+ {
+ xpath_allocator_capture cr(alloc);
+
+ size_t size_ = static_cast<size_t>(_end - _begin);
+
+ size_t hash_size = 1;
+ while (hash_size < size_ + size_ / 2) hash_size *= 2;
+
+ const void** hash_data = static_cast<const void**>(alloc->allocate(hash_size * sizeof(void*)));
+ if (!hash_data) return;
+
+ memset(hash_data, 0, hash_size * sizeof(void*));
+
+ xpath_node* write = _begin;
+
+ for (xpath_node* it = _begin; it != _end; ++it)
+ {
+ const void* attr = it->attribute().internal_object();
+ const void* node = it->node().internal_object();
+ const void* key = attr ? attr : node;
+
+ if (key && hash_insert(hash_data, hash_size, key))
+ {
+ *write++ = *it;
+ }
+ }
+
+ _end = write;
+ }
+ else
+ {
+ _end = unique(_begin, _end);
+ }
+ }
+
+ xpath_node_set::type_t type() const
+ {
+ return _type;
+ }
+
+ void set_type(xpath_node_set::type_t value)
+ {
+ _type = value;
+ }
+ };
+
+ PUGI_IMPL_FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc)
+ {
+ size_t capacity = static_cast<size_t>(_eos - _begin);
+
+ // get new capacity (1.5x rule)
+ size_t new_capacity = capacity + capacity / 2 + 1;
+
+ // reallocate the old array or allocate a new one
+ xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node)));
+ if (!data) return;
+
+ // finalize
+ _begin = data;
+ _end = data + capacity;
+ _eos = data + new_capacity;
+
+ // push
+ *_end++ = node;
+ }
+PUGI_IMPL_NS_END
+
+PUGI_IMPL_NS_BEGIN
+ struct xpath_context
+ {
+ xpath_node n;
+ size_t position, size;
+
+ xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_)
+ {
+ }
+ };
+
+ enum lexeme_t
+ {
+ lex_none = 0,
+ lex_equal,
+ lex_not_equal,
+ lex_less,
+ lex_greater,
+ lex_less_or_equal,
+ lex_greater_or_equal,
+ lex_plus,
+ lex_minus,
+ lex_multiply,
+ lex_union,
+ lex_var_ref,
+ lex_open_brace,
+ lex_close_brace,
+ lex_quoted_string,
+ lex_number,
+ lex_slash,
+ lex_double_slash,
+ lex_open_square_brace,
+ lex_close_square_brace,
+ lex_string,
+ lex_comma,
+ lex_axis_attribute,
+ lex_dot,
+ lex_double_dot,
+ lex_double_colon,
+ lex_eof
+ };
+
+ struct xpath_lexer_string
+ {
+ const char_t* begin;
+ const char_t* end;
+
+ xpath_lexer_string(): begin(NULL), end(NULL)
+ {
+ }
+
+ bool operator==(const char_t* other) const
+ {
+ size_t length = static_cast<size_t>(end - begin);
+
+ return strequalrange(other, begin, length);
+ }
+ };
+
+ class xpath_lexer
+ {
+ const char_t* _cur;
+ const char_t* _cur_lexeme_pos;
+ xpath_lexer_string _cur_lexeme_contents;
+
+ lexeme_t _cur_lexeme;
+
+ public:
+ explicit xpath_lexer(const char_t* query): _cur(query)
+ {
+ next();
+ }
+
+ const char_t* state() const
+ {
+ return _cur;
+ }
+
+ void next()
+ {
+ const char_t* cur = _cur;
+
+ while (PUGI_IMPL_IS_CHARTYPE(*cur, ct_space)) ++cur;
+
+ // save lexeme position for error reporting
+ _cur_lexeme_pos = cur;
+
+ switch (*cur)
+ {
+ case 0:
+ _cur_lexeme = lex_eof;
+ break;
+
+ case '>':
+ if (*(cur+1) == '=')
+ {
+ cur += 2;
+ _cur_lexeme = lex_greater_or_equal;
+ }
+ else
+ {
+ cur += 1;
+ _cur_lexeme = lex_greater;
+ }
+ break;
+
+ case '<':
+ if (*(cur+1) == '=')
+ {
+ cur += 2;
+ _cur_lexeme = lex_less_or_equal;
+ }
+ else
+ {
+ cur += 1;
+ _cur_lexeme = lex_less;
+ }
+ break;
+
+ case '!':
+ if (*(cur+1) == '=')
+ {
+ cur += 2;
+ _cur_lexeme = lex_not_equal;
+ }
+ else
+ {
+ _cur_lexeme = lex_none;
+ }
+ break;
+
+ case '=':
+ cur += 1;
+ _cur_lexeme = lex_equal;
+
+ break;
+
+ case '+':
+ cur += 1;
+ _cur_lexeme = lex_plus;
+
+ break;
+
+ case '-':
+ cur += 1;
+ _cur_lexeme = lex_minus;
+
+ break;
+
+ case '*':
+ cur += 1;
+ _cur_lexeme = lex_multiply;
+
+ break;
+
+ case '|':
+ cur += 1;
+ _cur_lexeme = lex_union;
+
+ break;
+
+ case '$':
+ cur += 1;
+
+ if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol))
+ {
+ _cur_lexeme_contents.begin = cur;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+
+ if (cur[0] == ':' && PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // qname
+ {
+ cur++; // :
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+ }
+
+ _cur_lexeme_contents.end = cur;
+
+ _cur_lexeme = lex_var_ref;
+ }
+ else
+ {
+ _cur_lexeme = lex_none;
+ }
+
+ break;
+
+ case '(':
+ cur += 1;
+ _cur_lexeme = lex_open_brace;
+
+ break;
+
+ case ')':
+ cur += 1;
+ _cur_lexeme = lex_close_brace;
+
+ break;
+
+ case '[':
+ cur += 1;
+ _cur_lexeme = lex_open_square_brace;
+
+ break;
+
+ case ']':
+ cur += 1;
+ _cur_lexeme = lex_close_square_brace;
+
+ break;
+
+ case ',':
+ cur += 1;
+ _cur_lexeme = lex_comma;
+
+ break;
+
+ case '/':
+ if (*(cur+1) == '/')
+ {
+ cur += 2;
+ _cur_lexeme = lex_double_slash;
+ }
+ else
+ {
+ cur += 1;
+ _cur_lexeme = lex_slash;
+ }
+ break;
+
+ case '.':
+ if (*(cur+1) == '.')
+ {
+ cur += 2;
+ _cur_lexeme = lex_double_dot;
+ }
+ else if (PUGI_IMPL_IS_CHARTYPEX(*(cur+1), ctx_digit))
+ {
+ _cur_lexeme_contents.begin = cur; // .
+
+ ++cur;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+
+ _cur_lexeme_contents.end = cur;
+
+ _cur_lexeme = lex_number;
+ }
+ else
+ {
+ cur += 1;
+ _cur_lexeme = lex_dot;
+ }
+ break;
+
+ case '@':
+ cur += 1;
+ _cur_lexeme = lex_axis_attribute;
+
+ break;
+
+ case '"':
+ case '\'':
+ {
+ char_t terminator = *cur;
+
+ ++cur;
+
+ _cur_lexeme_contents.begin = cur;
+ while (*cur && *cur != terminator) cur++;
+ _cur_lexeme_contents.end = cur;
+
+ if (!*cur)
+ _cur_lexeme = lex_none;
+ else
+ {
+ cur += 1;
+ _cur_lexeme = lex_quoted_string;
+ }
+
+ break;
+ }
+
+ case ':':
+ if (*(cur+1) == ':')
+ {
+ cur += 2;
+ _cur_lexeme = lex_double_colon;
+ }
+ else
+ {
+ _cur_lexeme = lex_none;
+ }
+ break;
+
+ default:
+ if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit))
+ {
+ _cur_lexeme_contents.begin = cur;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+
+ if (*cur == '.')
+ {
+ cur++;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+ }
+
+ _cur_lexeme_contents.end = cur;
+
+ _cur_lexeme = lex_number;
+ }
+ else if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol))
+ {
+ _cur_lexeme_contents.begin = cur;
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+
+ if (cur[0] == ':')
+ {
+ if (cur[1] == '*') // namespace test ncname:*
+ {
+ cur += 2; // :*
+ }
+ else if (PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname
+ {
+ cur++; // :
+
+ while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+ }
+ }
+
+ _cur_lexeme_contents.end = cur;
+
+ _cur_lexeme = lex_string;
+ }
+ else
+ {
+ _cur_lexeme = lex_none;
+ }
+ }
+
+ _cur = cur;
+ }
+
+ lexeme_t current() const
+ {
+ return _cur_lexeme;
+ }
+
+ const char_t* current_pos() const
+ {
+ return _cur_lexeme_pos;
+ }
+
+ const xpath_lexer_string& contents() const
+ {
+ assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string);
+
+ return _cur_lexeme_contents;
+ }
+ };
+
+ enum ast_type_t
+ {
+ ast_unknown,
+ ast_op_or, // left or right
+ ast_op_and, // left and right
+ ast_op_equal, // left = right
+ ast_op_not_equal, // left != right
+ ast_op_less, // left < right
+ ast_op_greater, // left > right
+ ast_op_less_or_equal, // left <= right
+ ast_op_greater_or_equal, // left >= right
+ ast_op_add, // left + right
+ ast_op_subtract, // left - right
+ ast_op_multiply, // left * right
+ ast_op_divide, // left / right
+ ast_op_mod, // left % right
+ ast_op_negate, // left - right
+ ast_op_union, // left | right
+ ast_predicate, // apply predicate to set; next points to next predicate
+ ast_filter, // select * from left where right
+ ast_string_constant, // string constant
+ ast_number_constant, // number constant
+ ast_variable, // variable
+ ast_func_last, // last()
+ ast_func_position, // position()
+ ast_func_count, // count(left)
+ ast_func_id, // id(left)
+ ast_func_local_name_0, // local-name()
+ ast_func_local_name_1, // local-name(left)
+ ast_func_namespace_uri_0, // namespace-uri()
+ ast_func_namespace_uri_1, // namespace-uri(left)
+ ast_func_name_0, // name()
+ ast_func_name_1, // name(left)
+ ast_func_string_0, // string()
+ ast_func_string_1, // string(left)
+ ast_func_concat, // concat(left, right, siblings)
+ ast_func_starts_with, // starts_with(left, right)
+ ast_func_contains, // contains(left, right)
+ ast_func_substring_before, // substring-before(left, right)
+ ast_func_substring_after, // substring-after(left, right)
+ ast_func_substring_2, // substring(left, right)
+ ast_func_substring_3, // substring(left, right, third)
+ ast_func_string_length_0, // string-length()
+ ast_func_string_length_1, // string-length(left)
+ ast_func_normalize_space_0, // normalize-space()
+ ast_func_normalize_space_1, // normalize-space(left)
+ ast_func_translate, // translate(left, right, third)
+ ast_func_boolean, // boolean(left)
+ ast_func_not, // not(left)
+ ast_func_true, // true()
+ ast_func_false, // false()
+ ast_func_lang, // lang(left)
+ ast_func_number_0, // number()
+ ast_func_number_1, // number(left)
+ ast_func_sum, // sum(left)
+ ast_func_floor, // floor(left)
+ ast_func_ceiling, // ceiling(left)
+ ast_func_round, // round(left)
+ ast_step, // process set left with step
+ ast_step_root, // select root node
+
+ ast_opt_translate_table, // translate(left, right, third) where right/third are constants
+ ast_opt_select_attribute, // @name
+ ast_opt_compare_attribute // @name = 'string'
+ };
+
+ enum axis_t
+ {
+ axis_ancestor,
+ axis_ancestor_or_self,
+ axis_attribute,
+ axis_child,
+ axis_descendant,
+ axis_descendant_or_self,
+ axis_following,
+ axis_following_sibling,
+ axis_namespace,
+ axis_parent,
+ axis_preceding,
+ axis_preceding_sibling,
+ axis_self
+ };
+
+ enum nodetest_t
+ {
+ nodetest_none,
+ nodetest_name,
+ nodetest_type_node,
+ nodetest_type_comment,
+ nodetest_type_pi,
+ nodetest_type_text,
+ nodetest_pi,
+ nodetest_all,
+ nodetest_all_in_namespace
+ };
+
+ enum predicate_t
+ {
+ predicate_default,
+ predicate_posinv,
+ predicate_constant,
+ predicate_constant_one
+ };
+
+ enum nodeset_eval_t
+ {
+ nodeset_eval_all,
+ nodeset_eval_any,
+ nodeset_eval_first
+ };
+
+ template <axis_t N> struct axis_to_type
+ {
+ static const axis_t axis;
+ };
+
+ template <axis_t N> const axis_t axis_to_type<N>::axis = N;
+
+ class xpath_ast_node
+ {
+ private:
+ // node type
+ char _type;
+ char _rettype;
+
+ // for ast_step
+ char _axis;
+
+ // for ast_step/ast_predicate/ast_filter
+ char _test;
+
+ // tree node structure
+ xpath_ast_node* _left;
+ xpath_ast_node* _right;
+ xpath_ast_node* _next;
+
+ union
+ {
+ // value for ast_string_constant
+ const char_t* string;
+ // value for ast_number_constant
+ double number;
+ // variable for ast_variable
+ xpath_variable* variable;
+ // node test for ast_step (node name/namespace/node type/pi target)
+ const char_t* nodetest;
+ // table for ast_opt_translate_table
+ const unsigned char* table;
+ } _data;
+
+ xpath_ast_node(const xpath_ast_node&);
+ xpath_ast_node& operator=(const xpath_ast_node&);
+
+ template <class Comp> static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp)
+ {
+ xpath_value_type lt = lhs->rettype(), rt = rhs->rettype();
+
+ if (lt != xpath_type_node_set && rt != xpath_type_node_set)
+ {
+ if (lt == xpath_type_boolean || rt == xpath_type_boolean)
+ return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack));
+ else if (lt == xpath_type_number || rt == xpath_type_number)
+ return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack));
+ else if (lt == xpath_type_string || rt == xpath_type_string)
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_string ls = lhs->eval_string(c, stack);
+ xpath_string rs = rhs->eval_string(c, stack);
+
+ return comp(ls, rs);
+ }
+ }
+ else if (lt == xpath_type_node_set && rt == xpath_type_node_set)
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+ for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ if (comp(string_value(*li, stack.result), string_value(*ri, stack.result)))
+ return true;
+ }
+
+ return false;
+ }
+ else
+ {
+ if (lt == xpath_type_node_set)
+ {
+ swap(lhs, rhs);
+ swap(lt, rt);
+ }
+
+ if (lt == xpath_type_boolean)
+ return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack));
+ else if (lt == xpath_type_number)
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ double l = lhs->eval_number(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+ return true;
+ }
+
+ return false;
+ }
+ else if (lt == xpath_type_string)
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_string l = lhs->eval_string(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ if (comp(l, string_value(*ri, stack.result)))
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ assert(false && "Wrong types"); // unreachable
+ return false;
+ }
+
+ static bool eval_once(xpath_node_set::type_t type, nodeset_eval_t eval)
+ {
+ return type == xpath_node_set::type_sorted ? eval != nodeset_eval_all : eval == nodeset_eval_any;
+ }
+
+ template <class Comp> static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp)
+ {
+ xpath_value_type lt = lhs->rettype(), rt = rhs->rettype();
+
+ if (lt != xpath_type_node_set && rt != xpath_type_node_set)
+ return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack));
+ else if (lt == xpath_type_node_set && rt == xpath_type_node_set)
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ double l = convert_string_to_number(string_value(*li, stack.result).c_str());
+
+ for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+ {
+ xpath_allocator_capture crii(stack.result);
+
+ if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+ return true;
+ }
+ }
+
+ return false;
+ }
+ else if (lt != xpath_type_node_set && rt == xpath_type_node_set)
+ {
+ if (rhs->_type == ast_opt_select_attribute)
+ {
+ xml_attribute attr = c.n.node().attribute(rhs->_data.nodetest);
+
+ return attr && comp(lhs->eval_number(c, stack), convert_string_to_number(attr.value()));
+ }
+
+ xpath_allocator_capture cr(stack.result);
+
+ double l = lhs->eval_number(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+ return true;
+ }
+
+ return false;
+ }
+ else if (lt == xpath_type_node_set && rt != xpath_type_node_set)
+ {
+ if (lhs->_type == ast_opt_select_attribute)
+ {
+ xml_attribute attr = c.n.node().attribute(lhs->_data.nodetest);
+
+ return attr && comp(convert_string_to_number(attr.value()), rhs->eval_number(c, stack));
+ }
+
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
+ double r = rhs->eval_number(c, stack);
+
+ for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r))
+ return true;
+ }
+
+ return false;
+ }
+ else
+ {
+ assert(false && "Wrong types"); // unreachable
+ return false;
+ }
+ }
+
+ static void apply_predicate_boolean(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once)
+ {
+ assert(ns.size() >= first);
+ assert(expr->rettype() != xpath_type_number);
+
+ size_t i = 1;
+ size_t size = ns.size() - first;
+
+ xpath_node* last = ns.begin() + first;
+
+ // remove_if... or well, sort of
+ for (xpath_node* it = last; it != ns.end(); ++it, ++i)
+ {
+ xpath_context c(*it, i, size);
+
+ if (expr->eval_boolean(c, stack))
+ {
+ *last++ = *it;
+
+ if (once) break;
+ }
+ }
+
+ ns.truncate(last);
+ }
+
+ static void apply_predicate_number(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once)
+ {
+ assert(ns.size() >= first);
+ assert(expr->rettype() == xpath_type_number);
+
+ size_t i = 1;
+ size_t size = ns.size() - first;
+
+ xpath_node* last = ns.begin() + first;
+
+ // remove_if... or well, sort of
+ for (xpath_node* it = last; it != ns.end(); ++it, ++i)
+ {
+ xpath_context c(*it, i, size);
+
+ if (expr->eval_number(c, stack) == static_cast<double>(i))
+ {
+ *last++ = *it;
+
+ if (once) break;
+ }
+ }
+
+ ns.truncate(last);
+ }
+
+ static void apply_predicate_number_const(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack)
+ {
+ assert(ns.size() >= first);
+ assert(expr->rettype() == xpath_type_number);
+
+ size_t size = ns.size() - first;
+
+ xpath_node* last = ns.begin() + first;
+
+ xpath_node cn;
+ xpath_context c(cn, 1, size);
+
+ double er = expr->eval_number(c, stack);
+
+ if (er >= 1.0 && er <= static_cast<double>(size))
+ {
+ size_t eri = static_cast<size_t>(er);
+
+ if (er == static_cast<double>(eri))
+ {
+ xpath_node r = last[eri - 1];
+
+ *last++ = r;
+ }
+ }
+
+ ns.truncate(last);
+ }
+
+ void apply_predicate(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, bool once)
+ {
+ if (ns.size() == first) return;
+
+ assert(_type == ast_filter || _type == ast_predicate);
+
+ if (_test == predicate_constant || _test == predicate_constant_one)
+ apply_predicate_number_const(ns, first, _right, stack);
+ else if (_right->rettype() == xpath_type_number)
+ apply_predicate_number(ns, first, _right, stack, once);
+ else
+ apply_predicate_boolean(ns, first, _right, stack, once);
+ }
+
+ void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, nodeset_eval_t eval)
+ {
+ if (ns.size() == first) return;
+
+ bool last_once = eval_once(ns.type(), eval);
+
+ for (xpath_ast_node* pred = _right; pred; pred = pred->_next)
+ pred->apply_predicate(ns, first, stack, !pred->_next && last_once);
+ }
+
+ bool step_push(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* parent, xpath_allocator* alloc)
+ {
+ assert(a);
+
+ const char_t* name = a->name ? a->name + 0 : PUGIXML_TEXT("");
+
+ switch (_test)
+ {
+ // nodetest_name is handled at a higher level
+ case nodetest_type_node:
+ case nodetest_all:
+ if (is_xpath_attribute(name))
+ {
+ ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_all_in_namespace:
+ if (starts_with(name, _data.nodetest) && is_xpath_attribute(name))
+ {
+ ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc);
+ return true;
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ return false;
+ }
+
+ bool step_push(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc)
+ {
+ assert(n);
+
+ xml_node_type type = PUGI_IMPL_NODETYPE(n);
+
+ switch (_test)
+ {
+ case nodetest_name:
+ if (type == node_element && n->name && strequal(n->name, _data.nodetest))
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_type_node:
+ ns.push_back(xml_node(n), alloc);
+ return true;
+
+ case nodetest_type_comment:
+ if (type == node_comment)
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_type_text:
+ if (type == node_pcdata || type == node_cdata)
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_type_pi:
+ if (type == node_pi)
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_pi:
+ if (type == node_pi && n->name && strequal(n->name, _data.nodetest))
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_all:
+ if (type == node_element)
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ case nodetest_all_in_namespace:
+ if (type == node_element && n->name && starts_with(n->name, _data.nodetest))
+ {
+ ns.push_back(xml_node(n), alloc);
+ return true;
+ }
+ break;
+
+ default:
+ assert(false && "Unknown axis"); // unreachable
+ }
+
+ return false;
+ }
+
+ template <class T> void step_fill(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc, bool once, T)
+ {
+ const axis_t axis = T::axis;
+
+ switch (axis)
+ {
+ case axis_attribute:
+ {
+ if (_test == nodetest_name)
+ {
+ for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute)
+ if (a->name && strequal(a->name, _data.nodetest) && is_xpath_attribute(a->name))
+ {
+ ns.push_back(xpath_node(xml_attribute(a), xml_node(n)), alloc);
+ return; // once=true by construction
+ }
+ }
+ else
+ {
+ for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute)
+ if (step_push(ns, a, n, alloc) & once)
+ return;
+ }
+
+ break;
+ }
+
+ case axis_child:
+ {
+ if (_test == nodetest_name)
+ {
+ for (xml_node_struct* c = n->first_child; c; c = c->next_sibling)
+ if (c->name && strequal(c->name, _data.nodetest) && PUGI_IMPL_NODETYPE(c) == node_element)
+ {
+ ns.push_back(xml_node(c), alloc);
+ if (once) return;
+ }
+ }
+ else
+ {
+ for (xml_node_struct* c = n->first_child; c; c = c->next_sibling)
+ if (step_push(ns, c, alloc) & once)
+ return;
+ }
+
+ break;
+ }
+
+ case axis_descendant:
+ case axis_descendant_or_self:
+ {
+ if (axis == axis_descendant_or_self)
+ if (step_push(ns, n, alloc) & once)
+ return;
+
+ xml_node_struct* cur = n->first_child;
+
+ while (cur)
+ {
+ if (step_push(ns, cur, alloc) & once)
+ return;
+
+ if (cur->first_child)
+ cur = cur->first_child;
+ else
+ {
+ while (!cur->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (cur == n) return;
+ }
+
+ cur = cur->next_sibling;
+ }
+ }
+
+ break;
+ }
+
+ case axis_following_sibling:
+ {
+ for (xml_node_struct* c = n->next_sibling; c; c = c->next_sibling)
+ if (step_push(ns, c, alloc) & once)
+ return;
+
+ break;
+ }
+
+ case axis_preceding_sibling:
+ {
+ for (xml_node_struct* c = n->prev_sibling_c; c->next_sibling; c = c->prev_sibling_c)
+ if (step_push(ns, c, alloc) & once)
+ return;
+
+ break;
+ }
+
+ case axis_following:
+ {
+ xml_node_struct* cur = n;
+
+ // exit from this node so that we don't include descendants
+ while (!cur->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (!cur) return;
+ }
+
+ cur = cur->next_sibling;
+
+ while (cur)
+ {
+ if (step_push(ns, cur, alloc) & once)
+ return;
+
+ if (cur->first_child)
+ cur = cur->first_child;
+ else
+ {
+ while (!cur->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (!cur) return;
+ }
+
+ cur = cur->next_sibling;
+ }
+ }
+
+ break;
+ }
+
+ case axis_preceding:
+ {
+ xml_node_struct* cur = n;
+
+ // exit from this node so that we don't include descendants
+ while (!cur->prev_sibling_c->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (!cur) return;
+ }
+
+ cur = cur->prev_sibling_c;
+
+ while (cur)
+ {
+ if (cur->first_child)
+ cur = cur->first_child->prev_sibling_c;
+ else
+ {
+ // leaf node, can't be ancestor
+ if (step_push(ns, cur, alloc) & once)
+ return;
+
+ while (!cur->prev_sibling_c->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (!cur) return;
+
+ if (!node_is_ancestor(cur, n))
+ if (step_push(ns, cur, alloc) & once)
+ return;
+ }
+
+ cur = cur->prev_sibling_c;
+ }
+ }
+
+ break;
+ }
+
+ case axis_ancestor:
+ case axis_ancestor_or_self:
+ {
+ if (axis == axis_ancestor_or_self)
+ if (step_push(ns, n, alloc) & once)
+ return;
+
+ xml_node_struct* cur = n->parent;
+
+ while (cur)
+ {
+ if (step_push(ns, cur, alloc) & once)
+ return;
+
+ cur = cur->parent;
+ }
+
+ break;
+ }
+
+ case axis_self:
+ {
+ step_push(ns, n, alloc);
+
+ break;
+ }
+
+ case axis_parent:
+ {
+ if (n->parent)
+ step_push(ns, n->parent, alloc);
+
+ break;
+ }
+
+ default:
+ assert(false && "Unimplemented axis"); // unreachable
+ }
+ }
+
+ template <class T> void step_fill(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* p, xpath_allocator* alloc, bool once, T v)
+ {
+ const axis_t axis = T::axis;
+
+ switch (axis)
+ {
+ case axis_ancestor:
+ case axis_ancestor_or_self:
+ {
+ if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test
+ if (step_push(ns, a, p, alloc) & once)
+ return;
+
+ xml_node_struct* cur = p;
+
+ while (cur)
+ {
+ if (step_push(ns, cur, alloc) & once)
+ return;
+
+ cur = cur->parent;
+ }
+
+ break;
+ }
+
+ case axis_descendant_or_self:
+ case axis_self:
+ {
+ if (_test == nodetest_type_node) // reject attributes based on principal node type test
+ step_push(ns, a, p, alloc);
+
+ break;
+ }
+
+ case axis_following:
+ {
+ xml_node_struct* cur = p;
+
+ while (cur)
+ {
+ if (cur->first_child)
+ cur = cur->first_child;
+ else
+ {
+ while (!cur->next_sibling)
+ {
+ cur = cur->parent;
+
+ if (!cur) return;
+ }
+
+ cur = cur->next_sibling;
+ }
+
+ if (step_push(ns, cur, alloc) & once)
+ return;
+ }
+
+ break;
+ }
+
+ case axis_parent:
+ {
+ step_push(ns, p, alloc);
+
+ break;
+ }
+
+ case axis_preceding:
+ {
+ // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding
+ step_fill(ns, p, alloc, once, v);
+ break;
+ }
+
+ default:
+ assert(false && "Unimplemented axis"); // unreachable
+ }
+ }
+
+ template <class T> void step_fill(xpath_node_set_raw& ns, const xpath_node& xn, xpath_allocator* alloc, bool once, T v)
+ {
+ const axis_t axis = T::axis;
+ const bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self);
+
+ if (xn.node())
+ step_fill(ns, xn.node().internal_object(), alloc, once, v);
+ else if (axis_has_attributes && xn.attribute() && xn.parent())
+ step_fill(ns, xn.attribute().internal_object(), xn.parent().internal_object(), alloc, once, v);
+ }
+
+ template <class T> xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v)
+ {
+ const axis_t axis = T::axis;
+ const bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling);
+ const xpath_node_set::type_t axis_type = axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted;
+
+ bool once =
+ (axis == axis_attribute && _test == nodetest_name) ||
+ (!_right && eval_once(axis_type, eval)) ||
+ // coverity[mixed_enums]
+ (_right && !_right->_next && _right->_test == predicate_constant_one);
+
+ xpath_node_set_raw ns;
+ ns.set_type(axis_type);
+
+ if (_left)
+ {
+ xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all);
+
+ // self axis preserves the original order
+ if (axis == axis_self) ns.set_type(s.type());
+
+ for (const xpath_node* it = s.begin(); it != s.end(); ++it)
+ {
+ size_t size = ns.size();
+
+ // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes
+ if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted);
+
+ step_fill(ns, *it, stack.result, once, v);
+ if (_right) apply_predicates(ns, size, stack, eval);
+ }
+ }
+ else
+ {
+ step_fill(ns, c.n, stack.result, once, v);
+ if (_right) apply_predicates(ns, 0, stack, eval);
+ }
+
+ // child, attribute and self axes always generate unique set of nodes
+ // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice
+ if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted)
+ ns.remove_duplicates(stack.temp);
+
+ return ns;
+ }
+
+ public:
+ xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value):
+ _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL)
+ {
+ assert(type == ast_string_constant);
+ _data.string = value;
+ }
+
+ xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value):
+ _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL)
+ {
+ assert(type == ast_number_constant);
+ _data.number = value;
+ }
+
+ xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value):
+ _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL)
+ {
+ assert(type == ast_variable);
+ _data.variable = value;
+ }
+
+ xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = NULL, xpath_ast_node* right = NULL):
+ _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(NULL)
+ {
+ }
+
+ xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents):
+ _type(static_cast<char>(type)), _rettype(xpath_type_node_set), _axis(static_cast<char>(axis)), _test(static_cast<char>(test)), _left(left), _right(NULL), _next(NULL)
+ {
+ assert(type == ast_step);
+ _data.nodetest = contents;
+ }
+
+ xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test):
+ _type(static_cast<char>(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast<char>(test)), _left(left), _right(right), _next(NULL)
+ {
+ assert(type == ast_filter || type == ast_predicate);
+ }
+
+ void set_next(xpath_ast_node* value)
+ {
+ _next = value;
+ }
+
+ void set_right(xpath_ast_node* value)
+ {
+ _right = value;
+ }
+
+ bool eval_boolean(const xpath_context& c, const xpath_stack& stack)
+ {
+ switch (_type)
+ {
+ case ast_op_or:
+ return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack);
+
+ case ast_op_and:
+ return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack);
+
+ case ast_op_equal:
+ return compare_eq(_left, _right, c, stack, equal_to());
+
+ case ast_op_not_equal:
+ return compare_eq(_left, _right, c, stack, not_equal_to());
+
+ case ast_op_less:
+ return compare_rel(_left, _right, c, stack, less());
+
+ case ast_op_greater:
+ return compare_rel(_right, _left, c, stack, less());
+
+ case ast_op_less_or_equal:
+ return compare_rel(_left, _right, c, stack, less_equal());
+
+ case ast_op_greater_or_equal:
+ return compare_rel(_right, _left, c, stack, less_equal());
+
+ case ast_func_starts_with:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_string lr = _left->eval_string(c, stack);
+ xpath_string rr = _right->eval_string(c, stack);
+
+ return starts_with(lr.c_str(), rr.c_str());
+ }
+
+ case ast_func_contains:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_string lr = _left->eval_string(c, stack);
+ xpath_string rr = _right->eval_string(c, stack);
+
+ return find_substring(lr.c_str(), rr.c_str()) != NULL;
+ }
+
+ case ast_func_boolean:
+ return _left->eval_boolean(c, stack);
+
+ case ast_func_not:
+ return !_left->eval_boolean(c, stack);
+
+ case ast_func_true:
+ return true;
+
+ case ast_func_false:
+ return false;
+
+ case ast_func_lang:
+ {
+ if (c.n.attribute()) return false;
+
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_string lang = _left->eval_string(c, stack);
+
+ for (xml_node n = c.n.node(); n; n = n.parent())
+ {
+ xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang"));
+
+ if (a)
+ {
+ const char_t* value = a.value();
+
+ // strnicmp / strncasecmp is not portable
+ for (const char_t* lit = lang.c_str(); *lit; ++lit)
+ {
+ if (tolower_ascii(*lit) != tolower_ascii(*value)) return false;
+ ++value;
+ }
+
+ return *value == 0 || *value == '-';
+ }
+ }
+
+ return false;
+ }
+
+ case ast_opt_select_attribute:
+ {
+ xml_attribute attr = c.n.node().attribute(_data.nodetest);
+
+ return !attr.empty();
+ }
+
+ case ast_opt_compare_attribute:
+ {
+ const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string();
+
+ xml_attribute attr = c.n.node().attribute(_left->_data.nodetest);
+
+ return attr && strequal(attr.value(), value);
+ }
+
+ case ast_variable:
+ {
+ assert(_rettype == _data.variable->type());
+
+ if (_rettype == xpath_type_boolean)
+ return _data.variable->get_boolean();
+
+ // variable needs to be converted to the correct type, this is handled by the fallthrough block below
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ // none of the ast types that return the value directly matched, we need to perform type conversion
+ switch (_rettype)
+ {
+ case xpath_type_number:
+ return convert_number_to_boolean(eval_number(c, stack));
+
+ case xpath_type_string:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return !eval_string(c, stack).empty();
+ }
+
+ case xpath_type_node_set:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return !eval_node_set(c, stack, nodeset_eval_any).empty();
+ }
+
+ default:
+ assert(false && "Wrong expression for return type boolean"); // unreachable
+ return false;
+ }
+ }
+
+ double eval_number(const xpath_context& c, const xpath_stack& stack)
+ {
+ switch (_type)
+ {
+ case ast_op_add:
+ return _left->eval_number(c, stack) + _right->eval_number(c, stack);
+
+ case ast_op_subtract:
+ return _left->eval_number(c, stack) - _right->eval_number(c, stack);
+
+ case ast_op_multiply:
+ return _left->eval_number(c, stack) * _right->eval_number(c, stack);
+
+ case ast_op_divide:
+ return _left->eval_number(c, stack) / _right->eval_number(c, stack);
+
+ case ast_op_mod:
+ return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack));
+
+ case ast_op_negate:
+ return -_left->eval_number(c, stack);
+
+ case ast_number_constant:
+ return _data.number;
+
+ case ast_func_last:
+ return static_cast<double>(c.size);
+
+ case ast_func_position:
+ return static_cast<double>(c.position);
+
+ case ast_func_count:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return static_cast<double>(_left->eval_node_set(c, stack, nodeset_eval_all).size());
+ }
+
+ case ast_func_string_length_0:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return static_cast<double>(string_value(c.n, stack.result).length());
+ }
+
+ case ast_func_string_length_1:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return static_cast<double>(_left->eval_string(c, stack).length());
+ }
+
+ case ast_func_number_0:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return convert_string_to_number(string_value(c.n, stack.result).c_str());
+ }
+
+ case ast_func_number_1:
+ return _left->eval_number(c, stack);
+
+ case ast_func_sum:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ double r = 0;
+
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all);
+
+ for (const xpath_node* it = ns.begin(); it != ns.end(); ++it)
+ {
+ xpath_allocator_capture cri(stack.result);
+
+ r += convert_string_to_number(string_value(*it, stack.result).c_str());
+ }
+
+ return r;
+ }
+
+ case ast_func_floor:
+ {
+ double r = _left->eval_number(c, stack);
+
+ return r == r ? floor(r) : r;
+ }
+
+ case ast_func_ceiling:
+ {
+ double r = _left->eval_number(c, stack);
+
+ return r == r ? ceil(r) : r;
+ }
+
+ case ast_func_round:
+ return round_nearest_nzero(_left->eval_number(c, stack));
+
+ case ast_opt_select_attribute:
+ {
+ xml_attribute attr = c.n.node().attribute(_data.nodetest);
+
+ return attr ? convert_string_to_number(attr.value()) : gen_nan();
+ }
+
+ case ast_variable:
+ {
+ assert(_rettype == _data.variable->type());
+
+ if (_rettype == xpath_type_number)
+ return _data.variable->get_number();
+
+ // variable needs to be converted to the correct type, this is handled by the fallthrough block below
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ // none of the ast types that return the value directly matched, we need to perform type conversion
+ switch (_rettype)
+ {
+ case xpath_type_boolean:
+ return eval_boolean(c, stack) ? 1 : 0;
+
+ case xpath_type_string:
+ case xpath_type_node_set: // implicit conversion to string
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ return convert_string_to_number(eval_string(c, stack).c_str());
+ }
+
+ default:
+ assert(false && "Wrong expression for return type number"); // unreachable
+ return 0;
+ }
+ }
+
+ xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack)
+ {
+ assert(_type == ast_func_concat);
+
+ xpath_allocator_capture ct(stack.temp);
+
+ // count the string number
+ size_t count = 1;
+ for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++;
+
+ // allocate a buffer for temporary string objects
+ xpath_string* buffer = static_cast<xpath_string*>(stack.temp->allocate(count * sizeof(xpath_string)));
+ if (!buffer) return xpath_string();
+
+ // evaluate all strings to temporary stack
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ buffer[0] = _left->eval_string(c, swapped_stack);
+
+ size_t pos = 1;
+ for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack);
+ assert(pos == count);
+
+ // get total length
+ size_t length = 0;
+ for (size_t i = 0; i < count; ++i) length += buffer[i].length();
+
+ // create final string
+ char_t* result = static_cast<char_t*>(stack.result->allocate((length + 1) * sizeof(char_t)));
+ if (!result) return xpath_string();
+
+ char_t* ri = result;
+
+ for (size_t j = 0; j < count; ++j)
+ for (const char_t* bi = buffer[j].c_str(); *bi; ++bi)
+ *ri++ = *bi;
+
+ *ri = 0;
+
+ return xpath_string::from_heap_preallocated(result, ri);
+ }
+
+ xpath_string eval_string(const xpath_context& c, const xpath_stack& stack)
+ {
+ switch (_type)
+ {
+ case ast_string_constant:
+ return xpath_string::from_const(_data.string);
+
+ case ast_func_local_name_0:
+ {
+ xpath_node na = c.n;
+
+ return xpath_string::from_const(local_name(na));
+ }
+
+ case ast_func_local_name_1:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
+ xpath_node na = ns.first();
+
+ return xpath_string::from_const(local_name(na));
+ }
+
+ case ast_func_name_0:
+ {
+ xpath_node na = c.n;
+
+ return xpath_string::from_const(qualified_name(na));
+ }
+
+ case ast_func_name_1:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
+ xpath_node na = ns.first();
+
+ return xpath_string::from_const(qualified_name(na));
+ }
+
+ case ast_func_namespace_uri_0:
+ {
+ xpath_node na = c.n;
+
+ return xpath_string::from_const(namespace_uri(na));
+ }
+
+ case ast_func_namespace_uri_1:
+ {
+ xpath_allocator_capture cr(stack.result);
+
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
+ xpath_node na = ns.first();
+
+ return xpath_string::from_const(namespace_uri(na));
+ }
+
+ case ast_func_string_0:
+ return string_value(c.n, stack.result);
+
+ case ast_func_string_1:
+ return _left->eval_string(c, stack);
+
+ case ast_func_concat:
+ return eval_string_concat(c, stack);
+
+ case ast_func_substring_before:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_string s = _left->eval_string(c, swapped_stack);
+ xpath_string p = _right->eval_string(c, swapped_stack);
+
+ const char_t* pos = find_substring(s.c_str(), p.c_str());
+
+ return pos ? xpath_string::from_heap(s.c_str(), pos, stack.result) : xpath_string();
+ }
+
+ case ast_func_substring_after:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_string s = _left->eval_string(c, swapped_stack);
+ xpath_string p = _right->eval_string(c, swapped_stack);
+
+ const char_t* pos = find_substring(s.c_str(), p.c_str());
+ if (!pos) return xpath_string();
+
+ const char_t* rbegin = pos + p.length();
+ const char_t* rend = s.c_str() + s.length();
+
+ return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin);
+ }
+
+ case ast_func_substring_2:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_string s = _left->eval_string(c, swapped_stack);
+ size_t s_length = s.length();
+
+ double first = round_nearest(_right->eval_number(c, stack));
+
+ if (is_nan(first)) return xpath_string(); // NaN
+ else if (first >= static_cast<double>(s_length + 1)) return xpath_string();
+
+ size_t pos = first < 1 ? 1 : static_cast<size_t>(first);
+ assert(1 <= pos && pos <= s_length + 1);
+
+ const char_t* rbegin = s.c_str() + (pos - 1);
+ const char_t* rend = s.c_str() + s.length();
+
+ return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin);
+ }
+
+ case ast_func_substring_3:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_string s = _left->eval_string(c, swapped_stack);
+ size_t s_length = s.length();
+
+ double first = round_nearest(_right->eval_number(c, stack));
+ double last = first + round_nearest(_right->_next->eval_number(c, stack));
+
+ if (is_nan(first) || is_nan(last)) return xpath_string();
+ else if (first >= static_cast<double>(s_length + 1)) return xpath_string();
+ else if (first >= last) return xpath_string();
+ else if (last < 1) return xpath_string();
+
+ size_t pos = first < 1 ? 1 : static_cast<size_t>(first);
+ size_t end = last >= static_cast<double>(s_length + 1) ? s_length + 1 : static_cast<size_t>(last);
+
+ assert(1 <= pos && pos <= end && end <= s_length + 1);
+ const char_t* rbegin = s.c_str() + (pos - 1);
+ const char_t* rend = s.c_str() + (end - 1);
+
+ return (end == s_length + 1 && !s.uses_heap()) ? xpath_string::from_const(rbegin) : xpath_string::from_heap(rbegin, rend, stack.result);
+ }
+
+ case ast_func_normalize_space_0:
+ {
+ xpath_string s = string_value(c.n, stack.result);
+
+ char_t* begin = s.data(stack.result);
+ if (!begin) return xpath_string();
+
+ char_t* end = normalize_space(begin);
+
+ return xpath_string::from_heap_preallocated(begin, end);
+ }
+
+ case ast_func_normalize_space_1:
+ {
+ xpath_string s = _left->eval_string(c, stack);
+
+ char_t* begin = s.data(stack.result);
+ if (!begin) return xpath_string();
+
+ char_t* end = normalize_space(begin);
+
+ return xpath_string::from_heap_preallocated(begin, end);
+ }
+
+ case ast_func_translate:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_string s = _left->eval_string(c, stack);
+ xpath_string from = _right->eval_string(c, swapped_stack);
+ xpath_string to = _right->_next->eval_string(c, swapped_stack);
+
+ char_t* begin = s.data(stack.result);
+ if (!begin) return xpath_string();
+
+ char_t* end = translate(begin, from.c_str(), to.c_str(), to.length());
+
+ return xpath_string::from_heap_preallocated(begin, end);
+ }
+
+ case ast_opt_translate_table:
+ {
+ xpath_string s = _left->eval_string(c, stack);
+
+ char_t* begin = s.data(stack.result);
+ if (!begin) return xpath_string();
+
+ char_t* end = translate_table(begin, _data.table);
+
+ return xpath_string::from_heap_preallocated(begin, end);
+ }
+
+ case ast_opt_select_attribute:
+ {
+ xml_attribute attr = c.n.node().attribute(_data.nodetest);
+
+ return attr ? xpath_string::from_const(attr.value()) : xpath_string();
+ }
+
+ case ast_variable:
+ {
+ assert(_rettype == _data.variable->type());
+
+ if (_rettype == xpath_type_string)
+ return xpath_string::from_const(_data.variable->get_string());
+
+ // variable needs to be converted to the correct type, this is handled by the fallthrough block below
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ // none of the ast types that return the value directly matched, we need to perform type conversion
+ switch (_rettype)
+ {
+ case xpath_type_boolean:
+ return xpath_string::from_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"));
+
+ case xpath_type_number:
+ return convert_number_to_string(eval_number(c, stack), stack.result);
+
+ case xpath_type_node_set:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first);
+ return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result);
+ }
+
+ default:
+ assert(false && "Wrong expression for return type string"); // unreachable
+ return xpath_string();
+ }
+ }
+
+ xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval)
+ {
+ switch (_type)
+ {
+ case ast_op_union:
+ {
+ xpath_allocator_capture cr(stack.temp);
+
+ xpath_stack swapped_stack = {stack.temp, stack.result};
+
+ xpath_node_set_raw ls = _left->eval_node_set(c, stack, eval);
+ xpath_node_set_raw rs = _right->eval_node_set(c, swapped_stack, eval);
+
+ // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother
+ ls.set_type(xpath_node_set::type_unsorted);
+
+ ls.append(rs.begin(), rs.end(), stack.result);
+ ls.remove_duplicates(stack.temp);
+
+ return ls;
+ }
+
+ case ast_filter:
+ {
+ xpath_node_set_raw set = _left->eval_node_set(c, stack, _test == predicate_constant_one ? nodeset_eval_first : nodeset_eval_all);
+
+ // either expression is a number or it contains position() call; sort by document order
+ if (_test != predicate_posinv) set.sort_do();
+
+ bool once = eval_once(set.type(), eval);
+
+ apply_predicate(set, 0, stack, once);
+
+ return set;
+ }
+
+ case ast_func_id:
+ return xpath_node_set_raw();
+
+ case ast_step:
+ {
+ switch (_axis)
+ {
+ case axis_ancestor:
+ return step_do(c, stack, eval, axis_to_type<axis_ancestor>());
+
+ case axis_ancestor_or_self:
+ return step_do(c, stack, eval, axis_to_type<axis_ancestor_or_self>());
+
+ case axis_attribute:
+ return step_do(c, stack, eval, axis_to_type<axis_attribute>());
+
+ case axis_child:
+ return step_do(c, stack, eval, axis_to_type<axis_child>());
+
+ case axis_descendant:
+ return step_do(c, stack, eval, axis_to_type<axis_descendant>());
+
+ case axis_descendant_or_self:
+ return step_do(c, stack, eval, axis_to_type<axis_descendant_or_self>());
+
+ case axis_following:
+ return step_do(c, stack, eval, axis_to_type<axis_following>());
+
+ case axis_following_sibling:
+ return step_do(c, stack, eval, axis_to_type<axis_following_sibling>());
+
+ case axis_namespace:
+ // namespaced axis is not supported
+ return xpath_node_set_raw();
+
+ case axis_parent:
+ return step_do(c, stack, eval, axis_to_type<axis_parent>());
+
+ case axis_preceding:
+ return step_do(c, stack, eval, axis_to_type<axis_preceding>());
+
+ case axis_preceding_sibling:
+ return step_do(c, stack, eval, axis_to_type<axis_preceding_sibling>());
+
+ case axis_self:
+ return step_do(c, stack, eval, axis_to_type<axis_self>());
+
+ default:
+ assert(false && "Unknown axis"); // unreachable
+ return xpath_node_set_raw();
+ }
+ }
+
+ case ast_step_root:
+ {
+ assert(!_right); // root step can't have any predicates
+
+ xpath_node_set_raw ns;
+ ns.set_type(xpath_node_set::type_sorted);
+
+ if (c.n.node()) ns.push_back(c.n.node().root(), stack.result);
+ else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result);
+
+ return ns;
+ }
+
+ case ast_opt_select_attribute:
+ {
+ xpath_node_set_raw ns;
+ ns.set_type(xpath_node_set::type_sorted);
+
+ xml_attribute attr = c.n.node().attribute(_data.nodetest);
+ if (attr) ns.push_back(xpath_node(attr, c.n.node()), stack.result);
+
+ return ns;
+ }
+
+ case ast_variable:
+ {
+ assert(_rettype == _data.variable->type());
+
+ if (_rettype == xpath_type_node_set)
+ {
+ const xpath_node_set& s = _data.variable->get_node_set();
+
+ xpath_node_set_raw ns;
+
+ ns.set_type(s.type());
+ ns.append(s.begin(), s.end(), stack.result);
+
+ return ns;
+ }
+
+ // variable needs to be converted to the correct type, this is handled by the fallthrough block below
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ // none of the ast types that return the value directly matched, but conversions to node set are invalid
+ assert(false && "Wrong expression for return type node set"); // unreachable
+ return xpath_node_set_raw();
+ }
+
+ void optimize(xpath_allocator* alloc)
+ {
+ if (_left)
+ _left->optimize(alloc);
+
+ if (_right)
+ _right->optimize(alloc);
+
+ if (_next)
+ _next->optimize(alloc);
+
+ // coverity[var_deref_model]
+ optimize_self(alloc);
+ }
+
+ void optimize_self(xpath_allocator* alloc)
+ {
+ // Rewrite [position()=expr] with [expr]
+ // Note that this step has to go before classification to recognize [position()=1]
+ if ((_type == ast_filter || _type == ast_predicate) &&
+ _right && // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate)
+ _right->_type == ast_op_equal && _right->_left->_type == ast_func_position && _right->_right->_rettype == xpath_type_number)
+ {
+ _right = _right->_right;
+ }
+
+ // Classify filter/predicate ops to perform various optimizations during evaluation
+ if ((_type == ast_filter || _type == ast_predicate) && _right) // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate)
+ {
+ assert(_test == predicate_default);
+
+ if (_right->_type == ast_number_constant && _right->_data.number == 1.0)
+ _test = predicate_constant_one;
+ else if (_right->_rettype == xpath_type_number && (_right->_type == ast_number_constant || _right->_type == ast_variable || _right->_type == ast_func_last))
+ _test = predicate_constant;
+ else if (_right->_rettype != xpath_type_number && _right->is_posinv_expr())
+ _test = predicate_posinv;
+ }
+
+ // Rewrite descendant-or-self::node()/child::foo with descendant::foo
+ // The former is a full form of //foo, the latter is much faster since it executes the node test immediately
+ // Do a similar kind of rewrite for self/descendant/descendant-or-self axes
+ // Note that we only rewrite positionally invariant steps (//foo[1] != /descendant::foo[1])
+ if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) &&
+ _left && _left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right &&
+ is_posinv_step())
+ {
+ if (_axis == axis_child || _axis == axis_descendant)
+ _axis = axis_descendant;
+ else
+ _axis = axis_descendant_or_self;
+
+ _left = _left->_left;
+ }
+
+ // Use optimized lookup table implementation for translate() with constant arguments
+ if (_type == ast_func_translate &&
+ _right && // workaround for clang static analyzer (_right is never null for ast_func_translate)
+ _right->_type == ast_string_constant && _right->_next->_type == ast_string_constant)
+ {
+ unsigned char* table = translate_table_generate(alloc, _right->_data.string, _right->_next->_data.string);
+
+ if (table)
+ {
+ _type = ast_opt_translate_table;
+ _data.table = table;
+ }
+ }
+
+ // Use optimized path for simple attribute selection (@attr)
+ // coverity[mixed_enums]
+ if (_type == ast_step && _axis == axis_attribute && _test == nodetest_name && !_left && !_right && is_xpath_attribute(_data.nodetest))
+ {
+ _type = ast_opt_select_attribute;
+ }
+
+ // Use optimized path for @attr = 'value' or @attr = $value (relies on attribute selection optimization for matching)
+ if (_type == ast_op_equal &&
+ _left && _right && // workaround for clang static analyzer and Coverity (_left and _right are never null for ast_op_equal)
+ _left->_type == ast_opt_select_attribute &&
+ (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->_rettype == xpath_type_string)))
+ {
+ _type = ast_opt_compare_attribute;
+ }
+ }
+
+ bool is_posinv_expr() const
+ {
+ switch (_type)
+ {
+ case ast_func_position:
+ case ast_func_last:
+ return false;
+
+ case ast_string_constant:
+ case ast_number_constant:
+ case ast_variable:
+ return true;
+
+ case ast_step:
+ case ast_step_root:
+ return true;
+
+ case ast_predicate:
+ case ast_filter:
+ return true;
+
+ default:
+ if (_left && !_left->is_posinv_expr()) return false;
+
+ for (xpath_ast_node* n = _right; n; n = n->_next)
+ if (!n->is_posinv_expr()) return false;
+
+ return true;
+ }
+ }
+
+ bool is_posinv_step() const
+ {
+ assert(_type == ast_step);
+
+ for (xpath_ast_node* n = _right; n; n = n->_next)
+ {
+ assert(n->_type == ast_predicate);
+
+ if (n->_test != predicate_posinv)
+ return false;
+ }
+
+ return true;
+ }
+
+ xpath_value_type rettype() const
+ {
+ return static_cast<xpath_value_type>(_rettype);
+ }
+ };
+
+ static const size_t xpath_ast_depth_limit =
+ #ifdef PUGIXML_XPATH_DEPTH_LIMIT
+ PUGIXML_XPATH_DEPTH_LIMIT
+ #else
+ 1024
+ #endif
+ ;
+
+ struct xpath_parser
+ {
+ xpath_allocator* _alloc;
+ xpath_lexer _lexer;
+
+ const char_t* _query;
+ xpath_variable_set* _variables;
+
+ xpath_parse_result* _result;
+
+ char_t _scratch[32];
+
+ size_t _depth;
+
+ xpath_ast_node* error(const char* message)
+ {
+ _result->error = message;
+ _result->offset = _lexer.current_pos() - _query;
+
+ return NULL;
+ }
+
+ xpath_ast_node* error_oom()
+ {
+ assert(_alloc->_error);
+ *_alloc->_error = true;
+
+ return NULL;
+ }
+
+ xpath_ast_node* error_rec()
+ {
+ return error("Exceeded maximum allowed query depth");
+ }
+
+ void* alloc_node()
+ {
+ return _alloc->allocate(sizeof(xpath_ast_node));
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, const char_t* value)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL;
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, double value)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL;
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_variable* value)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL;
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_ast_node* left = NULL, xpath_ast_node* right = NULL)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, rettype, left, right) : NULL;
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, left, axis, test, contents) : NULL;
+ }
+
+ xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test)
+ {
+ void* memory = alloc_node();
+ return memory ? new (memory) xpath_ast_node(type, left, right, test) : NULL;
+ }
+
+ const char_t* alloc_string(const xpath_lexer_string& value)
+ {
+ if (!value.begin)
+ return PUGIXML_TEXT("");
+
+ size_t length = static_cast<size_t>(value.end - value.begin);
+
+ char_t* c = static_cast<char_t*>(_alloc->allocate((length + 1) * sizeof(char_t)));
+ if (!c) return NULL;
+
+ memcpy(c, value.begin, length * sizeof(char_t));
+ c[length] = 0;
+
+ return c;
+ }
+
+ xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2])
+ {
+ switch (name.begin[0])
+ {
+ case 'b':
+ if (name == PUGIXML_TEXT("boolean") && argc == 1)
+ return alloc_node(ast_func_boolean, xpath_type_boolean, args[0]);
+
+ break;
+
+ case 'c':
+ if (name == PUGIXML_TEXT("count") && argc == 1)
+ {
+ if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set");
+ return alloc_node(ast_func_count, xpath_type_number, args[0]);
+ }
+ else if (name == PUGIXML_TEXT("contains") && argc == 2)
+ return alloc_node(ast_func_contains, xpath_type_boolean, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("concat") && argc >= 2)
+ return alloc_node(ast_func_concat, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("ceiling") && argc == 1)
+ return alloc_node(ast_func_ceiling, xpath_type_number, args[0]);
+
+ break;
+
+ case 'f':
+ if (name == PUGIXML_TEXT("false") && argc == 0)
+ return alloc_node(ast_func_false, xpath_type_boolean);
+ else if (name == PUGIXML_TEXT("floor") && argc == 1)
+ return alloc_node(ast_func_floor, xpath_type_number, args[0]);
+
+ break;
+
+ case 'i':
+ if (name == PUGIXML_TEXT("id") && argc == 1)
+ return alloc_node(ast_func_id, xpath_type_node_set, args[0]);
+
+ break;
+
+ case 'l':
+ if (name == PUGIXML_TEXT("last") && argc == 0)
+ return alloc_node(ast_func_last, xpath_type_number);
+ else if (name == PUGIXML_TEXT("lang") && argc == 1)
+ return alloc_node(ast_func_lang, xpath_type_boolean, args[0]);
+ else if (name == PUGIXML_TEXT("local-name") && argc <= 1)
+ {
+ if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set");
+ return alloc_node(argc == 0 ? ast_func_local_name_0 : ast_func_local_name_1, xpath_type_string, args[0]);
+ }
+
+ break;
+
+ case 'n':
+ if (name == PUGIXML_TEXT("name") && argc <= 1)
+ {
+ if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set");
+ return alloc_node(argc == 0 ? ast_func_name_0 : ast_func_name_1, xpath_type_string, args[0]);
+ }
+ else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1)
+ {
+ if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set");
+ return alloc_node(argc == 0 ? ast_func_namespace_uri_0 : ast_func_namespace_uri_1, xpath_type_string, args[0]);
+ }
+ else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1)
+ return alloc_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("not") && argc == 1)
+ return alloc_node(ast_func_not, xpath_type_boolean, args[0]);
+ else if (name == PUGIXML_TEXT("number") && argc <= 1)
+ return alloc_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]);
+
+ break;
+
+ case 'p':
+ if (name == PUGIXML_TEXT("position") && argc == 0)
+ return alloc_node(ast_func_position, xpath_type_number);
+
+ break;
+
+ case 'r':
+ if (name == PUGIXML_TEXT("round") && argc == 1)
+ return alloc_node(ast_func_round, xpath_type_number, args[0]);
+
+ break;
+
+ case 's':
+ if (name == PUGIXML_TEXT("string") && argc <= 1)
+ return alloc_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]);
+ else if (name == PUGIXML_TEXT("string-length") && argc <= 1)
+ return alloc_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]);
+ else if (name == PUGIXML_TEXT("starts-with") && argc == 2)
+ return alloc_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("substring-before") && argc == 2)
+ return alloc_node(ast_func_substring_before, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("substring-after") && argc == 2)
+ return alloc_node(ast_func_substring_after, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3))
+ return alloc_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("sum") && argc == 1)
+ {
+ if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set");
+ return alloc_node(ast_func_sum, xpath_type_number, args[0]);
+ }
+
+ break;
+
+ case 't':
+ if (name == PUGIXML_TEXT("translate") && argc == 3)
+ return alloc_node(ast_func_translate, xpath_type_string, args[0], args[1]);
+ else if (name == PUGIXML_TEXT("true") && argc == 0)
+ return alloc_node(ast_func_true, xpath_type_boolean);
+
+ break;
+
+ default:
+ break;
+ }
+
+ return error("Unrecognized function or wrong parameter count");
+ }
+
+ axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified)
+ {
+ specified = true;
+
+ switch (name.begin[0])
+ {
+ case 'a':
+ if (name == PUGIXML_TEXT("ancestor"))
+ return axis_ancestor;
+ else if (name == PUGIXML_TEXT("ancestor-or-self"))
+ return axis_ancestor_or_self;
+ else if (name == PUGIXML_TEXT("attribute"))
+ return axis_attribute;
+
+ break;
+
+ case 'c':
+ if (name == PUGIXML_TEXT("child"))
+ return axis_child;
+
+ break;
+
+ case 'd':
+ if (name == PUGIXML_TEXT("descendant"))
+ return axis_descendant;
+ else if (name == PUGIXML_TEXT("descendant-or-self"))
+ return axis_descendant_or_self;
+
+ break;
+
+ case 'f':
+ if (name == PUGIXML_TEXT("following"))
+ return axis_following;
+ else if (name == PUGIXML_TEXT("following-sibling"))
+ return axis_following_sibling;
+
+ break;
+
+ case 'n':
+ if (name == PUGIXML_TEXT("namespace"))
+ return axis_namespace;
+
+ break;
+
+ case 'p':
+ if (name == PUGIXML_TEXT("parent"))
+ return axis_parent;
+ else if (name == PUGIXML_TEXT("preceding"))
+ return axis_preceding;
+ else if (name == PUGIXML_TEXT("preceding-sibling"))
+ return axis_preceding_sibling;
+
+ break;
+
+ case 's':
+ if (name == PUGIXML_TEXT("self"))
+ return axis_self;
+
+ break;
+
+ default:
+ break;
+ }
+
+ specified = false;
+ return axis_child;
+ }
+
+ nodetest_t parse_node_test_type(const xpath_lexer_string& name)
+ {
+ switch (name.begin[0])
+ {
+ case 'c':
+ if (name == PUGIXML_TEXT("comment"))
+ return nodetest_type_comment;
+
+ break;
+
+ case 'n':
+ if (name == PUGIXML_TEXT("node"))
+ return nodetest_type_node;
+
+ break;
+
+ case 'p':
+ if (name == PUGIXML_TEXT("processing-instruction"))
+ return nodetest_type_pi;
+
+ break;
+
+ case 't':
+ if (name == PUGIXML_TEXT("text"))
+ return nodetest_type_text;
+
+ break;
+
+ default:
+ break;
+ }
+
+ return nodetest_none;
+ }
+
+ // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
+ xpath_ast_node* parse_primary_expression()
+ {
+ switch (_lexer.current())
+ {
+ case lex_var_ref:
+ {
+ xpath_lexer_string name = _lexer.contents();
+
+ if (!_variables)
+ return error("Unknown variable: variable set is not provided");
+
+ xpath_variable* var = NULL;
+ if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var))
+ return error_oom();
+
+ if (!var)
+ return error("Unknown variable: variable set does not contain the given name");
+
+ _lexer.next();
+
+ return alloc_node(ast_variable, var->type(), var);
+ }
+
+ case lex_open_brace:
+ {
+ _lexer.next();
+
+ xpath_ast_node* n = parse_expression();
+ if (!n) return NULL;
+
+ if (_lexer.current() != lex_close_brace)
+ return error("Expected ')' to match an opening '('");
+
+ _lexer.next();
+
+ return n;
+ }
+
+ case lex_quoted_string:
+ {
+ const char_t* value = alloc_string(_lexer.contents());
+ if (!value) return NULL;
+
+ _lexer.next();
+
+ return alloc_node(ast_string_constant, xpath_type_string, value);
+ }
+
+ case lex_number:
+ {
+ double value = 0;
+
+ if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value))
+ return error_oom();
+
+ _lexer.next();
+
+ return alloc_node(ast_number_constant, xpath_type_number, value);
+ }
+
+ case lex_string:
+ {
+ xpath_ast_node* args[2] = {NULL};
+ size_t argc = 0;
+
+ xpath_lexer_string function = _lexer.contents();
+ _lexer.next();
+
+ xpath_ast_node* last_arg = NULL;
+
+ if (_lexer.current() != lex_open_brace)
+ return error("Unrecognized function call");
+ _lexer.next();
+
+ size_t old_depth = _depth;
+
+ while (_lexer.current() != lex_close_brace)
+ {
+ if (argc > 0)
+ {
+ if (_lexer.current() != lex_comma)
+ return error("No comma between function arguments");
+ _lexer.next();
+ }
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ xpath_ast_node* n = parse_expression();
+ if (!n) return NULL;
+
+ if (argc < 2) args[argc] = n;
+ else last_arg->set_next(n);
+
+ argc++;
+ last_arg = n;
+ }
+
+ _lexer.next();
+
+ _depth = old_depth;
+
+ return parse_function(function, argc, args);
+ }
+
+ default:
+ return error("Unrecognizable primary expression");
+ }
+ }
+
+ // FilterExpr ::= PrimaryExpr | FilterExpr Predicate
+ // Predicate ::= '[' PredicateExpr ']'
+ // PredicateExpr ::= Expr
+ xpath_ast_node* parse_filter_expression()
+ {
+ xpath_ast_node* n = parse_primary_expression();
+ if (!n) return NULL;
+
+ size_t old_depth = _depth;
+
+ while (_lexer.current() == lex_open_square_brace)
+ {
+ _lexer.next();
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ if (n->rettype() != xpath_type_node_set)
+ return error("Predicate has to be applied to node set");
+
+ xpath_ast_node* expr = parse_expression();
+ if (!expr) return NULL;
+
+ n = alloc_node(ast_filter, n, expr, predicate_default);
+ if (!n) return NULL;
+
+ if (_lexer.current() != lex_close_square_brace)
+ return error("Expected ']' to match an opening '['");
+
+ _lexer.next();
+ }
+
+ _depth = old_depth;
+
+ return n;
+ }
+
+ // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
+ // AxisSpecifier ::= AxisName '::' | '@'?
+ // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
+ // NameTest ::= '*' | NCName ':' '*' | QName
+ // AbbreviatedStep ::= '.' | '..'
+ xpath_ast_node* parse_step(xpath_ast_node* set)
+ {
+ if (set && set->rettype() != xpath_type_node_set)
+ return error("Step has to be applied to node set");
+
+ bool axis_specified = false;
+ axis_t axis = axis_child; // implied child axis
+
+ if (_lexer.current() == lex_axis_attribute)
+ {
+ axis = axis_attribute;
+ axis_specified = true;
+
+ _lexer.next();
+ }
+ else if (_lexer.current() == lex_dot)
+ {
+ _lexer.next();
+
+ if (_lexer.current() == lex_open_square_brace)
+ return error("Predicates are not allowed after an abbreviated step");
+
+ return alloc_node(ast_step, set, axis_self, nodetest_type_node, NULL);
+ }
+ else if (_lexer.current() == lex_double_dot)
+ {
+ _lexer.next();
+
+ if (_lexer.current() == lex_open_square_brace)
+ return error("Predicates are not allowed after an abbreviated step");
+
+ return alloc_node(ast_step, set, axis_parent, nodetest_type_node, NULL);
+ }
+
+ nodetest_t nt_type = nodetest_none;
+ xpath_lexer_string nt_name;
+
+ if (_lexer.current() == lex_string)
+ {
+ // node name test
+ nt_name = _lexer.contents();
+ _lexer.next();
+
+ // was it an axis name?
+ if (_lexer.current() == lex_double_colon)
+ {
+ // parse axis name
+ if (axis_specified)
+ return error("Two axis specifiers in one step");
+
+ axis = parse_axis_name(nt_name, axis_specified);
+
+ if (!axis_specified)
+ return error("Unknown axis");
+
+ // read actual node test
+ _lexer.next();
+
+ if (_lexer.current() == lex_multiply)
+ {
+ nt_type = nodetest_all;
+ nt_name = xpath_lexer_string();
+ _lexer.next();
+ }
+ else if (_lexer.current() == lex_string)
+ {
+ nt_name = _lexer.contents();
+ _lexer.next();
+ }
+ else
+ {
+ return error("Unrecognized node test");
+ }
+ }
+
+ if (nt_type == nodetest_none)
+ {
+ // node type test or processing-instruction
+ if (_lexer.current() == lex_open_brace)
+ {
+ _lexer.next();
+
+ if (_lexer.current() == lex_close_brace)
+ {
+ _lexer.next();
+
+ nt_type = parse_node_test_type(nt_name);
+
+ if (nt_type == nodetest_none)
+ return error("Unrecognized node type");
+
+ nt_name = xpath_lexer_string();
+ }
+ else if (nt_name == PUGIXML_TEXT("processing-instruction"))
+ {
+ if (_lexer.current() != lex_quoted_string)
+ return error("Only literals are allowed as arguments to processing-instruction()");
+
+ nt_type = nodetest_pi;
+ nt_name = _lexer.contents();
+ _lexer.next();
+
+ if (_lexer.current() != lex_close_brace)
+ return error("Unmatched brace near processing-instruction()");
+ _lexer.next();
+ }
+ else
+ {
+ return error("Unmatched brace near node type test");
+ }
+ }
+ // QName or NCName:*
+ else
+ {
+ if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:*
+ {
+ nt_name.end--; // erase *
+
+ nt_type = nodetest_all_in_namespace;
+ }
+ else
+ {
+ nt_type = nodetest_name;
+ }
+ }
+ }
+ }
+ else if (_lexer.current() == lex_multiply)
+ {
+ nt_type = nodetest_all;
+ _lexer.next();
+ }
+ else
+ {
+ return error("Unrecognized node test");
+ }
+
+ const char_t* nt_name_copy = alloc_string(nt_name);
+ if (!nt_name_copy) return NULL;
+
+ xpath_ast_node* n = alloc_node(ast_step, set, axis, nt_type, nt_name_copy);
+ if (!n) return NULL;
+
+ size_t old_depth = _depth;
+
+ xpath_ast_node* last = NULL;
+
+ while (_lexer.current() == lex_open_square_brace)
+ {
+ _lexer.next();
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ xpath_ast_node* expr = parse_expression();
+ if (!expr) return NULL;
+
+ xpath_ast_node* pred = alloc_node(ast_predicate, NULL, expr, predicate_default);
+ if (!pred) return NULL;
+
+ if (_lexer.current() != lex_close_square_brace)
+ return error("Expected ']' to match an opening '['");
+ _lexer.next();
+
+ if (last) last->set_next(pred);
+ else n->set_right(pred);
+
+ last = pred;
+ }
+
+ _depth = old_depth;
+
+ return n;
+ }
+
+ // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step
+ xpath_ast_node* parse_relative_location_path(xpath_ast_node* set)
+ {
+ xpath_ast_node* n = parse_step(set);
+ if (!n) return NULL;
+
+ size_t old_depth = _depth;
+
+ while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash)
+ {
+ lexeme_t l = _lexer.current();
+ _lexer.next();
+
+ if (l == lex_double_slash)
+ {
+ n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL);
+ if (!n) return NULL;
+
+ ++_depth;
+ }
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ n = parse_step(n);
+ if (!n) return NULL;
+ }
+
+ _depth = old_depth;
+
+ return n;
+ }
+
+ // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath
+ // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath
+ xpath_ast_node* parse_location_path()
+ {
+ if (_lexer.current() == lex_slash)
+ {
+ _lexer.next();
+
+ xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set);
+ if (!n) return NULL;
+
+ // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path
+ lexeme_t l = _lexer.current();
+
+ if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply)
+ return parse_relative_location_path(n);
+ else
+ return n;
+ }
+ else if (_lexer.current() == lex_double_slash)
+ {
+ _lexer.next();
+
+ xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set);
+ if (!n) return NULL;
+
+ n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL);
+ if (!n) return NULL;
+
+ return parse_relative_location_path(n);
+ }
+
+ // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1
+ return parse_relative_location_path(NULL);
+ }
+
+ // PathExpr ::= LocationPath
+ // | FilterExpr
+ // | FilterExpr '/' RelativeLocationPath
+ // | FilterExpr '//' RelativeLocationPath
+ // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
+ // UnaryExpr ::= UnionExpr | '-' UnaryExpr
+ xpath_ast_node* parse_path_or_unary_expression()
+ {
+ // Clarification.
+ // PathExpr begins with either LocationPath or FilterExpr.
+ // FilterExpr begins with PrimaryExpr
+ // PrimaryExpr begins with '$' in case of it being a variable reference,
+ // '(' in case of it being an expression, string literal, number constant or
+ // function call.
+ if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace ||
+ _lexer.current() == lex_quoted_string || _lexer.current() == lex_number ||
+ _lexer.current() == lex_string)
+ {
+ if (_lexer.current() == lex_string)
+ {
+ // This is either a function call, or not - if not, we shall proceed with location path
+ const char_t* state = _lexer.state();
+
+ while (PUGI_IMPL_IS_CHARTYPE(*state, ct_space)) ++state;
+
+ if (*state != '(')
+ return parse_location_path();
+
+ // This looks like a function call; however this still can be a node-test. Check it.
+ if (parse_node_test_type(_lexer.contents()) != nodetest_none)
+ return parse_location_path();
+ }
+
+ xpath_ast_node* n = parse_filter_expression();
+ if (!n) return NULL;
+
+ if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash)
+ {
+ lexeme_t l = _lexer.current();
+ _lexer.next();
+
+ if (l == lex_double_slash)
+ {
+ if (n->rettype() != xpath_type_node_set)
+ return error("Step has to be applied to node set");
+
+ n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL);
+ if (!n) return NULL;
+ }
+
+ // select from location path
+ return parse_relative_location_path(n);
+ }
+
+ return n;
+ }
+ else if (_lexer.current() == lex_minus)
+ {
+ _lexer.next();
+
+ // precedence 7+ - only parses union expressions
+ xpath_ast_node* n = parse_expression(7);
+ if (!n) return NULL;
+
+ return alloc_node(ast_op_negate, xpath_type_number, n);
+ }
+ else
+ {
+ return parse_location_path();
+ }
+ }
+
+ struct binary_op_t
+ {
+ ast_type_t asttype;
+ xpath_value_type rettype;
+ int precedence;
+
+ binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0)
+ {
+ }
+
+ binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_)
+ {
+ }
+
+ static binary_op_t parse(xpath_lexer& lexer)
+ {
+ switch (lexer.current())
+ {
+ case lex_string:
+ if (lexer.contents() == PUGIXML_TEXT("or"))
+ return binary_op_t(ast_op_or, xpath_type_boolean, 1);
+ else if (lexer.contents() == PUGIXML_TEXT("and"))
+ return binary_op_t(ast_op_and, xpath_type_boolean, 2);
+ else if (lexer.contents() == PUGIXML_TEXT("div"))
+ return binary_op_t(ast_op_divide, xpath_type_number, 6);
+ else if (lexer.contents() == PUGIXML_TEXT("mod"))
+ return binary_op_t(ast_op_mod, xpath_type_number, 6);
+ else
+ return binary_op_t();
+
+ case lex_equal:
+ return binary_op_t(ast_op_equal, xpath_type_boolean, 3);
+
+ case lex_not_equal:
+ return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3);
+
+ case lex_less:
+ return binary_op_t(ast_op_less, xpath_type_boolean, 4);
+
+ case lex_greater:
+ return binary_op_t(ast_op_greater, xpath_type_boolean, 4);
+
+ case lex_less_or_equal:
+ return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4);
+
+ case lex_greater_or_equal:
+ return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4);
+
+ case lex_plus:
+ return binary_op_t(ast_op_add, xpath_type_number, 5);
+
+ case lex_minus:
+ return binary_op_t(ast_op_subtract, xpath_type_number, 5);
+
+ case lex_multiply:
+ return binary_op_t(ast_op_multiply, xpath_type_number, 6);
+
+ case lex_union:
+ return binary_op_t(ast_op_union, xpath_type_node_set, 7);
+
+ default:
+ return binary_op_t();
+ }
+ }
+ };
+
+ xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit)
+ {
+ binary_op_t op = binary_op_t::parse(_lexer);
+
+ while (op.asttype != ast_unknown && op.precedence >= limit)
+ {
+ _lexer.next();
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ xpath_ast_node* rhs = parse_path_or_unary_expression();
+ if (!rhs) return NULL;
+
+ binary_op_t nextop = binary_op_t::parse(_lexer);
+
+ while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence)
+ {
+ rhs = parse_expression_rec(rhs, nextop.precedence);
+ if (!rhs) return NULL;
+
+ nextop = binary_op_t::parse(_lexer);
+ }
+
+ if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set))
+ return error("Union operator has to be applied to node sets");
+
+ lhs = alloc_node(op.asttype, op.rettype, lhs, rhs);
+ if (!lhs) return NULL;
+
+ op = binary_op_t::parse(_lexer);
+ }
+
+ return lhs;
+ }
+
+ // Expr ::= OrExpr
+ // OrExpr ::= AndExpr | OrExpr 'or' AndExpr
+ // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
+ // EqualityExpr ::= RelationalExpr
+ // | EqualityExpr '=' RelationalExpr
+ // | EqualityExpr '!=' RelationalExpr
+ // RelationalExpr ::= AdditiveExpr
+ // | RelationalExpr '<' AdditiveExpr
+ // | RelationalExpr '>' AdditiveExpr
+ // | RelationalExpr '<=' AdditiveExpr
+ // | RelationalExpr '>=' AdditiveExpr
+ // AdditiveExpr ::= MultiplicativeExpr
+ // | AdditiveExpr '+' MultiplicativeExpr
+ // | AdditiveExpr '-' MultiplicativeExpr
+ // MultiplicativeExpr ::= UnaryExpr
+ // | MultiplicativeExpr '*' UnaryExpr
+ // | MultiplicativeExpr 'div' UnaryExpr
+ // | MultiplicativeExpr 'mod' UnaryExpr
+ xpath_ast_node* parse_expression(int limit = 0)
+ {
+ size_t old_depth = _depth;
+
+ if (++_depth > xpath_ast_depth_limit)
+ return error_rec();
+
+ xpath_ast_node* n = parse_path_or_unary_expression();
+ if (!n) return NULL;
+
+ n = parse_expression_rec(n, limit);
+
+ _depth = old_depth;
+
+ return n;
+ }
+
+ xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result), _depth(0)
+ {
+ }
+
+ xpath_ast_node* parse()
+ {
+ xpath_ast_node* n = parse_expression();
+ if (!n) return NULL;
+
+ assert(_depth == 0);
+
+ // check if there are unparsed tokens left
+ if (_lexer.current() != lex_eof)
+ return error("Incorrect query");
+
+ return n;
+ }
+
+ static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result)
+ {
+ xpath_parser parser(query, variables, alloc, result);
+
+ return parser.parse();
+ }
+ };
+
+ struct xpath_query_impl
+ {
+ static xpath_query_impl* create()
+ {
+ void* memory = xml_memory::allocate(sizeof(xpath_query_impl));
+ if (!memory) return NULL;
+
+ return new (memory) xpath_query_impl();
+ }
+
+ static void destroy(xpath_query_impl* impl)
+ {
+ // free all allocated pages
+ impl->alloc.release();
+
+ // free allocator memory (with the first page)
+ xml_memory::deallocate(impl);
+ }
+
+ xpath_query_impl(): root(NULL), alloc(&block, &oom), oom(false)
+ {
+ block.next = NULL;
+ block.capacity = sizeof(block.data);
+ }
+
+ xpath_ast_node* root;
+ xpath_allocator alloc;
+ xpath_memory_block block;
+ bool oom;
+ };
+
+ PUGI_IMPL_FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl)
+ {
+ if (!impl) return NULL;
+
+ if (impl->root->rettype() != xpath_type_node_set)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return 0;
+ #else
+ xpath_parse_result res;
+ res.error = "Expression does not evaluate to node set";
+
+ throw xpath_exception(res);
+ #endif
+ }
+
+ return impl->root;
+ }
+PUGI_IMPL_NS_END
+
+namespace pugi
+{
+#ifndef PUGIXML_NO_EXCEPTIONS
+ PUGI_IMPL_FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_)
+ {
+ assert(_result.error);
+ }
+
+ PUGI_IMPL_FN const char* xpath_exception::what() const PUGIXML_NOEXCEPT
+ {
+ return _result.error;
+ }
+
+ PUGI_IMPL_FN const xpath_parse_result& xpath_exception::result() const
+ {
+ return _result;
+ }
+#endif
+
+ PUGI_IMPL_FN xpath_node::xpath_node()
+ {
+ }
+
+ PUGI_IMPL_FN xpath_node::xpath_node(const xml_node& node_): _node(node_)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_)
+ {
+ }
+
+ PUGI_IMPL_FN xml_node xpath_node::node() const
+ {
+ return _attribute ? xml_node() : _node;
+ }
+
+ PUGI_IMPL_FN xml_attribute xpath_node::attribute() const
+ {
+ return _attribute;
+ }
+
+ PUGI_IMPL_FN xml_node xpath_node::parent() const
+ {
+ return _attribute ? _node : _node.parent();
+ }
+
+ PUGI_IMPL_FN static void unspecified_bool_xpath_node(xpath_node***)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_node::operator xpath_node::unspecified_bool_type() const
+ {
+ return (_node || _attribute) ? unspecified_bool_xpath_node : NULL;
+ }
+
+ PUGI_IMPL_FN bool xpath_node::operator!() const
+ {
+ return !(_node || _attribute);
+ }
+
+ PUGI_IMPL_FN bool xpath_node::operator==(const xpath_node& n) const
+ {
+ return _node == n._node && _attribute == n._attribute;
+ }
+
+ PUGI_IMPL_FN bool xpath_node::operator!=(const xpath_node& n) const
+ {
+ return _node != n._node || _attribute != n._attribute;
+ }
+
+#ifdef __BORLANDC__
+ PUGI_IMPL_FN bool operator&&(const xpath_node& lhs, bool rhs)
+ {
+ return (bool)lhs && rhs;
+ }
+
+ PUGI_IMPL_FN bool operator||(const xpath_node& lhs, bool rhs)
+ {
+ return (bool)lhs || rhs;
+ }
+#endif
+
+ PUGI_IMPL_FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_)
+ {
+ assert(begin_ <= end_);
+
+ size_t size_ = static_cast<size_t>(end_ - begin_);
+
+ // use internal buffer for 0 or 1 elements, heap buffer otherwise
+ xpath_node* storage = (size_ <= 1) ? _storage : static_cast<xpath_node*>(impl::xml_memory::allocate(size_ * sizeof(xpath_node)));
+
+ if (!storage)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return;
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ // deallocate old buffer
+ if (_begin != _storage)
+ impl::xml_memory::deallocate(_begin);
+
+ // size check is necessary because for begin_ = end_ = nullptr, memcpy is UB
+ if (size_)
+ memcpy(storage, begin_, size_ * sizeof(xpath_node));
+
+ _begin = storage;
+ _end = storage + size_;
+ _type = type_;
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN void xpath_node_set::_move(xpath_node_set& rhs) PUGIXML_NOEXCEPT
+ {
+ _type = rhs._type;
+ _storage[0] = rhs._storage[0];
+ _begin = (rhs._begin == rhs._storage) ? _storage : rhs._begin;
+ _end = _begin + (rhs._end - rhs._begin);
+
+ rhs._type = type_unsorted;
+ rhs._begin = rhs._storage;
+ rhs._end = rhs._storage;
+ }
+#endif
+
+ PUGI_IMPL_FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(_storage), _end(_storage)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(_storage), _end(_storage)
+ {
+ _assign(begin_, end_, type_);
+ }
+
+ PUGI_IMPL_FN xpath_node_set::~xpath_node_set()
+ {
+ if (_begin != _storage)
+ impl::xml_memory::deallocate(_begin);
+ }
+
+ PUGI_IMPL_FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage)
+ {
+ _assign(ns._begin, ns._end, ns._type);
+ }
+
+ PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns)
+ {
+ if (this == &ns) return *this;
+
+ _assign(ns._begin, ns._end, ns._type);
+
+ return *this;
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT: _type(type_unsorted), _begin(_storage), _end(_storage)
+ {
+ _move(rhs);
+ }
+
+ PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT
+ {
+ if (this == &rhs) return *this;
+
+ if (_begin != _storage)
+ impl::xml_memory::deallocate(_begin);
+
+ _move(rhs);
+
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN xpath_node_set::type_t xpath_node_set::type() const
+ {
+ return _type;
+ }
+
+ PUGI_IMPL_FN size_t xpath_node_set::size() const
+ {
+ return _end - _begin;
+ }
+
+ PUGI_IMPL_FN bool xpath_node_set::empty() const
+ {
+ return _begin == _end;
+ }
+
+ PUGI_IMPL_FN const xpath_node& xpath_node_set::operator[](size_t index) const
+ {
+ assert(index < size());
+ return _begin[index];
+ }
+
+ PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::begin() const
+ {
+ return _begin;
+ }
+
+ PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::end() const
+ {
+ return _end;
+ }
+
+ PUGI_IMPL_FN void xpath_node_set::sort(bool reverse)
+ {
+ _type = impl::xpath_sort(_begin, _end, _type, reverse);
+ }
+
+ PUGI_IMPL_FN xpath_node xpath_node_set::first() const
+ {
+ return impl::xpath_first(_begin, _end, _type);
+ }
+
+ PUGI_IMPL_FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_parse_result::operator bool() const
+ {
+ return error == NULL;
+ }
+
+ PUGI_IMPL_FN const char* xpath_parse_result::description() const
+ {
+ return error ? error : "No error";
+ }
+
+ PUGI_IMPL_FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN const char_t* xpath_variable::name() const
+ {
+ switch (_type)
+ {
+ case xpath_type_node_set:
+ return static_cast<const impl::xpath_variable_node_set*>(this)->name;
+
+ case xpath_type_number:
+ return static_cast<const impl::xpath_variable_number*>(this)->name;
+
+ case xpath_type_string:
+ return static_cast<const impl::xpath_variable_string*>(this)->name;
+
+ case xpath_type_boolean:
+ return static_cast<const impl::xpath_variable_boolean*>(this)->name;
+
+ default:
+ assert(false && "Invalid variable type"); // unreachable
+ return NULL;
+ }
+ }
+
+ PUGI_IMPL_FN xpath_value_type xpath_variable::type() const
+ {
+ return _type;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable::get_boolean() const
+ {
+ return (_type == xpath_type_boolean) ? static_cast<const impl::xpath_variable_boolean*>(this)->value : false;
+ }
+
+ PUGI_IMPL_FN double xpath_variable::get_number() const
+ {
+ return (_type == xpath_type_number) ? static_cast<const impl::xpath_variable_number*>(this)->value : impl::gen_nan();
+ }
+
+ PUGI_IMPL_FN const char_t* xpath_variable::get_string() const
+ {
+ const char_t* value = (_type == xpath_type_string) ? static_cast<const impl::xpath_variable_string*>(this)->value : NULL;
+ return value ? value : PUGIXML_TEXT("");
+ }
+
+ PUGI_IMPL_FN const xpath_node_set& xpath_variable::get_node_set() const
+ {
+ if (_type == xpath_type_node_set)
+ return static_cast<const impl::xpath_variable_node_set*>(this)->value;
+ static const xpath_node_set dummy_node_set;
+ return dummy_node_set;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable::set(bool value)
+ {
+ if (_type != xpath_type_boolean) return false;
+
+ static_cast<impl::xpath_variable_boolean*>(this)->value = value;
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable::set(double value)
+ {
+ if (_type != xpath_type_number) return false;
+
+ static_cast<impl::xpath_variable_number*>(this)->value = value;
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable::set(const char_t* value)
+ {
+ if (_type != xpath_type_string) return false;
+
+ impl::xpath_variable_string* var = static_cast<impl::xpath_variable_string*>(this);
+
+ // duplicate string
+ size_t size = (impl::strlength(value) + 1) * sizeof(char_t);
+
+ char_t* copy = static_cast<char_t*>(impl::xml_memory::allocate(size));
+ if (!copy) return false;
+
+ memcpy(copy, value, size);
+
+ // replace old string
+ if (var->value) impl::xml_memory::deallocate(var->value);
+ var->value = copy;
+
+ return true;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable::set(const xpath_node_set& value)
+ {
+ if (_type != xpath_type_node_set) return false;
+
+ static_cast<impl::xpath_variable_node_set*>(this)->value = value;
+ return true;
+ }
+
+ PUGI_IMPL_FN xpath_variable_set::xpath_variable_set()
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ _data[i] = NULL;
+ }
+
+ PUGI_IMPL_FN xpath_variable_set::~xpath_variable_set()
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ _destroy(_data[i]);
+ }
+
+ PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs)
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ _data[i] = NULL;
+
+ _assign(rhs);
+ }
+
+ PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs)
+ {
+ if (this == &rhs) return *this;
+
+ _assign(rhs);
+
+ return *this;
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ {
+ _data[i] = rhs._data[i];
+ rhs._data[i] = NULL;
+ }
+ }
+
+ PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ {
+ _destroy(_data[i]);
+
+ _data[i] = rhs._data[i];
+ rhs._data[i] = NULL;
+ }
+
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN void xpath_variable_set::_assign(const xpath_variable_set& rhs)
+ {
+ xpath_variable_set temp;
+
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ if (rhs._data[i] && !_clone(rhs._data[i], &temp._data[i]))
+ return;
+
+ _swap(temp);
+ }
+
+ PUGI_IMPL_FN void xpath_variable_set::_swap(xpath_variable_set& rhs)
+ {
+ for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+ {
+ xpath_variable* chain = _data[i];
+
+ _data[i] = rhs._data[i];
+ rhs._data[i] = chain;
+ }
+ }
+
+ PUGI_IMPL_FN xpath_variable* xpath_variable_set::_find(const char_t* name) const
+ {
+ const size_t hash_size = sizeof(_data) / sizeof(_data[0]);
+ size_t hash = impl::hash_string(name) % hash_size;
+
+ // look for existing variable
+ for (xpath_variable* var = _data[hash]; var; var = var->_next)
+ {
+ const char_t* vn = var->name();
+ if (vn && impl::strequal(vn, name))
+ return var;
+ }
+
+ return NULL;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result)
+ {
+ xpath_variable* last = NULL;
+
+ while (var)
+ {
+ // allocate storage for new variable
+ xpath_variable* nvar = impl::new_xpath_variable(var->_type, var->name());
+ if (!nvar) return false;
+
+ // link the variable to the result immediately to handle failures gracefully
+ if (last)
+ last->_next = nvar;
+ else
+ *out_result = nvar;
+
+ last = nvar;
+
+ // copy the value; this can fail due to out-of-memory conditions
+ if (!impl::copy_xpath_variable(nvar, var)) return false;
+
+ var = var->_next;
+ }
+
+ return true;
+ }
+
+ PUGI_IMPL_FN void xpath_variable_set::_destroy(xpath_variable* var)
+ {
+ while (var)
+ {
+ xpath_variable* next = var->_next;
+
+ impl::delete_xpath_variable(var->_type, var);
+
+ var = next;
+ }
+ }
+
+ PUGI_IMPL_FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type)
+ {
+ const size_t hash_size = sizeof(_data) / sizeof(_data[0]);
+ size_t hash = impl::hash_string(name) % hash_size;
+
+ // look for existing variable
+ for (xpath_variable* var = _data[hash]; var; var = var->_next)
+ {
+ const char_t* vn = var->name();
+ if (vn && impl::strequal(vn, name))
+ return var->type() == type ? var : NULL;
+ }
+
+ // add new variable
+ xpath_variable* result = impl::new_xpath_variable(type, name);
+
+ if (result)
+ {
+ result->_next = _data[hash];
+
+ _data[hash] = result;
+ }
+
+ return result;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, bool value)
+ {
+ xpath_variable* var = add(name, xpath_type_boolean);
+ return var ? var->set(value) : false;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, double value)
+ {
+ xpath_variable* var = add(name, xpath_type_number);
+ return var ? var->set(value) : false;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const char_t* value)
+ {
+ xpath_variable* var = add(name, xpath_type_string);
+ return var ? var->set(value) : false;
+ }
+
+ PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value)
+ {
+ xpath_variable* var = add(name, xpath_type_node_set);
+ return var ? var->set(value) : false;
+ }
+
+ PUGI_IMPL_FN xpath_variable* xpath_variable_set::get(const char_t* name)
+ {
+ return _find(name);
+ }
+
+ PUGI_IMPL_FN const xpath_variable* xpath_variable_set::get(const char_t* name) const
+ {
+ return _find(name);
+ }
+
+ PUGI_IMPL_FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(NULL)
+ {
+ impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create();
+
+ if (!qimpl)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ _result.error = "Out of memory";
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+ else
+ {
+ using impl::auto_deleter; // MSVC7 workaround
+ auto_deleter<impl::xpath_query_impl> impl(qimpl, impl::xpath_query_impl::destroy);
+
+ qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result);
+
+ if (qimpl->root)
+ {
+ qimpl->root->optimize(&qimpl->alloc);
+
+ _impl = impl.release();
+ _result.error = NULL;
+ }
+ else
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ if (qimpl->oom) _result.error = "Out of memory";
+ #else
+ if (qimpl->oom) throw std::bad_alloc();
+ throw xpath_exception(_result);
+ #endif
+ }
+ }
+ }
+
+ PUGI_IMPL_FN xpath_query::xpath_query(): _impl(NULL)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_query::~xpath_query()
+ {
+ if (_impl)
+ impl::xpath_query_impl::destroy(static_cast<impl::xpath_query_impl*>(_impl));
+ }
+
+#ifdef PUGIXML_HAS_MOVE
+ PUGI_IMPL_FN xpath_query::xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT
+ {
+ _impl = rhs._impl;
+ _result = rhs._result;
+ rhs._impl = NULL;
+ rhs._result = xpath_parse_result();
+ }
+
+ PUGI_IMPL_FN xpath_query& xpath_query::operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT
+ {
+ if (this == &rhs) return *this;
+
+ if (_impl)
+ impl::xpath_query_impl::destroy(static_cast<impl::xpath_query_impl*>(_impl));
+
+ _impl = rhs._impl;
+ _result = rhs._result;
+ rhs._impl = NULL;
+ rhs._result = xpath_parse_result();
+
+ return *this;
+ }
+#endif
+
+ PUGI_IMPL_FN xpath_value_type xpath_query::return_type() const
+ {
+ if (!_impl) return xpath_type_none;
+
+ return static_cast<impl::xpath_query_impl*>(_impl)->root->rettype();
+ }
+
+ PUGI_IMPL_FN bool xpath_query::evaluate_boolean(const xpath_node& n) const
+ {
+ if (!_impl) return false;
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ bool r = static_cast<impl::xpath_query_impl*>(_impl)->root->eval_boolean(c, sd.stack);
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return false;
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ return r;
+ }
+
+ PUGI_IMPL_FN double xpath_query::evaluate_number(const xpath_node& n) const
+ {
+ if (!_impl) return impl::gen_nan();
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ double r = static_cast<impl::xpath_query_impl*>(_impl)->root->eval_number(c, sd.stack);
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return impl::gen_nan();
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ return r;
+ }
+
+#ifndef PUGIXML_NO_STL
+ PUGI_IMPL_FN string_t xpath_query::evaluate_string(const xpath_node& n) const
+ {
+ if (!_impl) return string_t();
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ impl::xpath_string r = static_cast<impl::xpath_query_impl*>(_impl)->root->eval_string(c, sd.stack);
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return string_t();
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ return string_t(r.c_str(), r.length());
+ }
+#endif
+
+ PUGI_IMPL_FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const
+ {
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ impl::xpath_string r = _impl ? static_cast<impl::xpath_query_impl*>(_impl)->root->eval_string(c, sd.stack) : impl::xpath_string();
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ r = impl::xpath_string();
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ size_t full_size = r.length() + 1;
+
+ if (capacity > 0)
+ {
+ size_t size = (full_size < capacity) ? full_size : capacity;
+ assert(size > 0);
+
+ memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t));
+ buffer[size - 1] = 0;
+ }
+
+ return full_size;
+ }
+
+ PUGI_IMPL_FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const
+ {
+ impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast<impl::xpath_query_impl*>(_impl));
+ if (!root) return xpath_node_set();
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all);
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return xpath_node_set();
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ return xpath_node_set(r.begin(), r.end(), r.type());
+ }
+
+ PUGI_IMPL_FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const
+ {
+ impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast<impl::xpath_query_impl*>(_impl));
+ if (!root) return xpath_node();
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first);
+
+ if (sd.oom)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return xpath_node();
+ #else
+ throw std::bad_alloc();
+ #endif
+ }
+
+ return r.first();
+ }
+
+ PUGI_IMPL_FN const xpath_parse_result& xpath_query::result() const
+ {
+ return _result;
+ }
+
+ PUGI_IMPL_FN static void unspecified_bool_xpath_query(xpath_query***)
+ {
+ }
+
+ PUGI_IMPL_FN xpath_query::operator xpath_query::unspecified_bool_type() const
+ {
+ return _impl ? unspecified_bool_xpath_query : NULL;
+ }
+
+ PUGI_IMPL_FN bool xpath_query::operator!() const
+ {
+ return !_impl;
+ }
+
+ PUGI_IMPL_FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const
+ {
+ xpath_query q(query, variables);
+ return q.evaluate_node(*this);
+ }
+
+ PUGI_IMPL_FN xpath_node xml_node::select_node(const xpath_query& query) const
+ {
+ return query.evaluate_node(*this);
+ }
+
+ PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const
+ {
+ xpath_query q(query, variables);
+ return q.evaluate_node_set(*this);
+ }
+
+ PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const
+ {
+ return query.evaluate_node_set(*this);
+ }
+
+ PUGI_IMPL_FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const
+ {
+ xpath_query q(query, variables);
+ return q.evaluate_node(*this);
+ }
+
+ PUGI_IMPL_FN xpath_node xml_node::select_single_node(const xpath_query& query) const
+ {
+ return query.evaluate_node(*this);
+ }
+}
+
+#endif
+
+#ifdef __BORLANDC__
+# pragma option pop
+#endif
+
+#if defined(_MSC_VER) && defined(__c2__)
+# pragma clang diagnostic pop
+#endif
+
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+// Intel C++ does not properly keep warning state for function templates,
+// so popping warning state at the end of translation unit leads to warnings in the middle.
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+# pragma warning(pop)
+#endif
+
+// Undefine all local macros (makes sure we're not leaking macros in header-only mode)
+#undef PUGI_IMPL_NO_INLINE
+#undef PUGI_IMPL_UNALIGNED
+#undef PUGI_IMPL_UNLIKELY
+#undef PUGI_IMPL_STATIC_ASSERT
+#undef PUGI_IMPL_DMC_VOLATILE
+#undef PUGI_IMPL_UNSIGNED_OVERFLOW
+#undef PUGI_IMPL_MSVC_CRT_VERSION
+#undef PUGI_IMPL_SNPRINTF
+#undef PUGI_IMPL_NS_BEGIN
+#undef PUGI_IMPL_NS_END
+#undef PUGI_IMPL_FN
+#undef PUGI_IMPL_FN_NO_INLINE
+#undef PUGI_IMPL_GETHEADER_IMPL
+#undef PUGI_IMPL_GETPAGE_IMPL
+#undef PUGI_IMPL_GETPAGE
+#undef PUGI_IMPL_NODETYPE
+#undef PUGI_IMPL_IS_CHARTYPE_IMPL
+#undef PUGI_IMPL_IS_CHARTYPE
+#undef PUGI_IMPL_IS_CHARTYPEX
+#undef PUGI_IMPL_ENDSWITH
+#undef PUGI_IMPL_SKIPWS
+#undef PUGI_IMPL_OPTSET
+#undef PUGI_IMPL_PUSHNODE
+#undef PUGI_IMPL_POPNODE
+#undef PUGI_IMPL_SCANFOR
+#undef PUGI_IMPL_SCANWHILE
+#undef PUGI_IMPL_SCANWHILE_UNROLL
+#undef PUGI_IMPL_ENDSEG
+#undef PUGI_IMPL_THROW_ERROR
+#undef PUGI_IMPL_CHECK_ERROR
+
+#endif
+
+/**
+ * Copyright (c) 2006-2026 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/benchmarks/pugixml/pugixml.hpp b/benchmarks/pugixml/pugixml.hpp
new file mode 100644
index 0000000..c1fb1ad
--- /dev/null
+++ b/benchmarks/pugixml/pugixml.hpp
@@ -0,0 +1,1604 @@
+/**
+ * pugixml parser - version 1.16
+ * --------------------------------------------------------
+ * Report bugs and download new versions at https://pugixml.org/
+ *
+ * SPDX-FileCopyrightText: Copyright (C) 2006-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * SPDX-License-Identifier: MIT
+ *
+ * See LICENSE.md or notice at the end of this file.
+ */
+
+// Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons
+// Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits
+#ifndef PUGIXML_VERSION
+# define PUGIXML_VERSION 1160 // 1.16
+#endif
+
+// Include user configuration file (this can define various configuration macros)
+#include "pugiconfig.hpp"
+
+#ifndef HEADER_PUGIXML_HPP
+#define HEADER_PUGIXML_HPP
+
+// Include stddef.h for size_t and ptrdiff_t
+#include <stddef.h>
+
+// Include exception header for XPath
+#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS)
+# include <exception>
+#endif
+
+// Include STL headers
+#ifndef PUGIXML_NO_STL
+# include <iterator>
+# include <iosfwd>
+# include <string>
+#endif
+
+// Check if std::string_view is available
+#if !defined(PUGIXML_HAS_STRING_VIEW) && !defined(PUGIXML_NO_STL)
+# if __cplusplus >= 201703L
+# define PUGIXML_HAS_STRING_VIEW
+# elif defined(_MSVC_LANG) && _MSVC_LANG >= 201703L
+# define PUGIXML_HAS_STRING_VIEW
+# endif
+#endif
+
+// Include string_view if appropriate
+#ifdef PUGIXML_HAS_STRING_VIEW
+# include <string_view>
+#endif
+
+// Macro for deprecated features
+#ifndef PUGIXML_DEPRECATED
+# if defined(__GNUC__)
+# define PUGIXML_DEPRECATED __attribute__((deprecated))
+# elif defined(_MSC_VER) && _MSC_VER >= 1300
+# define PUGIXML_DEPRECATED __declspec(deprecated)
+# else
+# define PUGIXML_DEPRECATED
+# endif
+#endif
+
+// If no API is defined, assume default
+#ifndef PUGIXML_API
+# define PUGIXML_API
+#endif
+
+// If no API for classes is defined, assume default
+#ifndef PUGIXML_CLASS
+# define PUGIXML_CLASS PUGIXML_API
+#endif
+
+// If no API for functions is defined, assume default
+#ifndef PUGIXML_FUNCTION
+# define PUGIXML_FUNCTION PUGIXML_API
+#endif
+
+// If the platform is known to have long long support, enable long long functions
+#ifndef PUGIXML_HAS_LONG_LONG
+# if __cplusplus >= 201103
+# define PUGIXML_HAS_LONG_LONG
+# elif defined(_MSC_VER) && _MSC_VER >= 1400
+# define PUGIXML_HAS_LONG_LONG
+# endif
+#endif
+
+// If the platform is known to have move semantics support, compile move ctor/operator implementation
+#ifndef PUGIXML_HAS_MOVE
+# if __cplusplus >= 201103
+# define PUGIXML_HAS_MOVE
+# elif defined(_MSC_VER) && _MSC_VER >= 1600
+# define PUGIXML_HAS_MOVE
+# endif
+#endif
+
+// If C++ is 2011 or higher, use 'noexcept' specifiers
+#ifndef PUGIXML_NOEXCEPT
+# if __cplusplus >= 201103
+# define PUGIXML_NOEXCEPT noexcept
+# elif defined(_MSC_VER) && _MSC_VER >= 1900
+# define PUGIXML_NOEXCEPT noexcept
+# else
+# define PUGIXML_NOEXCEPT throw()
+# endif
+#endif
+
+// Some functions can not be noexcept in compact mode
+#ifdef PUGIXML_COMPACT
+# define PUGIXML_NOEXCEPT_IF_NOT_COMPACT
+#else
+# define PUGIXML_NOEXCEPT_IF_NOT_COMPACT PUGIXML_NOEXCEPT
+#endif
+
+// If C++ is 2011 or higher, add 'override' qualifiers
+#ifndef PUGIXML_OVERRIDE
+# if __cplusplus >= 201103
+# define PUGIXML_OVERRIDE override
+# elif defined(_MSC_VER) && _MSC_VER >= 1700
+# define PUGIXML_OVERRIDE override
+# else
+# define PUGIXML_OVERRIDE
+# endif
+#endif
+
+// If C++ is 2011 or higher, use 'nullptr'
+#ifndef PUGIXML_NULL
+# if __cplusplus >= 201103
+# define PUGIXML_NULL nullptr
+# elif defined(_MSC_VER) && _MSC_VER >= 1600
+# define PUGIXML_NULL nullptr
+# else
+# define PUGIXML_NULL 0
+# endif
+#endif
+
+// If C++ is 2017 or higher, add 'inline' variables
+#ifndef PUGIXML_INLINE_VAR
+# ifdef __cpp_inline_variables
+# define PUGIXML_INLINE_VAR inline
+# else
+# define PUGIXML_INLINE_VAR
+# endif
+#endif
+
+// Character interface macros
+#ifdef PUGIXML_WCHAR_MODE
+# define PUGIXML_TEXT(t) L ## t
+# define PUGIXML_CHAR wchar_t
+#else
+# define PUGIXML_TEXT(t) t
+# define PUGIXML_CHAR char
+#endif
+
+namespace pugi
+{
+ // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE
+ typedef PUGIXML_CHAR char_t;
+
+#ifndef PUGIXML_NO_STL
+ // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE
+ typedef std::basic_string<PUGIXML_CHAR> string_t;
+#endif
+
+#ifdef PUGIXML_HAS_STRING_VIEW
+ // String view type used for operations that can work with a length delimited string; depends on PUGIXML_WCHAR_MODE
+ typedef std::basic_string_view<PUGIXML_CHAR> string_view_t;
+#endif
+}
+
+// The PugiXML namespace
+namespace pugi
+{
+ // Tree node types
+ enum xml_node_type
+ {
+ node_null, // Empty (null) node handle
+ node_document, // A document tree's absolute root
+ node_element, // Element tag, i.e. '<node/>'
+ node_pcdata, // Plain character data, i.e. 'text'
+ node_cdata, // Character data, i.e. '<![CDATA[text]]>'
+ node_comment, // Comment tag, i.e. '<!-- text -->'
+ node_pi, // Processing instruction, i.e. '<?name?>'
+ node_declaration, // Document declaration, i.e. '<?xml version="1.0"?>'
+ node_doctype // Document type declaration, i.e. '<!DOCTYPE doc>'
+ };
+
+ // Parsing options
+
+ // Minimal parsing mode (equivalent to turning all other flags off).
+ // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed.
+ PUGIXML_INLINE_VAR const unsigned int parse_minimal = 0x0000;
+
+ // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_pi = 0x0001;
+
+ // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_comments = 0x0002;
+
+ // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_cdata = 0x0004;
+
+ // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree.
+ // This flag is off by default; turning it on usually results in slower parsing and more memory consumption.
+ PUGIXML_INLINE_VAR const unsigned int parse_ws_pcdata = 0x0008;
+
+ // This flag determines if character and entity references are expanded during parsing. This flag is on by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_escapes = 0x0010;
+
+ // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_eol = 0x0020;
+
+ // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_wconv_attribute = 0x0040;
+
+ // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_wnorm_attribute = 0x0080;
+
+ // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_declaration = 0x0100;
+
+ // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_doctype = 0x0200;
+
+ // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only
+ // of whitespace is added to the DOM tree.
+ // This flag is off by default; turning it on may result in slower parsing and more memory consumption.
+ PUGIXML_INLINE_VAR const unsigned int parse_ws_pcdata_single = 0x0400;
+
+ // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_trim_pcdata = 0x0800;
+
+ // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document
+ // is a valid document. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_fragment = 0x1000;
+
+ // This flag determines if plain character data is stored in the parent element's value. This significantly changes the structure of
+ // the document; this flag is only recommended for parsing documents with many PCDATA nodes in memory-constrained environments.
+ // This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_embed_pcdata = 0x2000;
+
+ // This flag determines whether two adjacent pcdata should be merged or not, if no intermediary data are parsed in the document.
+ // This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int parse_merge_pcdata = 0x4000;
+
+ // The default parsing mode.
+ // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded,
+ // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules.
+ PUGIXML_INLINE_VAR const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol;
+
+ // The full parsing mode.
+ // Nodes of all types are added to the DOM tree, character/reference entities are expanded,
+ // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules.
+ PUGIXML_INLINE_VAR const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype;
+
+ // These flags determine the encoding of input data for XML document
+ enum xml_encoding
+ {
+ encoding_auto, // Auto-detect input encoding using BOM or < / <? detection; use UTF8 if BOM is not found
+ encoding_utf8, // UTF8 encoding
+ encoding_utf16_le, // Little-endian UTF16
+ encoding_utf16_be, // Big-endian UTF16
+ encoding_utf16, // UTF16 with native endianness
+ encoding_utf32_le, // Little-endian UTF32
+ encoding_utf32_be, // Big-endian UTF32
+ encoding_utf32, // UTF32 with native endianness
+ encoding_wchar, // The same encoding wchar_t has (either UTF16 or UTF32)
+ encoding_latin1 // Latin1 encoding (ISO-8859-1)
+ };
+
+ // Formatting flags
+
+ // Indent the nodes that are written to output stream with as many indentation strings as deep the node is in DOM tree. This flag is on by default.
+ PUGIXML_INLINE_VAR const unsigned int format_indent = 0x01;
+
+ // Write encoding-specific BOM to the output stream. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_write_bom = 0x02;
+
+ // Use raw output mode (no indentation and no line breaks are written). This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_raw = 0x04;
+
+ // Omit default XML declaration even if there is no declaration in the document. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_no_declaration = 0x08;
+
+ // Don't escape attribute values and PCDATA contents. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_no_escapes = 0x10;
+
+ // Open file using text mode in xml_document::save_file. This enables special character (i.e. new-line) conversions on some systems. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_save_file_text = 0x20;
+
+ // Write every attribute on a new line with appropriate indentation. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_indent_attributes = 0x40;
+
+ // Don't output empty element tags, instead writing an explicit start and end tag even if there are no children. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_no_empty_element_tags = 0x80;
+
+ // Skip characters belonging to range [0; 32) instead of "&#xNN;" encoding. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_skip_control_chars = 0x100;
+
+ // Use single quotes ' instead of double quotes " for enclosing attribute values. This flag is off by default.
+ PUGIXML_INLINE_VAR const unsigned int format_attribute_single_quote = 0x200;
+
+ // The default set of formatting flags.
+ // Nodes are indented depending on their depth in DOM tree, a default declaration is output if document has none.
+ PUGIXML_INLINE_VAR const unsigned int format_default = format_indent;
+
+ PUGIXML_INLINE_VAR const int default_double_precision = 17;
+ PUGIXML_INLINE_VAR const int default_float_precision = 9;
+
+ // Forward declarations
+ struct xml_attribute_struct;
+ struct xml_node_struct;
+
+ class xml_node_iterator;
+ class xml_attribute_iterator;
+ class xml_named_node_iterator;
+
+ class xml_tree_walker;
+
+ struct xml_parse_result;
+
+ class xml_node;
+
+ class xml_text;
+
+ #ifndef PUGIXML_NO_XPATH
+ class xpath_node;
+ class xpath_node_set;
+ class xpath_query;
+ class xpath_variable_set;
+ #endif
+
+ // Range-based for loop support
+ template <typename It> class xml_object_range
+ {
+ public:
+ typedef It const_iterator;
+ typedef It iterator;
+
+ xml_object_range(It b, It e): _begin(b), _end(e)
+ {
+ }
+
+ It begin() const { return _begin; }
+ It end() const { return _end; }
+
+ bool empty() const { return _begin == _end; }
+
+ private:
+ It _begin, _end;
+ };
+
+ // Writer interface for node printing (see xml_node::print)
+ class PUGIXML_CLASS xml_writer
+ {
+ public:
+ virtual ~xml_writer();
+
+ // Write memory chunk into stream/file/whatever
+ virtual void write(const void* data, size_t size) = 0;
+ };
+
+ // xml_writer implementation for FILE*
+ class PUGIXML_CLASS xml_writer_file: public xml_writer
+ {
+ public:
+ // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio
+ xml_writer_file(void* file);
+
+ virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE;
+
+ private:
+ void* file;
+ };
+
+ #ifndef PUGIXML_NO_STL
+ // xml_writer implementation for streams
+ class PUGIXML_CLASS xml_writer_stream: public xml_writer
+ {
+ public:
+ // Construct writer from an output stream object
+ xml_writer_stream(std::basic_ostream<char>& stream);
+ xml_writer_stream(std::basic_ostream<wchar_t>& stream);
+
+ virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE;
+
+ private:
+ std::basic_ostream<char>* narrow_stream;
+ std::basic_ostream<wchar_t>* wide_stream;
+ };
+ #endif
+
+ // A light-weight handle for manipulating attributes in DOM tree
+ class PUGIXML_CLASS xml_attribute
+ {
+ friend class xml_attribute_iterator;
+ friend class xml_node;
+
+ private:
+ xml_attribute_struct* _attr;
+
+ typedef void (*unspecified_bool_type)(xml_attribute***);
+
+ public:
+ // Default constructor. Constructs an empty attribute.
+ xml_attribute();
+
+ // Constructs attribute from internal pointer
+ explicit xml_attribute(xml_attribute_struct* attr);
+
+ // Safe bool conversion operator
+ operator unspecified_bool_type() const;
+
+ // Borland C++ workaround
+ bool operator!() const;
+
+ // Comparison operators (compares wrapped attribute pointers)
+ bool operator==(const xml_attribute& r) const;
+ bool operator!=(const xml_attribute& r) const;
+ bool operator<(const xml_attribute& r) const;
+ bool operator>(const xml_attribute& r) const;
+ bool operator<=(const xml_attribute& r) const;
+ bool operator>=(const xml_attribute& r) const;
+
+ // Check if attribute is empty (null)
+ bool empty() const;
+
+ // Get attribute name/value, or "" if attribute is empty
+ const char_t* name() const;
+ const char_t* value() const;
+
+ // Get attribute value, or the default value if attribute is empty
+ const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const;
+
+ // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty
+ int as_int(int def = 0) const;
+ unsigned int as_uint(unsigned int def = 0) const;
+ double as_double(double def = 0) const;
+ float as_float(float def = 0) const;
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ long long as_llong(long long def = 0) const;
+ unsigned long long as_ullong(unsigned long long def = 0) const;
+ #endif
+
+ // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty
+ bool as_bool(bool def = false) const;
+
+ // Set attribute name/value (returns false if attribute is empty or there is not enough memory)
+ bool set_name(const char_t* rhs);
+ bool set_name(const char_t* rhs, size_t size);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool set_name(string_view_t rhs);
+ #endif
+ bool set_value(const char_t* rhs);
+ bool set_value(const char_t* rhs, size_t size);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool set_value(string_view_t rhs);
+ #endif
+
+ // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false")
+ bool set_value(int rhs);
+ bool set_value(unsigned int rhs);
+ bool set_value(long rhs);
+ bool set_value(unsigned long rhs);
+ bool set_value(double rhs);
+ bool set_value(double rhs, int precision);
+ bool set_value(float rhs);
+ bool set_value(float rhs, int precision);
+ bool set_value(bool rhs);
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ bool set_value(long long rhs);
+ bool set_value(unsigned long long rhs);
+ #endif
+
+ // Set attribute value (equivalent to set_value without error checking)
+ xml_attribute& operator=(const char_t* rhs);
+ xml_attribute& operator=(int rhs);
+ xml_attribute& operator=(unsigned int rhs);
+ xml_attribute& operator=(long rhs);
+ xml_attribute& operator=(unsigned long rhs);
+ xml_attribute& operator=(double rhs);
+ xml_attribute& operator=(float rhs);
+ xml_attribute& operator=(bool rhs);
+
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_attribute& operator=(string_view_t rhs);
+ #endif
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ xml_attribute& operator=(long long rhs);
+ xml_attribute& operator=(unsigned long long rhs);
+ #endif
+
+ // Get next/previous attribute in the attribute list of the parent node
+ xml_attribute next_attribute() const;
+ xml_attribute previous_attribute() const;
+
+ // Get hash value (unique for handles to the same object)
+ size_t hash_value() const;
+
+ // Get internal pointer
+ xml_attribute_struct* internal_object() const;
+ };
+
+#ifdef __BORLANDC__
+ // Borland C++ workaround
+ bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs);
+ bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs);
+#endif
+
+ // A light-weight handle for manipulating nodes in DOM tree
+ class PUGIXML_CLASS xml_node
+ {
+ friend class xml_attribute_iterator;
+ friend class xml_node_iterator;
+ friend class xml_named_node_iterator;
+
+ protected:
+ xml_node_struct* _root;
+
+ typedef void (*unspecified_bool_type)(xml_node***);
+
+ public:
+ // Default constructor. Constructs an empty node.
+ xml_node();
+
+ // Constructs node from internal pointer
+ explicit xml_node(xml_node_struct* p);
+
+ // Safe bool conversion operator
+ operator unspecified_bool_type() const;
+
+ // Borland C++ workaround
+ bool operator!() const;
+
+ // Comparison operators (compares wrapped node pointers)
+ bool operator==(const xml_node& r) const;
+ bool operator!=(const xml_node& r) const;
+ bool operator<(const xml_node& r) const;
+ bool operator>(const xml_node& r) const;
+ bool operator<=(const xml_node& r) const;
+ bool operator>=(const xml_node& r) const;
+
+ // Check if node is empty (null)
+ bool empty() const;
+
+ // Get node type
+ xml_node_type type() const;
+
+ // Get node name, or "" if node is empty or it has no name
+ const char_t* name() const;
+
+ // Get node value, or "" if node is empty or it has no value
+ // Note: For <node>text</node> node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes.
+ const char_t* value() const;
+
+ // Get attribute list
+ xml_attribute first_attribute() const;
+ xml_attribute last_attribute() const;
+
+ // Get children list
+ xml_node first_child() const;
+ xml_node last_child() const;
+
+ // Get next/previous sibling in the children list of the parent node
+ xml_node next_sibling() const;
+ xml_node previous_sibling() const;
+
+ // Get parent node
+ xml_node parent() const;
+
+ // Get root of DOM tree this node belongs to
+ xml_node root() const;
+
+ // Get text object for the current node
+ xml_text text() const;
+
+ // Get child, attribute or next/previous sibling with the specified name
+ xml_node child(const char_t* name) const;
+ xml_attribute attribute(const char_t* name) const;
+ xml_node next_sibling(const char_t* name) const;
+ xml_node previous_sibling(const char_t* name) const;
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_node child(string_view_t name) const;
+ xml_attribute attribute(string_view_t name) const;
+ xml_node next_sibling(string_view_t name) const;
+ xml_node previous_sibling(string_view_t name) const;
+ #endif
+
+ // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast)
+ xml_attribute attribute(const char_t* name, xml_attribute& hint) const;
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_attribute attribute(string_view_t name, xml_attribute& hint) const;
+ #endif
+
+ // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA
+ const char_t* child_value() const;
+
+ // Get child value of child with specified name. Equivalent to child(name).child_value().
+ const char_t* child_value(const char_t* name) const;
+
+ // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value)
+ bool set_name(const char_t* rhs);
+ bool set_name(const char_t* rhs, size_t size);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool set_name(string_view_t rhs);
+ #endif
+ bool set_value(const char_t* rhs);
+ bool set_value(const char_t* rhs, size_t size);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool set_value(string_view_t rhs);
+ #endif
+
+ // Add attribute with specified name. Returns added attribute, or empty attribute on errors.
+ xml_attribute append_attribute(const char_t* name);
+ xml_attribute prepend_attribute(const char_t* name);
+ xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr);
+ xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_attribute append_attribute(string_view_t name);
+ xml_attribute prepend_attribute(string_view_t name);
+ xml_attribute insert_attribute_after(string_view_t name, const xml_attribute& attr);
+ xml_attribute insert_attribute_before(string_view_t name, const xml_attribute& attr);
+ #endif
+
+ // Get attribute with specified name, adding one if it does not exist. Returns the existing or added attribute, or empty attribute on errors.
+ xml_attribute ensure_attribute(const char_t* name);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_attribute ensure_attribute(string_view_t name);
+ #endif
+
+ // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors.
+ xml_attribute append_copy(const xml_attribute& proto);
+ xml_attribute prepend_copy(const xml_attribute& proto);
+ xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr);
+ xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr);
+
+ // Add child node with specified type. Returns added node, or empty node on errors.
+ xml_node append_child(xml_node_type type = node_element);
+ xml_node prepend_child(xml_node_type type = node_element);
+ xml_node insert_child_after(xml_node_type type, const xml_node& node);
+ xml_node insert_child_before(xml_node_type type, const xml_node& node);
+
+ // Add child element with specified name. Returns added node, or empty node on errors.
+ xml_node append_child(const char_t* name);
+ xml_node prepend_child(const char_t* name);
+ xml_node insert_child_after(const char_t* name, const xml_node& node);
+ xml_node insert_child_before(const char_t* name, const xml_node& node);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_node append_child(string_view_t name);
+ xml_node prepend_child(string_view_t name);
+ xml_node insert_child_after(string_view_t name, const xml_node& node);
+ xml_node insert_child_before(string_view_t name, const xml_node& node);
+ #endif
+
+ // Get child with specified name, adding one if it does not exist. Returns the existing or added node, or empty node on errors.
+ xml_node ensure_child(const char_t* name);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_node ensure_child(string_view_t name);
+ #endif
+
+ // Add a copy of the specified node as a child. Returns added node, or empty node on errors.
+ xml_node append_copy(const xml_node& proto);
+ xml_node prepend_copy(const xml_node& proto);
+ xml_node insert_copy_after(const xml_node& proto, const xml_node& node);
+ xml_node insert_copy_before(const xml_node& proto, const xml_node& node);
+
+ // Move the specified node to become a child of this node. Returns moved node, or empty node on errors.
+ xml_node append_move(const xml_node& moved);
+ xml_node prepend_move(const xml_node& moved);
+ xml_node insert_move_after(const xml_node& moved, const xml_node& node);
+ xml_node insert_move_before(const xml_node& moved, const xml_node& node);
+
+ // Remove specified attribute
+ bool remove_attribute(const xml_attribute& a);
+ bool remove_attribute(const char_t* name);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool remove_attribute(string_view_t name);
+ #endif
+
+ // Remove all attributes
+ bool remove_attributes();
+
+ // Remove specified child
+ bool remove_child(const xml_node& n);
+ bool remove_child(const char_t* name);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool remove_child(string_view_t name);
+ #endif
+
+ // Remove all children
+ bool remove_children();
+
+ // Parses buffer as an XML document fragment and appends all nodes as children of the current node.
+ // Copies/converts the buffer, so it may be deleted or changed after the function returns.
+ // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory.
+ xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+ // Find attribute using predicate. Returns first attribute for which predicate returned true.
+ template <typename Predicate> xml_attribute find_attribute(Predicate pred) const
+ {
+ if (!_root) return xml_attribute();
+
+ for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute())
+ if (pred(attrib))
+ return attrib;
+
+ return xml_attribute();
+ }
+
+ // Find child node using predicate. Returns first child for which predicate returned true.
+ template <typename Predicate> xml_node find_child(Predicate pred) const
+ {
+ if (!_root) return xml_node();
+
+ for (xml_node node = first_child(); node; node = node.next_sibling())
+ if (pred(node))
+ return node;
+
+ return xml_node();
+ }
+
+ // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true.
+ template <typename Predicate> xml_node find_node(Predicate pred) const
+ {
+ if (!_root) return xml_node();
+
+ xml_node cur = first_child();
+
+ while (cur._root && cur._root != _root)
+ {
+ if (pred(cur)) return cur;
+
+ if (cur.first_child()) cur = cur.first_child();
+ else if (cur.next_sibling()) cur = cur.next_sibling();
+ else
+ {
+ while (!cur.next_sibling() && cur._root != _root) cur = cur.parent();
+
+ if (cur._root != _root) cur = cur.next_sibling();
+ }
+ }
+
+ return xml_node();
+ }
+
+ // Find child node by attribute name/value
+ xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const;
+ xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const;
+
+ #ifndef PUGIXML_NO_STL
+ // Get the absolute node path from root as a text string.
+ string_t path(char_t delimiter = '/') const;
+ #endif
+
+ // Search for a node by path consisting of node names and . or .. elements.
+ xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const;
+
+ // Recursively traverse subtree with xml_tree_walker
+ bool traverse(xml_tree_walker& walker);
+
+ #ifndef PUGIXML_NO_XPATH
+ // Select single node by evaluating XPath query. Returns first node from the resulting node set.
+ xpath_node select_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const;
+ xpath_node select_node(const xpath_query& query) const;
+
+ // Select node set by evaluating XPath query
+ xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const;
+ xpath_node_set select_nodes(const xpath_query& query) const;
+
+ // (deprecated: use select_node instead) Select single node by evaluating XPath query.
+ PUGIXML_DEPRECATED xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const;
+ PUGIXML_DEPRECATED xpath_node select_single_node(const xpath_query& query) const;
+
+ #endif
+
+ // Print subtree using a writer object
+ void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
+
+ #ifndef PUGIXML_NO_STL
+ // Print subtree to stream
+ void print(std::basic_ostream<char>& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
+ void print(std::basic_ostream<wchar_t>& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const;
+ #endif
+
+ // Child nodes iterators
+ typedef xml_node_iterator iterator;
+
+ iterator begin() const;
+ iterator end() const;
+
+ // Attribute iterators
+ typedef xml_attribute_iterator attribute_iterator;
+
+ attribute_iterator attributes_begin() const;
+ attribute_iterator attributes_end() const;
+
+ // Range-based for support
+ xml_object_range<xml_node_iterator> children() const;
+ xml_object_range<xml_attribute_iterator> attributes() const;
+
+ // Range-based for support for all children with the specified name
+ // Note: name pointer must have a longer lifetime than the returned object; be careful with passing temporaries!
+ xml_object_range<xml_named_node_iterator> children(const char_t* name) const;
+
+ // Get node offset in parsed file/string (in char_t units) for debugging purposes
+ ptrdiff_t offset_debug() const;
+
+ // Get hash value (unique for handles to the same object)
+ size_t hash_value() const;
+
+ // Get internal pointer
+ xml_node_struct* internal_object() const;
+ };
+
+#ifdef __BORLANDC__
+ // Borland C++ workaround
+ bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs);
+ bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs);
+#endif
+
+ // A helper for working with text inside PCDATA nodes
+ class PUGIXML_CLASS xml_text
+ {
+ friend class xml_node;
+
+ xml_node_struct* _root;
+
+ typedef void (*unspecified_bool_type)(xml_text***);
+
+ explicit xml_text(xml_node_struct* root);
+
+ xml_node_struct* _data_new();
+ xml_node_struct* _data() const;
+
+ public:
+ // Default constructor. Constructs an empty object.
+ xml_text();
+
+ // Safe bool conversion operator
+ operator unspecified_bool_type() const;
+
+ // Borland C++ workaround
+ bool operator!() const;
+
+ // Check if text object is empty (null)
+ bool empty() const;
+
+ // Get text, or "" if object is empty
+ const char_t* get() const;
+
+ // Get text, or the default value if object is empty
+ const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const;
+
+ // Get text as a number, or the default value if conversion did not succeed or object is empty
+ int as_int(int def = 0) const;
+ unsigned int as_uint(unsigned int def = 0) const;
+ double as_double(double def = 0) const;
+ float as_float(float def = 0) const;
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ long long as_llong(long long def = 0) const;
+ unsigned long long as_ullong(unsigned long long def = 0) const;
+ #endif
+
+ // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty
+ bool as_bool(bool def = false) const;
+
+ // Set text (returns false if object is empty or there is not enough memory)
+ bool set(const char_t* rhs);
+ bool set(const char_t* rhs, size_t size);
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ bool set(string_view_t rhs);
+ #endif
+
+ // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false")
+ bool set(int rhs);
+ bool set(unsigned int rhs);
+ bool set(long rhs);
+ bool set(unsigned long rhs);
+ bool set(double rhs);
+ bool set(double rhs, int precision);
+ bool set(float rhs);
+ bool set(float rhs, int precision);
+ bool set(bool rhs);
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ bool set(long long rhs);
+ bool set(unsigned long long rhs);
+ #endif
+
+ // Set text (equivalent to set without error checking)
+ xml_text& operator=(const char_t* rhs);
+ xml_text& operator=(int rhs);
+ xml_text& operator=(unsigned int rhs);
+ xml_text& operator=(long rhs);
+ xml_text& operator=(unsigned long rhs);
+ xml_text& operator=(double rhs);
+ xml_text& operator=(float rhs);
+ xml_text& operator=(bool rhs);
+
+ #ifdef PUGIXML_HAS_STRING_VIEW
+ xml_text& operator=(string_view_t rhs);
+ #endif
+
+ #ifdef PUGIXML_HAS_LONG_LONG
+ xml_text& operator=(long long rhs);
+ xml_text& operator=(unsigned long long rhs);
+ #endif
+
+ // Get the data node (node_pcdata or node_cdata) for this object
+ xml_node data() const;
+ };
+
+#ifdef __BORLANDC__
+ // Borland C++ workaround
+ bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs);
+ bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs);
+#endif
+
+ // Child node iterator (a bidirectional iterator over a collection of xml_node)
+ class PUGIXML_CLASS xml_node_iterator
+ {
+ friend class xml_node;
+
+ private:
+ mutable xml_node _wrap;
+ xml_node _parent;
+
+ xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent);
+
+ public:
+ // Iterator traits
+ typedef ptrdiff_t difference_type;
+ typedef xml_node value_type;
+ typedef xml_node* pointer;
+ typedef xml_node& reference;
+
+ #ifndef PUGIXML_NO_STL
+ typedef std::bidirectional_iterator_tag iterator_category;
+ #endif
+
+ // Default constructor
+ xml_node_iterator();
+
+ // Construct an iterator which points to the specified node
+ xml_node_iterator(const xml_node& node);
+
+ // Iterator operators
+ bool operator==(const xml_node_iterator& rhs) const;
+ bool operator!=(const xml_node_iterator& rhs) const;
+
+ xml_node& operator*() const;
+ xml_node* operator->() const;
+
+ xml_node_iterator& operator++();
+ xml_node_iterator operator++(int);
+
+ xml_node_iterator& operator--();
+ xml_node_iterator operator--(int);
+ };
+
+ // Attribute iterator (a bidirectional iterator over a collection of xml_attribute)
+ class PUGIXML_CLASS xml_attribute_iterator
+ {
+ friend class xml_node;
+
+ private:
+ mutable xml_attribute _wrap;
+ xml_node _parent;
+
+ xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent);
+
+ public:
+ // Iterator traits
+ typedef ptrdiff_t difference_type;
+ typedef xml_attribute value_type;
+ typedef xml_attribute* pointer;
+ typedef xml_attribute& reference;
+
+ #ifndef PUGIXML_NO_STL
+ typedef std::bidirectional_iterator_tag iterator_category;
+ #endif
+
+ // Default constructor
+ xml_attribute_iterator();
+
+ // Construct an iterator which points to the specified attribute
+ xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent);
+
+ // Iterator operators
+ bool operator==(const xml_attribute_iterator& rhs) const;
+ bool operator!=(const xml_attribute_iterator& rhs) const;
+
+ xml_attribute& operator*() const;
+ xml_attribute* operator->() const;
+
+ xml_attribute_iterator& operator++();
+ xml_attribute_iterator operator++(int);
+
+ xml_attribute_iterator& operator--();
+ xml_attribute_iterator operator--(int);
+ };
+
+ // Named node range helper
+ class PUGIXML_CLASS xml_named_node_iterator
+ {
+ friend class xml_node;
+
+ public:
+ // Iterator traits
+ typedef ptrdiff_t difference_type;
+ typedef xml_node value_type;
+ typedef xml_node* pointer;
+ typedef xml_node& reference;
+
+ #ifndef PUGIXML_NO_STL
+ typedef std::bidirectional_iterator_tag iterator_category;
+ #endif
+
+ // Default constructor
+ xml_named_node_iterator();
+
+ // Construct an iterator which points to the specified node
+ // Note: name pointer is stored in the iterator and must have a longer lifetime than iterator itself
+ xml_named_node_iterator(const xml_node& node, const char_t* name);
+
+ // Iterator operators
+ bool operator==(const xml_named_node_iterator& rhs) const;
+ bool operator!=(const xml_named_node_iterator& rhs) const;
+
+ xml_node& operator*() const;
+ xml_node* operator->() const;
+
+ xml_named_node_iterator& operator++();
+ xml_named_node_iterator operator++(int);
+
+ xml_named_node_iterator& operator--();
+ xml_named_node_iterator operator--(int);
+
+ private:
+ mutable xml_node _wrap;
+ xml_node _parent;
+ const char_t* _name;
+
+ xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name);
+ };
+
+ // Abstract tree walker class (see xml_node::traverse)
+ class PUGIXML_CLASS xml_tree_walker
+ {
+ friend class xml_node;
+
+ private:
+ int _depth;
+
+ protected:
+ // Get current traversal depth
+ int depth() const;
+
+ public:
+ xml_tree_walker();
+ virtual ~xml_tree_walker();
+
+ // Callback that is called when traversal begins
+ virtual bool begin(xml_node& node);
+
+ // Callback that is called for each node traversed
+ virtual bool for_each(xml_node& node) = 0;
+
+ // Callback that is called when traversal ends
+ virtual bool end(xml_node& node);
+ };
+
+ // Parsing status, returned as part of xml_parse_result object
+ enum xml_parse_status
+ {
+ status_ok = 0, // No error
+
+ status_file_not_found, // File was not found during load_file()
+ status_io_error, // Error reading from file/stream
+ status_out_of_memory, // Could not allocate memory
+ status_internal_error, // Internal error occurred
+
+ status_unrecognized_tag, // Parser could not determine tag type
+
+ status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction
+ status_bad_comment, // Parsing error occurred while parsing comment
+ status_bad_cdata, // Parsing error occurred while parsing CDATA section
+ status_bad_doctype, // Parsing error occurred while parsing document type declaration
+ status_bad_pcdata, // Parsing error occurred while parsing PCDATA section
+ status_bad_start_element, // Parsing error occurred while parsing start element tag
+ status_bad_attribute, // Parsing error occurred while parsing element attribute
+ status_bad_end_element, // Parsing error occurred while parsing end element tag
+ status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag)
+
+ status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer)
+
+ status_no_document_element // Parsing resulted in a document without element nodes
+ };
+
+ // Parsing result
+ struct PUGIXML_CLASS xml_parse_result
+ {
+ // Parsing status (see xml_parse_status)
+ xml_parse_status status;
+
+ // Last parsed offset (in char_t units from start of input data)
+ ptrdiff_t offset;
+
+ // Source document encoding
+ xml_encoding encoding;
+
+ // Default constructor, initializes object to failed state
+ xml_parse_result();
+
+ // Cast to bool operator
+ operator bool() const;
+
+ // Get error description
+ const char* description() const;
+ };
+
+ // Document class (DOM tree root)
+ class PUGIXML_CLASS xml_document: public xml_node
+ {
+ private:
+ char_t* _buffer;
+
+ char _memory[192];
+
+ // Non-copyable semantics
+ xml_document(const xml_document&);
+ xml_document& operator=(const xml_document&);
+
+ void _create();
+ void _destroy();
+ void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT;
+
+ public:
+ // Default constructor, makes empty document
+ xml_document();
+
+ // Destructor, invalidates all node/attribute handles to this document
+ ~xml_document();
+
+ #ifdef PUGIXML_HAS_MOVE
+ // Move semantics support
+ xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT;
+ xml_document& operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT;
+ #endif
+
+ // Removes all nodes, leaving the empty document
+ void reset();
+
+ // Removes all nodes, then copies the entire contents of the specified document
+ void reset(const xml_document& proto);
+
+ #ifndef PUGIXML_NO_STL
+ // Load document from stream.
+ xml_parse_result load(std::basic_istream<char>& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+ xml_parse_result load(std::basic_istream<wchar_t>& stream, unsigned int options = parse_default);
+ #endif
+
+ // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied.
+ PUGIXML_DEPRECATED xml_parse_result load(const char_t* contents, unsigned int options = parse_default);
+
+ // Load document from zero-terminated string. No encoding conversions are applied.
+ xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default);
+
+ // Load document from file
+ xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+ xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+ // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns.
+ xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+ // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data).
+ // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed.
+ xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+ // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data).
+ // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore).
+ xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+ // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details).
+ void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+
+ #ifndef PUGIXML_NO_STL
+ // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details).
+ void save(std::basic_ostream<char>& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+ void save(std::basic_ostream<wchar_t>& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const;
+ #endif
+
+ // Save XML to file
+ bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+ bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+
+ // Get document element
+ xml_node document_element() const;
+ };
+
+#ifndef PUGIXML_NO_XPATH
+ // XPath query return type
+ enum xpath_value_type
+ {
+ xpath_type_none, // Unknown type (query failed to compile)
+ xpath_type_node_set, // Node set (xpath_node_set)
+ xpath_type_number, // Number
+ xpath_type_string, // String
+ xpath_type_boolean // Boolean
+ };
+
+ // XPath parsing result
+ struct PUGIXML_CLASS xpath_parse_result
+ {
+ // Error message (0 if no error)
+ const char* error;
+
+ // Last parsed offset (in char_t units from string start)
+ ptrdiff_t offset;
+
+ // Default constructor, initializes object to failed state
+ xpath_parse_result();
+
+ // Cast to bool operator
+ operator bool() const;
+
+ // Get error description
+ const char* description() const;
+ };
+
+ // A single XPath variable
+ class PUGIXML_CLASS xpath_variable
+ {
+ friend class xpath_variable_set;
+
+ protected:
+ xpath_value_type _type;
+ xpath_variable* _next;
+
+ xpath_variable(xpath_value_type type);
+
+ // Non-copyable semantics
+ xpath_variable(const xpath_variable&);
+ xpath_variable& operator=(const xpath_variable&);
+
+ public:
+ // Get variable name
+ const char_t* name() const;
+
+ // Get variable type
+ xpath_value_type type() const;
+
+ // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error
+ bool get_boolean() const;
+ double get_number() const;
+ const char_t* get_string() const;
+ const xpath_node_set& get_node_set() const;
+
+ // Set variable value; no type conversion is performed, false is returned on type mismatch error
+ bool set(bool value);
+ bool set(double value);
+ bool set(const char_t* value);
+ bool set(const xpath_node_set& value);
+ };
+
+ // A set of XPath variables
+ class PUGIXML_CLASS xpath_variable_set
+ {
+ private:
+ xpath_variable* _data[64];
+
+ void _assign(const xpath_variable_set& rhs);
+ void _swap(xpath_variable_set& rhs);
+
+ xpath_variable* _find(const char_t* name) const;
+
+ static bool _clone(xpath_variable* var, xpath_variable** out_result);
+ static void _destroy(xpath_variable* var);
+
+ public:
+ // Default constructor/destructor
+ xpath_variable_set();
+ ~xpath_variable_set();
+
+ // Copy constructor/assignment operator
+ xpath_variable_set(const xpath_variable_set& rhs);
+ xpath_variable_set& operator=(const xpath_variable_set& rhs);
+
+ #ifdef PUGIXML_HAS_MOVE
+ // Move semantics support
+ xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT;
+ xpath_variable_set& operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT;
+ #endif
+
+ // Add a new variable or get the existing one, if the types match
+ xpath_variable* add(const char_t* name, xpath_value_type type);
+
+ // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch
+ bool set(const char_t* name, bool value);
+ bool set(const char_t* name, double value);
+ bool set(const char_t* name, const char_t* value);
+ bool set(const char_t* name, const xpath_node_set& value);
+
+ // Get existing variable by name
+ xpath_variable* get(const char_t* name);
+ const xpath_variable* get(const char_t* name) const;
+ };
+
+ // A compiled XPath query object
+ class PUGIXML_CLASS xpath_query
+ {
+ private:
+ void* _impl;
+ xpath_parse_result _result;
+
+ typedef void (*unspecified_bool_type)(xpath_query***);
+
+ // Non-copyable semantics
+ xpath_query(const xpath_query&);
+ xpath_query& operator=(const xpath_query&);
+
+ public:
+ // Construct a compiled object from XPath expression.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors.
+ explicit xpath_query(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL);
+
+ // Constructor
+ xpath_query();
+
+ // Destructor
+ ~xpath_query();
+
+ #ifdef PUGIXML_HAS_MOVE
+ // Move semantics support
+ xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT;
+ xpath_query& operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT;
+ #endif
+
+ // Get query expression return type
+ xpath_value_type return_type() const;
+
+ // Evaluate expression as boolean value in the specified context; performs type conversion if necessary.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+ bool evaluate_boolean(const xpath_node& n) const;
+
+ // Evaluate expression as double value in the specified context; performs type conversion if necessary.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+ double evaluate_number(const xpath_node& n) const;
+
+ #ifndef PUGIXML_NO_STL
+ // Evaluate expression as string value in the specified context; performs type conversion if necessary.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+ string_t evaluate_string(const xpath_node& n) const;
+ #endif
+
+ // Evaluate expression as string value in the specified context; performs type conversion if necessary.
+ // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero).
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+ // If PUGIXML_NO_EXCEPTIONS is defined, returns empty string instead.
+ size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const;
+
+ // Evaluate expression as node set in the specified context.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors.
+ // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead.
+ xpath_node_set evaluate_node_set(const xpath_node& n) const;
+
+ // Evaluate expression as node set in the specified context.
+ // Return first node in document order, or empty node if node set is empty.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors.
+ // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead.
+ xpath_node evaluate_node(const xpath_node& n) const;
+
+ // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode)
+ const xpath_parse_result& result() const;
+
+ // Safe bool conversion operator
+ operator unspecified_bool_type() const;
+
+ // Borland C++ workaround
+ bool operator!() const;
+ };
+
+ #ifndef PUGIXML_NO_EXCEPTIONS
+ #if defined(_MSC_VER)
+ // C4275 can be ignored in Visual C++ if you are deriving
+ // from a type in the Standard C++ Library
+ #pragma warning(push)
+ #pragma warning(disable: 4275)
+ #endif
+ // XPath exception class
+ class PUGIXML_CLASS xpath_exception: public std::exception
+ {
+ private:
+ xpath_parse_result _result;
+
+ public:
+ // Construct exception from parse result
+ explicit xpath_exception(const xpath_parse_result& result);
+
+ // Get error message
+ virtual const char* what() const PUGIXML_NOEXCEPT PUGIXML_OVERRIDE;
+
+ // Get parse result
+ const xpath_parse_result& result() const;
+ };
+ #if defined(_MSC_VER)
+ #pragma warning(pop)
+ #endif
+ #endif
+
+ // XPath node class (either xml_node or xml_attribute)
+ class PUGIXML_CLASS xpath_node
+ {
+ private:
+ xml_node _node;
+ xml_attribute _attribute;
+
+ typedef void (*unspecified_bool_type)(xpath_node***);
+
+ public:
+ // Default constructor; constructs empty XPath node
+ xpath_node();
+
+ // Construct XPath node from XML node/attribute
+ xpath_node(const xml_node& node);
+ xpath_node(const xml_attribute& attribute, const xml_node& parent);
+
+ // Get node/attribute, if any
+ xml_node node() const;
+ xml_attribute attribute() const;
+
+ // Get parent of contained node/attribute
+ xml_node parent() const;
+
+ // Safe bool conversion operator
+ operator unspecified_bool_type() const;
+
+ // Borland C++ workaround
+ bool operator!() const;
+
+ // Comparison operators
+ bool operator==(const xpath_node& n) const;
+ bool operator!=(const xpath_node& n) const;
+ };
+
+#ifdef __BORLANDC__
+ // Borland C++ workaround
+ bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs);
+ bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs);
+#endif
+
+ // A fixed-size collection of XPath nodes
+ class PUGIXML_CLASS xpath_node_set
+ {
+ public:
+ // Collection type
+ enum type_t
+ {
+ type_unsorted, // Not ordered
+ type_sorted, // Sorted by document order (ascending)
+ type_sorted_reverse // Sorted by document order (descending)
+ };
+
+ // Constant iterator type
+ typedef const xpath_node* const_iterator;
+
+ // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work
+ typedef const xpath_node* iterator;
+
+ // Default constructor. Constructs empty set.
+ xpath_node_set();
+
+ // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful
+ xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted);
+
+ // Destructor
+ ~xpath_node_set();
+
+ // Copy constructor/assignment operator
+ xpath_node_set(const xpath_node_set& ns);
+ xpath_node_set& operator=(const xpath_node_set& ns);
+
+ #ifdef PUGIXML_HAS_MOVE
+ // Move semantics support
+ xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT;
+ xpath_node_set& operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT;
+ #endif
+
+ // Get collection type
+ type_t type() const;
+
+ // Get collection size
+ size_t size() const;
+
+ // Indexing operator
+ const xpath_node& operator[](size_t index) const;
+
+ // Collection iterators
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ // Sort the collection in ascending/descending order by document order
+ void sort(bool reverse = false);
+
+ // Get first node in the collection by document order
+ xpath_node first() const;
+
+ // Check if collection is empty
+ bool empty() const;
+
+ private:
+ type_t _type;
+
+ xpath_node _storage[1];
+
+ xpath_node* _begin;
+ xpath_node* _end;
+
+ void _assign(const_iterator begin, const_iterator end, type_t type);
+ void _move(xpath_node_set& rhs) PUGIXML_NOEXCEPT;
+ };
+#endif
+
+#ifndef PUGIXML_NO_STL
+ // Convert wide string to UTF8
+ std::basic_string<char> PUGIXML_FUNCTION as_utf8(const wchar_t* str);
+ std::basic_string<char> PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t>& str);
+
+ // Convert UTF8 to wide string
+ std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const char* str);
+ std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const std::basic_string<char>& str);
+#endif
+
+ // Memory allocation function interface; returns pointer to allocated memory or NULL on failure
+ typedef void* (*allocation_function)(size_t size);
+
+ // Memory deallocation function interface
+ typedef void (*deallocation_function)(void* ptr);
+
+ // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions.
+ void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate);
+
+ // Get current memory management functions
+ allocation_function PUGIXML_FUNCTION get_memory_allocation_function();
+ deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function();
+}
+
+#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC))
+namespace std
+{
+ // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier)
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&);
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&);
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&);
+}
+#endif
+
+#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC)
+namespace std
+{
+ // Workarounds for (non-standard) iterator category detection
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&);
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&);
+ std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&);
+}
+#endif
+
+#endif
+
+// Make sure implementation is included in header-only mode
+// Use macro expansion in #include to work around QMake (QTBUG-11923)
+#if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE)
+# define PUGIXML_SOURCE "pugixml.cpp"
+# include PUGIXML_SOURCE
+#endif
+
+/**
+ * Copyright (c) 2006-2026 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/benchmarks/quick-xml/.env/build.sh b/benchmarks/quick-xml/.env/build.sh
new file mode 100644
index 0000000..09ce8e7
--- /dev/null
+++ b/benchmarks/quick-xml/.env/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+#RUSTFLAGS="-C target-cpu=native" cargo build --release
+cargo build --release
+
diff --git a/benchmarks/quick-xml/.env/debug.sh b/benchmarks/quick-xml/.env/debug.sh
new file mode 100644
index 0000000..23f310e
--- /dev/null
+++ b/benchmarks/quick-xml/.env/debug.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -xe
+
+. .env/build.sh
+
+./target/release/quick-xml-bench
+
diff --git a/benchmarks/quick-xml/.gitignore b/benchmarks/quick-xml/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/benchmarks/quick-xml/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/benchmarks/quick-xml/Cargo.lock b/benchmarks/quick-xml/Cargo.lock
new file mode 100644
index 0000000..33fc320
--- /dev/null
+++ b/benchmarks/quick-xml/Cargo.lock
@@ -0,0 +1,25 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quick-xml-bench"
+version = "0.1.0"
+dependencies = [
+ "quick-xml",
+]
diff --git a/benchmarks/quick-xml/Cargo.toml b/benchmarks/quick-xml/Cargo.toml
new file mode 100644
index 0000000..fb1a66b
--- /dev/null
+++ b/benchmarks/quick-xml/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "quick-xml-bench"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+quick-xml = "0.41.0"
+
+[profile.release]
+opt-level = 3
+lto = true
+codegen-units = 1
+panic = "abort"
+
diff --git a/benchmarks/quick-xml/benchmark-results.xml b/benchmarks/quick-xml/benchmark-results.xml
new file mode 100644
index 0000000..b40abb0
--- /dev/null
+++ b/benchmarks/quick-xml/benchmark-results.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<benchmarks library="quick-xml">
+ <benchmark name="count_highway_crossings">
+ <res>229</res>
+ <time unit="ns">510085538</time>
+ <throughput unit="MiB/s">892.6964484824999</throughput>
+ <energy-consumption unit="J/GiB">33.06487793812473</energy-consumption>
+ </benchmark>
+ <benchmark name="count_highway_crossings_cheat_and_early_out">
+ <res>229</res>
+ <time unit="ns">362069942</time>
+ <throughput unit="MiB/s">1257.634217520562</throughput>
+ <energy-consumption unit="J/GiB">23.364528672792822</energy-consumption>
+ </benchmark>
+ <benchmark name="pubmed_len">
+ <res>74757</res>
+ <time unit="ns">173540</time>
+ <throughput unit="MiB/s">847.5963209608781</throughput>
+ </benchmark>
+ <benchmark name="pubmed_len_with_entities">
+ <res>74757</res>
+ <time unit="ns">195641</time>
+ <throughput unit="MiB/s">751.8458070626851</throughput>
+ </benchmark>
+</benchmarks>
+
diff --git a/benchmarks/quick-xml/src/count_highway_crossings.rs b/benchmarks/quick-xml/src/count_highway_crossings.rs
new file mode 100644
index 0000000..9b68142
--- /dev/null
+++ b/benchmarks/quick-xml/src/count_highway_crossings.rs
@@ -0,0 +1,74 @@
+use quick_xml::events::Event;
+use quick_xml::Reader;
+
+pub fn count_highway_crossings(buffer: &[u8]) -> usize {
+ let mut reader = Reader::from_reader(buffer);
+ reader.config_mut().check_end_names = false;
+
+ let mut count = 0usize;
+ let mut in_node = false;
+
+ loop {
+ match reader.read_event() {
+ Ok(Event::Eof) => break,
+
+ Ok(Event::Start(e)) if e.name().as_ref() == b"node" => {
+ in_node = true;
+ }
+
+ Ok(Event::End(e)) if e.name().as_ref() == b"node" => {
+ in_node = false;
+ }
+
+ Ok(Event::Start(e)) | Ok(Event::Empty(e))
+ if in_node && e.name().as_ref() == b"tag" =>
+ {
+ let mut attrs = e.attributes();
+ if attrs.next().unwrap().unwrap().value.as_ref() == b"highway"
+ && attrs.next().unwrap().unwrap().value.as_ref() == b"crossing" {
+ count += 1;
+ }
+ }
+
+ Ok(_) => {}
+ Err(_) => break,
+ }
+ }
+
+ count
+}
+
+pub fn count_highway_crossings_cheat_and_early_out(buffer: &[u8]) -> usize {
+ let mut reader = Reader::from_reader(buffer);
+ reader.config_mut().check_end_names = false;
+
+ let mut count = 0usize;
+
+ loop {
+ match reader.read_event() {
+ Ok(Event::Eof) => break,
+
+ Ok(Event::Start(e)) | Ok(Event::Empty(e))
+ if e.name().as_ref() == b"tag" =>
+ {
+ let mut attrs = e.attributes();
+ if attrs.next().unwrap().unwrap().value.as_ref() == b"highway"
+ && attrs.next().unwrap().unwrap().value.as_ref() == b"crossing" {
+ count += 1;
+ }
+ }
+
+ Ok(Event::Start(e)) | Ok(Event::Empty(e))
+ if e.name().as_ref() == b"way" =>
+ {
+ break;
+ }
+
+ Ok(_) => {}
+ Err(_) => break,
+ }
+ }
+
+ count
+}
+
diff --git a/benchmarks/quick-xml/src/main.rs b/benchmarks/quick-xml/src/main.rs
new file mode 100644
index 0000000..fef0ee7
--- /dev/null
+++ b/benchmarks/quick-xml/src/main.rs
@@ -0,0 +1,39 @@
+use std::fs;
+
+#[path = "../../harness.rs"]
+mod harness;
+use harness::*;
+
+mod pubmed_len;
+use pubmed_len::*;
+mod count_highway_crossings;
+use count_highway_crossings::*;
+
+macro_rules! bench {
+ ($f:expr, $buffer:expr, $out:expr) => {
+ bench(String::from(stringify!($f)), $f, $buffer.as_slice(), &mut $out)
+ };
+}
+
+fn main() {
+ let mut out = String::from(
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
+ <benchmarks library=\"quick-xml\">\n",
+ );
+
+ if true {
+ let buffer = fs::read("../../dat/yellowstone.osm").expect("Failed to read file");
+ bench!(count_highway_crossings, buffer, out);
+ bench!(count_highway_crossings_cheat_and_early_out, buffer, out);
+ }
+
+ if true {
+ let buffer = fs::read("../../dat/PMC176545.xml").expect("Failed to read file");
+ bench!(pubmed_len, buffer, out);
+ bench!(pubmed_len_with_entities, buffer, out);
+ }
+
+ out.push_str("</benchmarks>\n\n");
+ std::fs::write("benchmark-results.xml", out).unwrap();
+}
+
diff --git a/benchmarks/quick-xml/src/pubmed_len.rs b/benchmarks/quick-xml/src/pubmed_len.rs
new file mode 100644
index 0000000..592e6e3
--- /dev/null
+++ b/benchmarks/quick-xml/src/pubmed_len.rs
@@ -0,0 +1,82 @@
+use quick_xml::events::Event;
+use quick_xml::Reader;
+
+pub fn pubmed_len(buffer: &[u8]) -> usize {
+ let mut reader = Reader::from_reader(buffer);
+ let mut in_content = 0usize;
+ let mut total = 0usize;
+
+ loop {
+ match reader.read_event() {
+ Ok(Event::Start(e)) => {
+ if in_content != 0 {
+ in_content += 1;
+ } else {
+ if e.name().as_ref() == b"abstract"
+ || e.name().as_ref()== b"body" {
+ in_content = 1;
+ }
+ }
+ }
+
+ Ok(Event::End(_)) => {
+ if in_content != 0 {
+ in_content -= 1;
+ }
+ }
+
+ Ok(Event::Text(e)) => {
+ if in_content != 0 {
+ total += e.len();
+ }
+ }
+
+ Ok(Event::Eof) => break,
+
+ Ok(_) => {}
+ Err(_) => unreachable!("buffer is valid PMC XML"),
+ }
+ }
+
+ total
+}
+
+pub fn pubmed_len_with_entities(buffer: &[u8]) -> usize {
+ let mut reader = Reader::from_reader(buffer);
+ let mut in_content = 0usize;
+ let mut total = 0usize;
+
+ loop {
+ match reader.read_event() {
+ Ok(Event::Start(e)) => {
+ if in_content != 0 {
+ in_content += 1;
+ } else if e.name().as_ref() == b"abstract"
+ || e.name().as_ref() == b"body"
+ {
+ in_content = 1;
+ }
+ }
+
+ Ok(Event::End(_)) => {
+ if in_content != 0 {
+ in_content -= 1;
+ }
+ }
+
+ Ok(Event::Text(e)) => {
+ if in_content != 0 {
+ total += e.decode().unwrap().len();
+ }
+ }
+
+ Ok(Event::Eof) => break,
+
+ Ok(_) => {}
+ Err(_) => unreachable!("buffer is valid PMC XML"),
+ }
+ }
+
+ total
+}
+
diff --git a/download_benchmark_data.sh b/download_benchmark_data.sh
new file mode 100644
index 0000000..efb28b3
--- /dev/null
+++ b/download_benchmark_data.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+mkdir -p dat
+wget -nc https://imxml.vandorp.lu/dat/PMC176545.xml -P dat
+wget -nc https://imxml.vandorp.lu/dat/yellowstone.osm -P dat
+
diff --git a/examples/c89.c b/examples/c89.c
new file mode 100644
index 0000000..4b0eaf6
--- /dev/null
+++ b/examples/c89.c
@@ -0,0 +1,33 @@
+#include <assert.h>
+#include <stdio.h>
+#define ASSERT assert
+#define IMXML_SIMD_DEFAULT SSE2
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+
+int main (void) {
+ ImxmlString s, from, to, msg;
+ XmlContent content;
+ ImxmlCharSequence const xml = "<msg from=\"John\" to=\"Jane\">Hello, World!</msg>"IMXML_LIT_PAD;
+ XmlParser p = {0};
+ p.head = xml;
+
+ imxml_strlit89(s, "msg");
+ assert(xml_tag_expect_str(&p, s));
+ imxml_strlit89(s, "from");
+ assert(xml_attr_expect_str(&p, s));
+ from = xml_value(&p);
+ imxml_strlit89(s, "to");
+ assert(xml_attr_expect_str(&p, s));
+ to = xml_value(&p);
+
+ content = xml_content(&p);
+ assert(content.type == XML_CONTENT_TEXT);
+ msg = content.data;
+ assert(xml_content(&p).type == XML_CONTENT_TAG);
+
+ printf("%.*s->%.*s: '%.*s'\n", (int)from.count, from.items, (int)to.count, to.items, (int)msg.count, msg.items);
+
+ return 0;
+}
+
diff --git a/examples/nolibc.c b/examples/nolibc.c
new file mode 100644
index 0000000..1a68383
--- /dev/null
+++ b/examples/nolibc.c
@@ -0,0 +1,71 @@
+#define assert(X) do { if (!(X)) __asm__("int3"); } while (0)
+
+#define IMXML_SIMD_DEFAULT SCALAR
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES
+#define IMXML_NO_SUPPORT_COMMENTS
+#define IMXML_NO_SUPPORT_CDATA
+#define IMXML_NO_SUPPORT_ENTITIES
+#define IMXML_NO_SUPPORT_NAMESPACES
+#define THREADLOCAL
+#define IMXML_NO_CHECK_BOUNDS
+
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+
+// some compilers require memset to be present
+void* memset (void* s, int c, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
+ ((char*)s)[i] = c;
+ }
+ return s;
+}
+
+static void write (ImxmlString s) {
+ long ret;
+ __asm__ volatile(
+ "syscall"
+ : "=a"(ret)
+ : "a"(1), "D"(1), "S"(s.items), "d"(s.count)
+ : "rcx", "r11", "memory"
+ );
+}
+
+void start (void) {
+ static ImxmlCharSequence xml = "<msg from=\"John\" to=\"Jane\">Hello, World!</msg>"IMXML_LIT_PAD;
+
+ XmlParser p = {
+ .head = xml,
+ };
+
+ assert(xml_tag_expect(&p, "msg"));
+ assert(xml_attr_expect(&p, "from"));
+ ImxmlString const from = xml_value(&p);
+ assert(xml_attr_expect(&p, "to"));
+ ImxmlString const to = xml_value(&p);
+
+ XmlContent content = xml_content(&p);
+ assert(content.type == XML_CONTENT_TEXT);
+ ImxmlString const msg = content.data;
+
+ write(from);
+ write(imxml_strlit("->"));
+ write(to);
+ write(imxml_strlit(": "));
+ write(msg);
+ write(imxml_strlit("\n"));
+
+ __asm__ volatile(
+ "syscall\n"
+ :
+ : "a"(60), "D"(0)
+ : "rcx", "r11", "memory"
+ );
+}
+
+void _start (void) {
+ __asm__ volatile (
+ "andq $-16, %rsp\n" // required because the compiler emits aligned stack loads later
+ "call start\n"
+ );
+}
+
diff --git a/examples/simple.c b/examples/simple.c
new file mode 100644
index 0000000..caacc83
--- /dev/null
+++ b/examples/simple.c
@@ -0,0 +1,27 @@
+#include <assert.h>
+#include <stdio.h>
+#define ASSERT assert
+#define IMXML_SIMD_DEFAULT SSE2
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+
+int main (void) {
+ ImxmlCharSequence const xml = "<msg from=\"John\" to=\"Jane\">Hello, World!</msg>"IMXML_LIT_PAD;
+ XmlParser p = {
+ .head = xml,
+ };
+
+ assert(xml_tag_expect(&p, "msg"));
+ assert(xml_attr_expect(&p, "from"));
+ ImxmlString const from = xml_value(&p);
+ assert(xml_attr_expect(&p, "to"));
+ ImxmlString const to = xml_value(&p);
+
+ XmlContent content = xml_content(&p);
+ assert(content.type == XML_CONTENT_TEXT);
+ ImxmlString msg = content.data;
+ assert(xml_content(&p).type == XML_CONTENT_TAG);
+
+ printf("%.*s->%.*s: '%.*s'\n", (int)from.count, from.items, (int)to.count, to.items, (int)msg.count, msg.items);
+}
+
diff --git a/imxml.h b/imxml.h
new file mode 100644
index 0000000..7ed0563
--- /dev/null
+++ b/imxml.h
@@ -0,0 +1,1934 @@
+#ifndef IMXML_H_
+#define IMXML_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* TODO
+ - a nice way of handling entities inside attribute values. Maybe generalize xml_content
+ - recursive entity references
+ - normalization
+ - end-of-line normalization (CR and CRLF to LF)
+ - trim whitespace
+ - attribute-value whitespace normalization
+ - collapse whitespace
+
+ MAYBE/IDEAS
+ - signal handling helper
+ - DTD validation helper functions, maybe?
+ - XPath
+ - SYSTEM entities
+ - ESP port
+ - XSLT
+ - schema to code transpiler
+*/
+
+/* ===== DOCUMENTATION =====
+ * === EXPERIMENTAL ===
+ * This library is still in an experimental stage and should not be used in
+ * environments where stability/safety/security/reliability are important.
+ * Some minor features like normalization and recursive entity references
+ * aren't supported yet.
+ *
+ * While this library is in an experimental state, semantic versioning
+ * conventions will not be respected and minor updates may introduce breaking
+ * API/ABI changes. Semantic versioning will be respected once this library
+ * hits 1.0.
+ *
+ * === COMPATIBILITY ===
+ * Standard C89 (ANSI C) and C++98 are supported. The library has been tested with
+ * clang, gcc, and tcc. Every standards compliant C compiler should work,
+ * though if emmintrin.h/immintrin.h isn't supported, only SCALAR will work
+ * (unless you define your own instruction set).
+ *
+ * UTF-8, ISO-Latin-1, ASCII, and in general all char-based encodings are
+ * supported. UTF-16 is not supported.
+ *
+ * === USAGE ===
+ * This API is intended to be used as part of a while (true) loop where you
+ * break when `xml_tag() == "/your-root"`. For specific examples, see the
+ * examples/ and benchmarks/imxml/ directories. Here is a basic example for
+ * counting the number of highway crossing nodes are in a OSM file:
+
+#define IMXML_LINUX // We're using Linux, of course
+#define IMXML_NO_SUPPORT_SINGLE_QUOTES // OSM always uses double-quotes
+#define IMXML_NO_SUPPORT_COMMENTS // OSM doesn't include comments
+#define IMXML_NO_SUPPORT_CDATA // OSM doesn't include CDATA
+#define IMXML_NO_SUPPORT_ENTITIES // We don't need to parse entities
+#define IMXML_NO_SUPPORT_NAMESPACES // OSM doesn't include namespaces
+#define IMXML_NO_CHECK_BOUNDS // We know our data will end with </osm>
+#define THREADLOCAL // We disable thread-local by defining it as nothing
+#define IMXML_IMPLEMENTATION
+#include "imxml.h"
+#include <stdio.h> // for printf
+
+int main (void) {
+ size_t file_size;
+ // We need to use this file_open function because the input data needs to
+ // be padded to make sure we're not reading out-of-bounds
+ char* const file = imxml_linux_file_open("dat/yellowstone.osm", &file_size, false);
+ if (file == 0) return -1;
+ XmlParser p = {0};
+ p.head = file;
+
+ // Verify we're really dealing with osm data. This is mostly unnecessary,
+ // but it doesn't cost much and might catch some dumb bugs
+ if (!xml_parse_header(&p, NULL)) goto err;
+ if (!xml_tag_expect(&p, "osm")) goto err;
+ if (!xml_has_children(&p)) goto err;
+ if (!xml_tag_expect(&p, "bounds")) goto err;
+ if (xml_has_children(&p)) goto err;
+
+ size_t highway_crossing_count = 0;
+ while (true) {
+ ImxmlString tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("node"))) {
+ if (xml_has_children(&p)) {
+ while (true) {
+ tag = xml_tag(&p);
+ if (imxml_streql(tag, imxml_strlit("/node"))) break;
+ ImxmlString key = xml_value(&p);
+ if (!imxml_streql(key, imxml_strlit("highway"))) continue;
+ ImxmlString value = xml_value(&p);
+ if (imxml_streql(value, imxml_strlit("crossing"))) {
+ highway_crossing_count += 1;
+ }
+ }
+ }
+ } else {
+ // encountered non-node tag, which means we can early-out (because
+ // OSM always groups all of the nodes up-front).
+ // Alternatively, we could check if tag == "/osm", but then we'd
+ // just be skipping over all the <way> and <relation> tags, which
+ // would be a waste of time
+ break;
+ }
+ }
+
+ printf("%zu\n", highway_crossing_count);
+ imxml_linux_file_close(file, file_size);
+ return 0;
+
+ err:
+ return -1;
+}
+
+ *
+ * === PERFORMANCE FLAGS AND #DEFINES ===
+ * These flags each measurably improve performance in at least some situations,
+ * at the cost of runtime safety and/or standards compliance. Most of these
+ * will only give you a few percent better performance, with the exception of
+ * IMXML_NO_SUPPORT_NAMESPACES and IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD, which
+ * can impact parse-times by about 30%. It's highly recommended to disable
+ * namespace support entirely, since it's usually not necessary.
+ * IMXML_NO_CHECK_BOUNDS
+ * If set, the parser skips checks for NUL terminators. It's
+ * recommended to enable this only if you can guarantee that the XML will be
+ * valid. If you can guarantee valid XML, set this flag and break on `xml_tag
+ * == "/root-node"` to improve performance. Even if you can't guarantee valid
+ * XML, consider handling SIGSEGV and SIGBUS instead of enabling bounds
+ * checking to improve performance.
+ * IMXML_NO_SUPPORT_COMMENTS
+ * If set, the parser assumes <!--XML comments--> will not occur in the
+ * input. Only set this flag if you can guarantee that your XML contains no
+ * comments. If a comment is encountered when this flag is set, the parser
+ * will break in unpredictable ways.
+ * IMXML_NO_SUPPORT_CDATA
+ * If set, the parser assumes <![CDATA[XML CDATA]]> will not occur in the
+ * input. Only set this flag if you can guarantee that your XML contains no
+ * CDATA. If CDATA is encountered when this flag is set, the parser will
+ * break in unpredictable ways.
+ * IMXML_NO_SUPPORT_SINGLE_QUOTES
+ * If set, the parser assumes no attribute values (except the XML in the
+ * header) will be enclosed by 'single quotes'. This flag is relevant even if
+ * you don't read the attribute values, only disable it if you can guarantee
+ * there are either no attribute values in the XML, or all attribute values
+ * are enclosed in "double quotes". This flag is usually mutually exclusive
+ * with IMXML_NO_SUPPORT_DOUBLE_QUOTES, but technically both can be set if
+ * you can guarantee that there are no attribute values in the XML, which
+ * will improve performance and reduce code size
+ * IMXML_NO_SUPPORT_DOUBLE_QUOTES
+ * Same as IMXML_NO_SUPPORT_SINGLE_QUOTES, but for "double quotes"
+ * IMXML_NO_SUPPORT_NAMESPACES
+ * If set, the parser will not manage namespaces. This is by far the most
+ * performance-critical flag and it's heavily recommended to set it if at all
+ * possible. Even when this flag is set, you can check xml_attr for
+ * "xmlns", check xml_tag for prefixes (you can still use
+ * xml_tag_remove_namespace for convenience), and manage namespaces yourself,
+ * which is almost certain to be more efficient than this general solution.
+ * IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD
+ * If set, imxml_default_namespace_resolve does not resolve namespaces
+ * defined within the current tag. E.g. The namespace of `foo` in `<foo
+ * xmlns="foo.com">` would NOT be "foo.com", but instead the default
+ * namespace of the enclosing tag (which may be empty ""). This lookahead is
+ * rarely necessary in practice and significantly impacts performance, so,
+ * even if you're using the library's default namespace handling (which
+ * itself is rarely optimal), it's strongly recommended to disable this if
+ * possible.
+ * IMXML_NO_SUPPORT_ENTITIES
+ * If set, xml_content ignores &entities;. This is technically safe to
+ * set, even if you can't guarantee that your XML does not contain entities.
+ * This means that entities will simply be reported as regular text, which is
+ * fine if you don't care about resolving the entities.
+ * IMXML_NO_SUPPORT_CUSTOM_ENTITIES
+ * If set, the parser ignores !ENTITY definitions in <!DOCTYPE> and will not
+ * resolve non-standard &entities;. Note that this flag will be ignored if
+ * IMXML_NO_SUPPORT_ENTITIES is set.
+ * IMXML_SIMD_DEFAULT and IMXML_SIMD_DENSE
+ * IMXML_SIMD_DEFAULT determines which instruction set is used for parsing
+ * when it's probable that a structurally relevant character WILL NOT be
+ * within a single vector window (e.g. when parsing content).
+ * IMXML_SIMD_DENSE determines which instruction set is used for parsing when
+ * it's probable that a structurally relevant character WILL be within a
+ * single vector window (e.g. when parsing tag arguments and values).
+ * Possible values are SCALAR, MMX, SSE2, and AVX2. Defaults to SSE2. When
+ * IMXML_SIMD_DEFAULT is SSE2 or lower, IMXML_SIMD_DENSE will default ot
+ * IMXML_SIMD_DEFAULT. If it's higher, IMXML_SIMD_DENSE will be set to SSE2.
+ * If you change these, be sure to benchmark, as it's not uncommon for SSE2
+ * to outperform AVX2 in certain configurations for certain workloads. It's
+ * even possible for SCALAR to outperform everything else depending on the
+ * exact data, worload, configuration, compiler, and optimization flags.
+ *
+ * === OTHER FLAGS AND #DEFINES ===
+ * IMXML_LIT_PAD
+ * This is just for convenience. You can use it to pad string literals in
+ * code so the SIMD operations don't read out-of-bounds. Usage example
+ * `ImxmlCharSequence xml = "<foo bar="baz"/>" IMXML_LIT_PAD;`
+ * IMXML_CUSTOM_STDBOOL_H
+ * If set, the library will not include stdint.h, and you must provide
+ * `uint8_t`, `uint16_t`, `uint32_t`, and `SIZE_MAX`.
+ * IMXML_CUSTOM_STDINT_H
+ * If set, the library will not include stdbool.h, and you must provide
+ * `bool`, `true`, and `false`.
+ * IMXML_CUSTOM_SIMD_INTRINSICS
+ * If set, the library will not include emmintrin.h/immintrin.h, and you must
+ * provide all necessary SIMD intrinsics. This should only be necessary if
+ * your compiler doesn't support those headers, and/or if you're generating
+ * custom imxml_seek__next_char_XXX functions with different intrinsics.
+ * IMXML_SIMD_DEFAULT_INSTRUCTIONS and IMXML_SIMD_DENSE_INSTRUCTIONS
+ * These are the arguments passed to the imxml_seek_next_char_XXX functions.
+ * You should only set these if you need to support an instruction set that
+ * isn't supported by this library (i.e. MMX, SSE2, AVX2).
+ * IMXMLAPI
+ * Defaults to `static inline`. If you want to compile this library into an
+ * object file, you might want to change this.
+ * IMXML_CUSTOM_CHAR
+ * If set, you must define a `ImxmlChar` type. You almost certainly don't
+ * want to do that. `sizeof(ImxmlChar) != 1` is UB.
+ * IMXML_CUSTOM_CHAR_SEQUENCE
+ * If set, you must define a `ImxmlCharSequence` type. This is useful if you
+ * also have a custom string type (see IMXML_CUSTOM_STRING) that uses
+ * non-const data.
+ * IMXML_CUSTOM_STRING
+ * If set, you must define a `ImxmlString` type. You must also define
+ * IMXML_STRCOUNT and IMXML_STRITEMS as the identifier for your string's
+ * count/length and items/chars field respectively. Furthermore, you must
+ * implement/define the following functions/macros: imxml_strsubcstr,
+ * imxml_strlit, imxml_strsubcstr, imxml_streql.
+ * IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE
+ * The maximum number of custom entities the default custom entity
+ * implementation can support. Note that if more entities are encountered,
+ * they will be silently ignored. Irrelevant if
+ * IMXML_NO_SUPPORT_CUSTOM_ENTITIES is set. Defaults to 255.
+ * IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE
+ * The maximum namespace depth the default implementation can support. Note
+ * that if namespaces encountered after this maximum has been reached will be
+ * silently ignored. Irrelevant if IMXML_NO_SUPPORT_NAMESPACES is set.
+ * Defaults to 255.
+ * THREADLOCAL
+ * You can define THREADLOCAL to be nothing (e.g. `#define THREADLOCAL`) if
+ * you know you won't be using multithreading (this can improve performance
+ * and simplifies the emitted code), or if you have one parser that calls
+ * default entity/namespace implementation functions on multiple threads
+ * (though that's not recommended and you should probably consider just
+ * creating your own implementations at that point).
+ *
+ * === MULTITHREADING ===
+ * This library, by default, has rudimentary thread-safety in the form of
+ * thread-local global variables. Thread-safety is only guaranteed when a
+ * single parser runs in a thread. If there are multiple parsers running
+ * concurrently in the same thread, or one parser runs in multiple threads,
+ * things will break. Note that, in practice, the only reasonable
+ * multi-threading approach with this library is parsing multiple different
+ * documents (with different parsers) on separate threads. Note that this
+ * library will max out a core, so it's generally best to not run more threads
+ * than the number of physical cores on the machine.
+ *
+ * === A NOTE ON THE DEFAULT CUSTOM ENTITY AND NAMESPACE IMPLEMENTATIONS ===
+ * The imxml_default_XXX functions are included so you don't have to implement
+ * your own if you're just doing some quick and dirty parsing. But if you
+ * intend on using this library in a more sophisticated context, these default
+ * implementations should be replaced by custom code for both performance and
+ * correctness reasons. The two correctness-related issues with the default
+ * implementations are that they are limited to a static
+ * entity-size/namespace-depth, and that they are only thread-safe if each
+ * parser runs in its own thread.
+ *
+ * Creating a custom implementation for custom entities is relatively easy: You
+ * just need to call xml_parse_header with a custom register_entity callback
+ * and call xml_entity_resolve with a custom get_entity callback.
+ *
+ * Creating a custom implementation for namespace management is substantially
+ * more involved and not recommended unless you are parsing arbitrary and
+ * unboundable XML. Firstly, you need to set the namespace_push and
+ * namespace_pop callbacks in the XmlParser struct. Beside that, you also need
+ * to provide imxml_default_ns_pop_to_depth_. I'll note again that, in all
+ * likelihood, this is not what you want to be doing. You're almost certainly
+ * able to use the regular xml_tag, xml_attr, and xml_value APIs to accurately
+ * resolve namespaces in a more readable and performant way than would be
+ * possible with a generalized namespace management solution.
+ */
+
+/* The numbers here aren't super important, we just care that smaller
+ * instruction sets are smaller so we can compare them. */
+#define SCALAR 100
+#define MMX 6400 /*TODO maybe MMX_NO_EMMS and MMX_FEMS ?*/
+#define SSE2 12800
+#define AVX2 25600
+
+#ifndef IMXML_SIMD_DEFAULT
+#define IMXML_SIMD_DEFAULT SSE2
+#endif
+#ifndef IMXML_SIMD_DENSE
+#if IMXML_SIMD_DEFAULT >= SSE2
+#define IMXML_SIMD_DENSE SSE2
+#else
+#define IMXML_SIMD_DENSE IMXML_SIMD_DEFAULT
+#endif /*IMXML_SIMD_DEFAULT >= SSE2*/
+#endif /*ifndef IMXML_SIMD_DENSE*/
+
+#define IMXML_SCALAR (IMXML_SIMD_DEFAULT == SCALAR || IMXML_SIMD_DENSE == SCALAR)
+#define IMXML_MMX (IMXML_SIMD_DEFAULT == MMX || IMXML_SIMD_DENSE == MMX)
+#define IMXML_SSE2 (IMXML_SIMD_DEFAULT == SSE2 || IMXML_SIMD_DENSE == SSE2)
+#define IMXML_AVX2 (IMXML_SIMD_DEFAULT == AVX2 || IMXML_SIMD_DENSE == AVX2)
+
+#include <stddef.h>
+#if !defined(IMXML_CUSTOM_STDBOOL_H) && defined(__STDC_VERSION__)
+#include <stdbool.h>
+#endif
+#ifndef IMXML_CUSTOM_STDINT_H
+#include <stdint.h>
+#endif
+#if !defined(IMXML_CUSTOM_SIMD_INTRINSICS) && !IMXML_SCALAR
+#if IMXML_AVX2
+#include <immintrin.h>
+#else
+#include <emmintrin.h>
+#endif /*AVX2*/
+#endif /*IMXML_CUSTOM_SIMD_INTRINSICS*/
+
+#ifndef NODISCARD
+#if defined(__clang__) || defined(__GNUC__)
+#define NODISCARD __attribute__((warn_unused_result))
+#else
+#define NODISCARD
+#endif
+#endif
+
+#ifndef ASSERT
+#ifndef NDEBUG
+#define ASSERT(X) if (UNLIKELY(!(X))) __asm__("int3")
+#else
+#define ASSERT(X)
+#endif
+#endif
+
+#ifndef ASSUME
+#ifndef NDEBUG
+#define ASSUME ASSERT
+#else /*NDEBUG*/
+#if defined(__clang__)
+#define ASSUME __builtin_assume
+#elif defined(__GNUC__)
+#define ASSUME(X) if (!(X)) __builtin_unreachable()
+#else
+#define ASSUME(X)
+#endif
+#endif /*NDEBUG*/
+#endif /*ASSUME*/
+
+#ifndef ALIGN
+#if defined(__clang__) || defined(__GNUC__)
+#define ALIGN(X) __attribute__((aligned(X)))
+#else
+#define ALIGN(X)
+#endif
+#endif
+
+#ifndef UNLIKELY
+#if defined(__clang__) || defined(__GNUC__)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define UNLIKELY(x) (x)
+#endif
+#endif
+#ifndef LIKELY
+#if defined(__clang__) || defined(__GNUC__)
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#else
+#define LIKELY(x) (x)
+#endif
+#endif
+
+#ifndef THREADLOCAL
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define THREADLOCAL _Thread_local
+#elif defined(__clang__) || defined(__GNUC__)
+#define THREADLOCAL __thread
+#else
+#error imxml: THREADLOCAL undefined. Use C11 or newer, clang, gcc, or define \
+THREADLOCAL yourself. You may define it as nothing (i.e. `#define \
+THREADLOCAL`), though that may cause issues when using default entity and \
+namespace implementations, and unicode entity decoding in a multithreaded \
+context.
+#endif
+#endif
+
+#ifndef __STDC_VERSION__
+/* C89 compatibility */
+#if defined(__clang__) || defined(__GNUC__)
+#define inline __inline__
+#define restrict __restrict__
+#else
+#define inline /*inline*/
+#define restrict /*restrict*/
+#endif
+
+#define bool uint8_t
+#define true 1
+#define false 0
+#endif /*__STDC_VERSION__*/
+
+#ifndef IMXML_NO_CHECK_BOUNDS
+#define IMXML_CHECK_BOUNDS
+#endif
+#ifndef IMXML_NO_SUPPORT_COMMENTS
+#define IMXML_SUPPORT_COMMENTS
+#endif
+#ifndef IMXML_NO_SUPPORT_CDATA
+#define IMXML_SUPPORT_CDATA
+#endif
+#ifndef IMXML_NO_SUPPORT_SINGLE_QUOTES
+#define IMXML_SUPPORT_SINGLE_QUOTES
+#endif
+#ifndef IMXML_NO_SUPPORT_DOUBLE_QUOTES
+#define IMXML_SUPPORT_DOUBLE_QUOTES
+#endif
+#ifndef IMXML_NO_SUPPORT_NAMESPACES
+#define IMXML_SUPPORT_NAMESPACES
+#endif
+#ifndef IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD
+#define IMXML_SUPPORT_NAMESPACE_LOOKAHEAD
+#endif
+#ifndef IMXML_NO_SUPPORT_ENTITIES
+#define IMXML_SUPPORT_ENTITIES
+#ifndef IMXML_NO_SUPPORT_CUSTOM_ENTITIES
+#define IMXML_SUPPORT_CUSTOM_ENTITIES
+#endif
+#endif /*IMXML_NO_SUPPORT_ENTITIES*/
+
+#ifdef IMXML_CHECK_BOUNDS
+#define IF_BOUNDS(x) x
+#else
+#define IF_BOUNDS(x)
+#endif
+#ifdef IMXML_SUPPORT_SINGLE_QUOTES
+#define IF_SINGLE_QUOTES(x) x
+#else
+#define IF_SINGLE_QUOTES(x)
+#endif
+#ifdef IMXML_SUPPORT_DOUBLE_QUOTES
+#define IF_DOUBLE_QUOTES(x) x
+#else
+#define IF_DOUBLE_QUOTES(x)
+#endif
+#ifdef IMXML_SUPPORT_COMMENTS
+#define IF_COMMENTS(x) x
+#else
+#define IF_COMMENTS(x)
+#endif
+#ifdef IMXML_SUPPORT_CDATA
+#define IF_CDATA(x) x
+#else
+#define IF_CDATA(x)
+#endif
+#if defined(IMXML_SUPPORT_CDATA) || defined(IMXML_SUPPORT_COMMENTS)
+#define IF_COMMENTS_OR_CDATA(x) x
+#else
+#define IF_COMMENTS_OR_CDATA(x)
+#endif
+
+#define scalar_set1(x) ((uint8_t)(x))
+#define scalar_loadu(p) (*(p))
+#define scalar_cmpeq(a, b) ((uint8_t)((a) == (b) ? 0xffu : 0u))
+#define scalar_or(a, b) ((uint8_t)((a) | (b)))
+#define scalar_movemask(x) ((unsigned)((x) != 0))
+#define IMXML_SCALAR_INSTRUCTIONS 1, uint8_t, uint8_t, uint8_t, scalar_set1, scalar_loadu, scalar_cmpeq, scalar_or, scalar_movemask, {}, {}
+
+#define mmx_loadu(PTR) *(__m64 const*)PTR /*TODO this is actually an unaligned load, which may crash on some platforms*/
+#define IMXML_MMX_INSTRUCTIONS 8, uint8_t, __m64, void, _mm_set1_pi8, mmx_loadu, _mm_cmpeq_pi8, _mm_or_si64, _mm_movemask_pi8, {},_mm_empty();
+
+#define IMXML_SSE2_INSTRUCTIONS 16, uint16_t, __m128i, __m128i_u, _mm_set1_epi8, _mm_loadu_si128, _mm_cmpeq_epi8, _mm_or_si128, _mm_movemask_epi8, {}, {}
+
+#define IMXML_AVX2_INSTRUCTIONS 32, uint32_t, __m256i, __m256i_u, _mm256_set1_epi8, _mm256_loadu_si256, _mm256_cmpeq_epi8, _mm256_or_si256, _mm256_movemask_epi8, {}, {}
+
+#ifndef IMXML_SIMD_DEFAULT_INSTRUCTIONS
+#if IMXML_SIMD_DEFAULT == SCALAR
+#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_SCALAR_INSTRUCTIONS
+#elif IMXML_SIMD_DEFAULT == MMX
+#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_MMX_INSTRUCTIONS
+#elif IMXML_SIMD_DEFAULT == SSE2
+#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_SSE2_INSTRUCTIONS
+#elif IMXML_SIMD_DEFAULT == AVX2
+#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_AVX2_INSTRUCTIONS
+#endif
+#endif
+#ifndef IMXML_SIMD_DENSE_INSTRUCTIONS
+#if IMXML_SIMD_DENSE == SCALAR
+#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_SCALAR_INSTRUCTIONS
+#elif IMXML_SIMD_DENSE == MMX
+#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_MMX_INSTRUCTIONS
+#elif IMXML_SIMD_DENSE == SSE2
+#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_SSE2_INSTRUCTIONS
+#elif IMXML_SIMD_DENSE == AVX2
+#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_AVX2_INSTRUCTIONS
+#endif
+#endif
+
+#define IMXML_LIT_PAD "\0""123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" /* NUL + 63 bytes for AVX512 */
+
+/* === API === */
+
+#ifndef IMXMLAPI
+/* NOTE: inline gains about 5% in certain cases, and does't ever seem to hurt,
+ * so I'm enabling it by default */
+#define IMXMLAPI static inline
+#endif
+
+#ifndef IMXML_CUSTOM_CHAR
+/* NOTE sizeof(ImxmlChar) must be 1 */
+typedef char ImxmlChar;
+#endif
+#ifndef IMXML_CUSTOM_CHAR_SEQUENCE
+typedef ImxmlChar const* ImxmlCharSequence;
+#endif
+
+#ifndef IMXML_CUSTOM_STRING
+typedef size_t ImxmlStrLen;
+typedef struct {
+ ImxmlStrLen count;
+ ImxmlChar const* items;
+} ALIGN(16) ImxmlString;
+#define IMXML_STRCOUNT count
+#define IMXML_STRITEMS items
+#else /*IMXML_CUSTOM_STRING*/
+#ifndef IMXML_STRCOUNT
+#error Must define IMXML_STRCOUNT as the `count`/`size` field of your string struct
+#endif /*IMXML_STRCOUNT*/
+#ifndef IMXML_STRITEMS
+#error Must define IMXML_STRITEMS as the `items`/`chars` field of your string struct
+#endif /*IMXML_STRITEMS*/
+#endif /*IMXML_CUSTOM_STRING*/
+
+typedef struct {
+ ImxmlString prefix;
+ ImxmlString ns;
+} ALIGN(32) ImxmlNamespace;
+
+typedef struct {
+ ImxmlString current_tag;
+ ImxmlCharSequence head;
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ size_t depth;
+ void (*namespace_push)(size_t depth, ImxmlString prefix, ImxmlString ns);
+ void (*namespace_pop)(void);
+ #endif
+ bool in_tag;
+} ALIGN(64) XmlParser;
+
+typedef enum {
+ XML_CONTENT_NONE = 0,
+ XML_CONTENT_TEXT,
+ XML_CONTENT_ENTITY,
+ XML_CONTENT_TAG,
+ XML_CONTENT_COMMENT
+} XmlContentType;
+typedef struct {
+ ImxmlString data;
+ XmlContentType type;
+} ALIGN(32) XmlContent;
+typedef struct {
+ ImxmlString key;
+ ImxmlString value;
+} ALIGN(32) ImxmlEntityDefinition;
+
+IMXMLAPI bool imxml_memeql_ (ImxmlCharSequence a, ImxmlCharSequence b, size_t n);
+#ifndef IMXML_CUSTOM_STRING
+IMXMLAPI ImxmlString imxml_strsubcstr (ImxmlCharSequence cstr, ImxmlStrLen len);
+#ifdef __STDC_VERSION__
+#define imxml_strlit(lit) (ImxmlString){.IMXML_STRCOUNT = sizeof(lit) - 1, .IMXML_STRITEMS = "" lit}
+#endif /*__STDC_VERSION__*/
+#define imxml_strlit89(res, lit) do { \
+ (res).IMXML_STRITEMS = lit; \
+ (res).IMXML_STRCOUNT = sizeof(""lit)-1; \
+} while (0)
+#endif /*IMXML_CUSTOM_STRING*/
+
+IMXMLAPI ImxmlString xml_tag (XmlParser* p);
+IMXMLAPI ImxmlString xml_attr (XmlParser* p);
+/* NOTE This will just get the next quoted text, which means you should only
+ * use this if you KNOW there is a following attribute-value. If you're unsure,
+ * call xml_attribute first. */
+IMXMLAPI ImxmlString xml_value (XmlParser* p);
+
+IMXMLAPI bool xml_has_children (XmlParser* p);
+IMXMLAPI ImxmlString xml_skip_element (XmlParser* p);
+
+typedef ImxmlString (*ImxmlEntityGetFunc)(ImxmlString key);
+typedef void (*ImxmlEntityRegisterFunc)(ImxmlString key, ImxmlString value);
+/* Returns the value for an &entity;. If it's a character entity (e.g. &#69;),
+ * the returned value is a view into a global thread-local buffer that will be
+ * overwritten when the next character entity is decoded. It's the caller's
+ * responsibility to either immediately use the value (before another call to
+ * xml_entity_resolve is made on the same thread) or duplicate it. */
+IMXMLAPI ImxmlString xml_entity_resolve (
+ ImxmlString entity_name,
+ ImxmlEntityGetFunc get_entity
+);
+/* Returns a string representing the content. See `xml_entity_resolve`
+ * regarding handling of character entity resolution. */
+IMXMLAPI ImxmlString xml_content_to_string (
+ XmlContent content,
+ ImxmlEntityGetFunc get_entity
+);
+
+IMXMLAPI XmlContent xml_content (XmlParser* p);
+
+IMXMLAPI bool xml_tag_expect_str (XmlParser* p, ImxmlString expected);
+IMXMLAPI bool xml_attr_expect_str (XmlParser* p, ImxmlString expected);
+#ifdef __STDC_VERSION__
+#define xml_tag_expect(p, s) xml_tag_expect_str((p), imxml_strlit(s))
+#define xml_attr_expect(p, s) xml_attr_expect_str((p), imxml_strlit(s))
+#endif /*__STDC_VERSION__*/
+
+IMXMLAPI bool xml_parse_doctype (
+ XmlParser* p,
+ ImxmlEntityRegisterFunc register_entity
+);
+IMXMLAPI bool xml_parse_header (
+ XmlParser* p,
+ ImxmlEntityRegisterFunc register_entity
+);
+
+#ifdef IMXML_SUPPORT_CUSTOM_ENTITIES
+/* Call before parsing a document */
+IMXMLAPI void imxml_default_entity_reset (void);
+IMXMLAPI void imxml_default_entity_register (ImxmlString key, ImxmlString value);
+IMXMLAPI ImxmlString imxml_default_entity_get (ImxmlString key);
+#endif /*IMXML_SUPPORT_CUSTOM_ENTITIES*/
+
+/* NOTE All imxml_default_namespace_* functions are meant to work in tandem.
+ * I.e., require custom behavior, you'll need to reimplement all the callbacks
+ * and using any of the default implementations here is UB */
+#ifdef IMXML_SUPPORT_NAMESPACES
+/* Call before parsing a document */
+IMXMLAPI void imxml_default_namespace_reset (void);
+/* NOTE Only relevant when using the default namespace implementation */
+/* Returns the namespace for `id` where `id` is the full tag with prefix (the
+ * result of `xml_tag`). By default, this function performs a lookahead to
+ * check for namespaces that are being opened in the current tag. This incurs a
+ * significant performance cost and can be disabled via
+ * IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD which, if set, will not consider any
+ * namespaces that are being opened in the current tag. E.g. `<foo
+ * xmlns="foo.com">` will NOT be in the "foo.com" namespace. */
+IMXMLAPI ImxmlNamespace imxml_default_namespace_resolve (XmlParser* p, ImxmlString id);
+#endif /*IMXML_SUPPORT_NAMESPACES*/
+IMXMLAPI ImxmlString xml_tag_remove_namespace (ImxmlString tag);
+
+#ifdef IMXML_LINUX
+IMXMLAPI char* imxml_linux_file_open (
+ char const* const file_name,
+ size_t* const mapped_size,
+ bool populate /* load entire file into memory instead of streaming from disk */
+);
+IMXMLAPI void imxml_linux_file_close (char* file, size_t file_size);
+#endif /*IMXML_LINUX*/
+
+/* === END OF API === */
+
+#ifdef IMXML_IMPLEMENTATION
+
+IMXMLAPI bool imxml_memeql_ (
+ ImxmlCharSequence const a,
+ ImxmlCharSequence const b,
+ size_t const n
+) {
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+}
+
+#ifndef IMXML_CUSTOM_STRING
+IMXMLAPI ImxmlString imxml_strsubcstr (ImxmlCharSequence const cstr, ImxmlStrLen len) {
+ ImxmlString res;
+ res.IMXML_STRCOUNT = len;
+ res.IMXML_STRITEMS = cstr;
+ return res;
+}
+IMXMLAPI bool imxml_streql (ImxmlString a, ImxmlString b) {
+ if (a.IMXML_STRCOUNT != b.IMXML_STRCOUNT) return false;
+ return imxml_memeql_(a.IMXML_STRITEMS, b.IMXML_STRITEMS, a.IMXML_STRCOUNT);
+}
+#endif /*IMXML_CUSTOM_STRING*/
+
+IMXMLAPI bool is_whitespace (ImxmlChar c) {
+ return c == ' '
+ || c == '\t'
+ || c == '\r'
+ || c == '\n'
+ ;
+}
+
+#ifndef imxml_ctz
+#if defined(__builtin_ctz) || (defined(__clang__) || defined(__GNUC__))
+#define imxml_ctz (uint8_t)__builtin_ctz
+#else
+static inline uint8_t imxml_ctz(uint64_t x) {
+ uint8_t n = 0;
+ while (!(x & 1)) {
+ x >>= 1;
+ n += 1;
+ }
+ return n;
+}
+#endif
+#endif /*imxml_ctz*/
+
+#define IMXML_FUNCTION_GENERATOR_xml_skip_single_delim( \
+ SUFFIX, \
+ STEP, \
+ MASK_T, \
+ VEC_T, \
+ VEC_TU, \
+ SET1, \
+ LOADU, \
+ CMPEQ, \
+ VEC_OR, \
+ MOVEMASK, \
+ PREAMBLE, \
+ POSTAMBLE \
+) \
+IMXMLAPI bool xml_skip_single_delim##SUFFIX (XmlParser* const p, ImxmlChar delimiter) { \
+ VEC_T const qt = SET1(delimiter); \
+ IF_BOUNDS( \
+ VEC_T const zero = SET1(0); \
+ ) \
+ PREAMBLE \
+ while (true) { \
+ VEC_T block = LOADU((VEC_TU const*)(p->head)); \
+ VEC_T match = CMPEQ(block, qt); \
+ uint8_t offset; \
+ MASK_T mask; \
+ IF_BOUNDS( \
+ VEC_T cmp_zero = CMPEQ(block, zero); \
+ match = VEC_OR(cmp_zero, match); \
+ ) \
+ mask = (MASK_T)MOVEMASK(match); \
+ if (!mask) { \
+ p->head += STEP; \
+ continue; \
+ } \
+ offset = imxml_ctz(mask); \
+ p->head += offset; \
+ IF_BOUNDS( \
+ if (*p->head == 0) { \
+ POSTAMBLE \
+ return false; \
+ } \
+ ) \
+ p->head += 1; \
+ POSTAMBLE \
+ return true; \
+ } \
+}
+
+#define GEN_FUNC(suffix, x) IMXML_FUNCTION_GENERATOR_xml_skip_single_delim(suffix, x)
+
+GEN_FUNC(_, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef GEN_FUNC
+
+#define xml_skip_sstring(p) xml_skip_single_delim_(p, '\'')
+#define xml_skip_dstring(p) xml_skip_single_delim_(p, '"')
+
+#define IMXML_FUNCTION_GENERATOR_xml_skip_double_delim_gt( \
+ SUFFIX, \
+ STEP, \
+ MASK_T, \
+ VEC_T, \
+ VEC_TU, \
+ SET1, \
+ LOADU, \
+ CMPEQ, \
+ VEC_OR, \
+ MOVEMASK, \
+ PREAMBLE, \
+ POSTAMBLE \
+) \
+IMXMLAPI bool xml_skip_double_delim_gt##SUFFIX (XmlParser* const p, ImxmlCharSequence delim) { \
+ VEC_T const gt = SET1('>'); \
+ IF_BOUNDS( \
+ VEC_T const zero = SET1(0); \
+ ) \
+ PREAMBLE \
+ while (true) { \
+ VEC_T block = LOADU((VEC_TU const*)(p->head)); \
+ VEC_T match = CMPEQ(block, gt); \
+ MASK_T mask; \
+ uint8_t offset; \
+ IF_BOUNDS( \
+ VEC_T cmp_zero = CMPEQ(block, zero); \
+ match = VEC_OR(cmp_zero, match); \
+ ) \
+ mask = (MASK_T)MOVEMASK(match); \
+ if (mask) { \
+ offset = imxml_ctz(mask); \
+ p->head += offset; \
+ IF_BOUNDS( \
+ if (*p->head == 0) { \
+ POSTAMBLE \
+ return false; \
+ } \
+ ) \
+ p->head += 1; \
+ /* NOTE the following can't underflow because this only gets called
+ * in contexts where we have already parsed >4 bytes ("<!--" or
+ * "![CDATA[") */ \
+ if (*(p->head - 3) == delim[0] && *(p->head - 2) == delim[1]) { \
+ POSTAMBLE \
+ return true; \
+ } else { \
+ continue; \
+ } \
+ } \
+ p->head += STEP; \
+ } \
+}
+
+#define GEN_FUNC(suffix, x) IMXML_FUNCTION_GENERATOR_xml_skip_double_delim_gt(suffix, x)
+
+GEN_FUNC(_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+
+#undef GEN_FUNC
+
+#define xml_skip_comment(p) xml_skip_double_delim_gt_(p, "--")
+#define xml_skip_cdata(p) xml_skip_double_delim_gt_(p, "]]")
+
+#define IF_SEEK_MULTIPLE(x)
+#define IF_SEEK_MULTIPLE2(a, b)
+#define IF_NO_SEEK_MULTIPLE(x) x
+
+#ifndef IMXML_MAX_MULTI_SEEK_COUNT
+#define IMXML_MAX_MULTI_SEEK_COUNT 6
+#endif
+
+#define IMXML_FUNCTION_GENERATOR_imxml_seek_next_char( \
+ SUFFIX, \
+ STEP, \
+ MASK_T, \
+ VEC_T, \
+ VEC_TU, \
+ SET1, \
+ LOADU, \
+ CMPEQ, \
+ VEC_OR, \
+ MOVEMASK, \
+ PREAMBLE, \
+ POSTAMBLE \
+) \
+IMXMLAPI NODISCARD bool imxml_seek_next_char##SUFFIX ( \
+ XmlParser* const p, \
+ IF_NO_SEEK_MULTIPLE( \
+ ImxmlChar const needle_ \
+ ) \
+ IF_SEEK_MULTIPLE2( \
+ ImxmlChar const* const needles_, \
+ uint8_t const needles_count \
+ ) \
+) { \
+ ImxmlCharSequence ptr = p->head; \
+ IF_SINGLE_QUOTES( VEC_T const st = SET1('\''); ) \
+ IF_DOUBLE_QUOTES( VEC_T const qt = SET1('"'); ) \
+ IF_BOUNDS( VEC_T const zero = SET1(0); ) \
+ IF_COMMENTS_OR_CDATA( VEC_T const lt = SET1('<'); ) \
+ IF_NO_SEEK_MULTIPLE( \
+ VEC_T const needle = SET1(needle_); \
+ )\
+ IF_SEEK_MULTIPLE( \
+ uint8_t i; \
+ VEC_T needles[IMXML_MAX_MULTI_SEEK_COUNT]; \
+ ASSUME(needles_count <= IMXML_MAX_MULTI_SEEK_COUNT); \
+ for (i = 0; i < needles_count; ++i) { \
+ needles[i] = SET1(needles_[i]); \
+ } \
+ )\
+ PREAMBLE \
+ while (true) { \
+ VEC_T block = LOADU((VEC_TU const*)(ptr)); \
+ MASK_T mask_any; \
+ VEC_T any_match; \
+ \
+ IF_SEEK_MULTIPLE( \
+ VEC_T cmp_needle = {0}; \
+ for (i = 0; i < needles_count; ++i) { \
+ cmp_needle = VEC_OR(cmp_needle, CMPEQ(block, needles[i])); \
+ } \
+ )\
+ IF_NO_SEEK_MULTIPLE( \
+ VEC_T cmp_needle = CMPEQ(block, needle); \
+ )\
+ \
+ any_match = cmp_needle; \
+ IF_SINGLE_QUOTES({ \
+ VEC_T cmp_st = CMPEQ(block, st); \
+ any_match = VEC_OR(cmp_st, any_match); \
+ }) \
+ IF_DOUBLE_QUOTES({ \
+ VEC_T cmp_qt = CMPEQ(block, qt); \
+ any_match = VEC_OR(cmp_qt, any_match); \
+ }) \
+ IF_BOUNDS({ \
+ VEC_T cmp_zero = CMPEQ(block, zero); \
+ any_match = VEC_OR(cmp_zero, any_match); \
+ }) \
+ IF_COMMENTS_OR_CDATA({ \
+ VEC_T cmp_lt = CMPEQ(block, lt); \
+ any_match = VEC_OR(cmp_lt, any_match); \
+ }) \
+ \
+ mask_any = (MASK_T)MOVEMASK(any_match); \
+ if (!mask_any) { \
+ ptr += STEP; \
+ continue; \
+ } \
+ ptr += imxml_ctz(mask_any); \
+ if (UNLIKELY(*ptr == '<')) { \
+ IF_COMMENTS( \
+ if (imxml_memeql_(ptr+1, "!--", 3)) { \
+ ptr += 1+3+2; \
+ p->head = ptr; \
+ (void)xml_skip_comment(p); /* bounds check will happen next iteration */ \
+ ptr = p->head; \
+ continue; \
+ } \
+ ) \
+ IF_CDATA( \
+ if (imxml_memeql_(ptr+1, "![CDATA[", 8)) { \
+ ptr += 1+8+2; \
+ p->head = ptr; \
+ (void)xml_skip_cdata(p); /* bounds check will happen next iteration */ \
+ ptr = p->head; \
+ continue; \
+ } \
+ ) \
+ IF_SEEK_MULTIPLE ({ \
+ bool any_needle_is_gt = false; \
+ for (i = 0; i < needles_count; ++i) { \
+ any_needle_is_gt |= (bool)(needles_[i] == '<'); \
+ } \
+ if (!any_needle_is_gt) { \
+ ptr += 1; \
+ continue; \
+ } \
+ }) \
+ IF_NO_SEEK_MULTIPLE ( \
+ if (LIKELY(needle_ != '<')) { \
+ ptr += 1; \
+ continue; \
+ } \
+ )\
+ } \
+ IF_SEEK_MULTIPLE ( \
+ for (i = 0; i < needles_count; ++i) { \
+ if (*ptr == needles_[i]) { \
+ p->head = ptr; \
+ POSTAMBLE \
+ return true; \
+ } \
+ } \
+ ) \
+ IF_NO_SEEK_MULTIPLE ( \
+ if (*ptr == needle_) { \
+ p->head = ptr; \
+ POSTAMBLE \
+ return true; \
+ } \
+ ) \
+ IF_SINGLE_QUOTES ( \
+ if (*ptr == '\'') { \
+ ptr += 1; \
+ p->head = ptr; \
+ xml_skip_sstring(p); \
+ ptr = p->head; \
+ continue; \
+ } \
+ ) \
+ IF_DOUBLE_QUOTES ( \
+ if (*ptr == '\"') { \
+ ptr += 1; \
+ p->head = ptr; \
+ xml_skip_dstring(p); \
+ ptr = p->head; \
+ continue; \
+ } \
+ ) \
+ IF_BOUNDS ( \
+ ASSUME(*ptr == 0); \
+ POSTAMBLE \
+ return false; \
+ ) \
+ } \
+}
+
+#define GEN_FUNC(suffix, x) IMXML_FUNCTION_GENERATOR_imxml_seek_next_char(suffix, x)
+
+GEN_FUNC(_default, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+GEN_FUNC(_dense, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef IF_SEEK_MULTIPLE
+#define IF_SEEK_MULTIPLE(x) x
+#undef IF_SEEK_MULTIPLE2
+#define IF_SEEK_MULTIPLE2(a, b) a,b
+#undef IF_NO_SEEK_MULTIPLE
+#define IF_NO_SEEK_MULTIPLE(x)
+GEN_FUNC(_default_multi_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+GEN_FUNC(_dense_multi_, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef IF_SEEK_MULTIPLE
+#define IF_SEEK_MULTIPLE(x)
+#undef IF_SEEK_MULTIPLE2
+#define IF_SEEK_MULTIPLE2(a, b)
+#undef IF_NO_SEEK_MULTIPLE
+#define IF_NO_SEEK_MULTIPLE(x) x
+#undef IF_SINGLE_QUOTES
+#define IF_SINGLE_QUOTES(x)
+#undef IF_DOUBLE_QUOTES
+#define IF_DOUBLE_QUOTES(x)
+GEN_FUNC(_default_ignore_quotes_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+
+#undef IF_SEEK_MULTIPLE
+#define IF_SEEK_MULTIPLE(x) x
+#undef IF_SEEK_MULTIPLE2
+#define IF_SEEK_MULTIPLE2(a, b) a,b
+#undef IF_NO_SEEK_MULTIPLE
+#define IF_NO_SEEK_MULTIPLE(x)
+
+#undef IF_CDATA
+#define IF_CDATA(x)
+#undef IF_COMMENTS_OR_CDATA
+#ifdef IMXML_SUPPORT_COMMENTS
+#define IF_COMMENTS_OR_CDATA(x) x
+#else
+#define IF_COMMENTS_OR_CDATA(x)
+#endif
+GEN_FUNC(_default_raw_text_with_comments_multi_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+GEN_FUNC(_dense_raw_text_with_comments_multi_, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef IF_COMMENTS
+#define IF_COMMENTS(x)
+#undef IF_COMMENTS_OR_CDATA
+#define IF_COMMENTS_OR_CDATA(x)
+GEN_FUNC(_default_raw_text_multi_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+GEN_FUNC(_dense_raw_text_multi_, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef IF_SEEK_MULTIPLE
+#define IF_SEEK_MULTIPLE(x)
+#undef IF_SEEK_MULTIPLE2
+#define IF_SEEK_MULTIPLE2(a, b)
+#undef IF_NO_SEEK_MULTIPLE
+#define IF_NO_SEEK_MULTIPLE(x) x
+GEN_FUNC(_default_raw_text_, IMXML_SIMD_DEFAULT_INSTRUCTIONS)
+GEN_FUNC(_dense_raw_text_, IMXML_SIMD_DENSE_INSTRUCTIONS)
+
+#undef GEN_FUNC
+
+#ifdef IMXML_SUPPORT_CUSTOM_ENTITIES
+#ifndef IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE
+#define IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE 255
+#endif
+static THREADLOCAL ImxmlEntityDefinition imxml_custom_entities_[IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE];
+static THREADLOCAL size_t imxml_custom_entity_count_ = 0;
+IMXMLAPI void imxml_default_entity_reset (void) {
+ imxml_custom_entity_count_ = 0;
+}
+IMXMLAPI void imxml_default_entity_register (ImxmlString key, ImxmlString value) {
+ if (imxml_custom_entity_count_ >= IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE) {
+ ASSERT(false);
+ return;
+ }
+ imxml_custom_entities_[imxml_custom_entity_count_].key = key;
+ imxml_custom_entities_[imxml_custom_entity_count_].value = value;
+ imxml_custom_entity_count_ += 1;
+}
+IMXMLAPI ImxmlString imxml_default_entity_get (ImxmlString key) {
+ size_t i;
+ ImxmlEntityDefinition e;
+ ImxmlString res = {0};
+ if (imxml_custom_entity_count_ == 0) {
+ ASSERT(false);
+ return res;
+ }
+ for (i = 0; i < imxml_custom_entity_count_; ++i) {
+ e = imxml_custom_entities_[i];
+ if (imxml_streql(e.key, key)) {
+ res = e.value;
+ return res;
+ }
+ }
+ /* NOTE it might seem nice to return "&key;", but that would either require
+ * an allocation or create a footgun where calling this with a
+ * `imxml_strlit("foo")` reads OOB, so we just return empty and let the
+ * user decide how to handle it */
+ return res;
+}
+#endif /*IMXML_SUPPORT_CUSTOM_ENTITIES*/
+
+static inline bool imxml_try_parse_xmlns (XmlParser* p);
+static inline bool imxml_seek_gt_ (XmlParser* const p) {
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ while (true) {
+ if (!imxml_seek_next_char_dense_multi_(p, "x>", 2)) return false;
+ if (*p->head != 'x') break;
+ ASSUME(*p->head == 'x');
+ if (imxml_try_parse_xmlns(p)) continue; /* there mighe be multiple namespace definitions */
+ p->head += 1;
+ }
+ #else
+ if (!imxml_seek_next_char_dense(p, '>')) return false;
+ #endif /*IMXML_SUPPORT_NAMESPACES*/
+ ASSUME(*p->head == '>');
+ p->in_tag = false;
+ return true;
+}
+
+#ifdef IMXML_SUPPORT_NAMESPACES
+#ifndef IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE
+#define IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE 255
+#endif
+typedef struct {
+ ImxmlNamespace ns;
+ size_t depth;
+} ALIGN(64) ImxmlNamespaceStackEntry;
+static THREADLOCAL ImxmlNamespaceStackEntry imxml_default_namespace_stack_[IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE];
+static THREADLOCAL size_t imxml_default_namespace_stack_count_ = 0;
+IMXMLAPI void imxml_default_namespace_reset (void) {
+ imxml_default_namespace_stack_count_ = 0;
+}
+static void imxml_default_namespace_push_ (size_t depth, ImxmlString const prefix, ImxmlString const ns_) {
+ ImxmlNamespace ns;
+ ns.prefix = prefix;
+ ns.ns = ns_;
+ if(imxml_default_namespace_stack_count_ >= IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE) {
+ ASSERT(false);
+ return;
+ }
+ imxml_default_namespace_stack_[imxml_default_namespace_stack_count_ ].depth = depth;
+ imxml_default_namespace_stack_[imxml_default_namespace_stack_count_ ].ns = ns;
+ imxml_default_namespace_stack_count_ += 1;
+}
+static void imxml_default_namespace_pop_ (void) {
+ if (imxml_default_namespace_stack_count_ == 0) return;
+ imxml_default_namespace_stack_count_ -= 1;
+}
+IMXMLAPI ImxmlNamespace imxml_default_namespace_resolve (XmlParser* const p, ImxmlString const tag) {
+ ImxmlStrLen i;
+ ImxmlNamespace res = {0};
+ ImxmlString prefix = {0};
+ for (i = 0; i < tag.IMXML_STRCOUNT; ++i) {
+ if (tag.IMXML_STRITEMS[i] == ':') {
+ prefix.IMXML_STRITEMS = tag.IMXML_STRITEMS;
+ prefix.IMXML_STRCOUNT = i;
+ break;
+ }
+ }
+
+ if (prefix.IMXML_STRITEMS != 0 && *prefix.IMXML_STRITEMS == '/') {
+ /*closing tag*/
+ prefix.IMXML_STRITEMS += 1;
+ prefix.IMXML_STRCOUNT -= 1;
+ }
+
+ {
+ #ifdef IMXML_SUPPORT_NAMESPACE_LOOKAHEAD
+ /* NOTE we need to look ahead here, since if this tag might define a
+ * namespace */
+ XmlParser pp = *p;
+ size_t j;
+ size_t const old_count = imxml_default_namespace_stack_count_;
+ imxml_seek_gt_(&pp); /* Finds xmlns definitions */
+ j = imxml_default_namespace_stack_count_;
+ imxml_default_namespace_stack_count_ = old_count;
+ #else
+ (void)p;
+ j = imxml_default_namespace_stack_count_;
+ #endif
+
+ for (; j; --j) {
+ res = imxml_default_namespace_stack_[j-1].ns;
+ if (imxml_streql(res.prefix, prefix)) {
+ return res;
+ }
+ }
+ }
+ return res;
+}
+
+void static inline imxml_default_ns_pop_to_depth_ (XmlParser* const p) {
+ if (UNLIKELY(!p->namespace_pop)) {
+ p->namespace_pop = imxml_default_namespace_pop_;
+ }
+ while (imxml_default_namespace_stack_count_ != 0) {
+ ImxmlNamespaceStackEntry e =
+ imxml_default_namespace_stack_[imxml_default_namespace_stack_count_ - 1];
+
+ if (e.depth <= p->depth) break;
+
+ p->namespace_pop();
+ }
+}
+
+static inline bool imxml_try_parse_xmlns (XmlParser* const p) {
+ ImxmlCharSequence const tmp = p->head;
+ ImxmlString prefix = {0};
+ ImxmlString ns;
+ if (LIKELY(
+ *p->head != 'x'
+ || !is_whitespace(*(p->head - 1))
+ || !imxml_memeql_(p->head, "xmlns", 5)
+ )) return false;
+ p->head += 5;
+ if ( *p->head != ':'
+ && ((!is_whitespace(*p->head) && *p->head != '=')
+ || (*p->head == '>' || !imxml_seek_next_char_dense_raw_text_multi_(p, "=>", 2))
+ )
+ ) {
+ p->head = tmp;
+ return false;
+ }
+
+ if (*p->head == ':') {
+ prefix.IMXML_STRITEMS = p->head + 1;
+ if (!imxml_seek_next_char_dense_raw_text_(p, '=')) return false;
+ prefix.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - prefix.IMXML_STRITEMS);
+ while (is_whitespace(prefix.IMXML_STRITEMS[prefix.IMXML_STRCOUNT-1])) {
+ prefix.IMXML_STRCOUNT -= 1;
+ }
+ }
+ ASSUME(*p->head == '=');
+ ns = xml_value(p);
+ if (UNLIKELY(!p->namespace_push)) {
+ p->namespace_push = imxml_default_namespace_push_;
+ }
+ p->namespace_push(p->depth, prefix, ns);
+ return true;
+}
+#endif /*IMXML_SUPPORT_NAMESPACES*/
+
+/* Returns the tag without the namespace.
+ * NOTE This function behaves consistently regardless of whether
+ * IMXML_NO_SUPPORT_NAMESPACES is set */
+IMXMLAPI ImxmlString xml_tag_remove_namespace (ImxmlString tag) {
+ ImxmlString tag_without_ns = tag;
+
+ while (true) {
+ if (tag_without_ns.IMXML_STRITEMS == tag.IMXML_STRITEMS + tag.IMXML_STRCOUNT) {
+ /*we have to do this for copy-elision, can't just return tag*/
+ tag_without_ns.IMXML_STRITEMS = tag.IMXML_STRITEMS;
+ return tag_without_ns;
+ }
+ if (*tag_without_ns.IMXML_STRITEMS == ':') break;
+ tag_without_ns.IMXML_STRITEMS += 1;
+ }
+ tag_without_ns.IMXML_STRITEMS += 1;
+
+ tag_without_ns.IMXML_STRCOUNT -= (ImxmlStrLen)(tag_without_ns.IMXML_STRITEMS - tag.IMXML_STRITEMS);
+
+ return tag_without_ns;
+}
+
+/* NOTE no bounds checking, only use if you know there is a previous
+ * non-whitespace char */
+static inline ImxmlChar imxml_previous_char_ (XmlParser* const p) {
+ ImxmlCharSequence h;
+ for (h = p->head - 1; ; --h) {
+ if (!is_whitespace(*h)) return *h;
+ }
+}
+
+static inline void imxml_exit_tag (XmlParser* const p) {
+ if (!p->in_tag) return;
+ if (!imxml_seek_gt_(p)) return;
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ if (imxml_previous_char_(p) == '/') {
+ p->depth -= 1;
+ imxml_default_ns_pop_to_depth_(p);
+ }
+ #endif
+ p->head += 1;
+ ASSUME(*p->head != '>');
+ p->in_tag = *p->head == '<';
+}
+
+IMXMLAPI ImxmlString xml_tag (XmlParser* const p) {
+ ImxmlString res = {0};
+ ImxmlCharSequence tag_start;
+ ImxmlChar c;
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ imxml_exit_tag(p); /* in case we're still currently inside a tag */
+ imxml_default_ns_pop_to_depth_(p);
+ #endif
+ /* NOTE we can ignore quotes here because attribute values cannot contain
+ * '<' and content ignores quotes anyways */
+ if (!imxml_seek_next_char_default_ignore_quotes_(p, '<')) return res;
+ ASSUME(*p->head == '<');
+ p->in_tag = true;
+ p->head += 1;
+
+ tag_start = p->head;
+ p->head += 1;
+ while (true) {
+ c = *p->head;
+ #ifdef IMXML_CHECK_BOUNDS
+ if (c == 0) return res;
+ #endif
+ if (is_whitespace(c) || c == '>' || c == '/') break;
+ p->head += 1;
+ }
+ res = imxml_strsubcstr(tag_start, (ImxmlStrLen)(p->head - tag_start));
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ if (res.IMXML_STRITEMS[0] == '/') {
+ imxml_default_ns_pop_to_depth_(p);
+ p->depth -= 1;
+ } else {
+ p->depth += 1;
+ }
+ #endif
+ p->current_tag = res;
+ return res;
+}
+
+IMXMLAPI ImxmlString xml_attr (XmlParser* const p) {
+ ImxmlString res = {0};
+ /* 1. find whitespace, in case we're in the middle of something */
+ while (true) {
+ ImxmlChar c = *p->head;
+ if (
+ /* UNLIKELY because we probably have >1 attrs */
+ #ifdef IMXML_CHECK_BOUNDS
+ UNLIKELY(c == 0 || c == '>')
+ #else
+ UNLIKELY(c == '>')
+ #endif
+ ) {
+ return res;
+ }
+ #ifdef IMXML_SUPPORT_SINGLE_QUOTES
+ if (c == '\'') {
+ xml_skip_sstring(p);
+ continue;
+ }
+ #endif
+ #ifdef IMXML_SUPPORT_DOUBLE_QUOTES
+ if (c == '"') {
+ xml_skip_dstring(p);
+ continue;
+ }
+ #endif
+ if (is_whitespace(c)) break;
+ p->head += 1;
+ }
+ /* 2. skip whitespace */
+ while (true) {
+ ImxmlChar c = *p->head;
+ #ifdef IMXML_CHECK_BOUNDS
+ if (c == 0) return res;
+ #endif
+ if (!is_whitespace(c)) break;
+ p->head += 1;
+ }
+ if (UNLIKELY(*p->head == '>' || imxml_memeql_(p->head, "/>", 2))) {
+ return res;
+ }
+
+ {
+ ImxmlCharSequence const attr_start = p->head;
+ /* 3. parse */
+ if (!imxml_seek_next_char_dense_raw_text_multi_(p, "=>", 2)) return res;
+ if (*p->head == '>') return res;
+ res = imxml_strsubcstr(attr_start, (ImxmlStrLen)(p->head - attr_start));
+ while (is_whitespace(res.IMXML_STRITEMS[res.IMXML_STRCOUNT-1])) {
+ res.IMXML_STRCOUNT -= 1;
+ }
+ p->head += 1;
+ }
+
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ if (UNLIKELY(
+ imxml_memeql_(res.IMXML_STRITEMS, "xmlns", 5)
+ && (res.IMXML_STRCOUNT == 5 || *(res.IMXML_STRITEMS + 5) == ':')
+ )) {
+ XmlParser pp;
+ ImxmlString prefix = {0};
+ ImxmlString ns;
+ pp.head = res.IMXML_STRITEMS + 5;
+ if (*pp.head == ':') {
+ bool success;
+ prefix.IMXML_STRITEMS = pp.head + 1;
+ success = imxml_seek_next_char_dense_raw_text_(&pp, '=');
+ ASSUME(success);
+ prefix.IMXML_STRCOUNT = (ImxmlStrLen)(pp.head - prefix.IMXML_STRITEMS);
+ }
+ ASSUME(*pp.head == '=');
+ ns = xml_value(&pp);
+
+ if (UNLIKELY(!p->namespace_push)) {
+ p->namespace_push = imxml_default_namespace_push_;
+ }
+ p->namespace_push(p->depth, prefix, ns);
+ }
+ #endif
+
+ return res;
+}
+
+IMXMLAPI ImxmlString xml_value (XmlParser* const p) {
+ ImxmlChar delim;
+ ImxmlString res = {0};
+ while (true) {
+ ImxmlChar c = *p->head;
+ #ifdef IMXML_CHECK_BOUNDS
+ if (c == 0) return res;
+ #endif
+ #ifdef IMXML_SUPPORT_SINGLE_QUOTES
+ if (c == '\'') {
+ delim = '\'';
+ break;
+ }
+ #endif
+ #ifdef IMXML_SUPPORT_DOUBLE_QUOTES
+ if (c == '"') {
+ delim = '"';
+ break;
+ }
+ #endif
+ p->head += 1;
+ }
+ p->head += 1;
+ {
+ ImxmlCharSequence const value_start = p->head;
+ while (true) {
+ ImxmlChar c = *p->head;
+ #ifdef IMXML_CHECK_BOUNDS
+ if (c == 0) return res;
+ #endif
+ if (c == delim) break;
+ p->head += 1;
+ }
+ res = imxml_strsubcstr(value_start, (ImxmlStrLen)(p->head - value_start));
+ p->head += 1;
+ }
+ return res;
+}
+
+IMXMLAPI bool xml_has_children (XmlParser* const p) {
+ bool has_children;
+ if (!p->in_tag) return false;
+ if (!imxml_seek_gt_(p)) return false;
+ has_children = imxml_previous_char_(p) != '/';
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ if (!has_children) {
+ p->depth -= 1;
+ imxml_default_ns_pop_to_depth_(p);
+ }
+ #endif
+ return has_children;
+}
+
+IMXMLAPI ImxmlString xml_skip_element (XmlParser* const p) {
+ ImxmlString res = {0};
+ size_t depth = 0;
+ /* trying to skip closing tag */
+ if (p->current_tag.IMXML_STRCOUNT && p->current_tag.IMXML_STRITEMS[0] == '/') {
+ res = p->current_tag;
+ return res;
+ }
+
+ if (UNLIKELY(!imxml_seek_next_char_default(p, '>'))) return res;
+ if (imxml_previous_char_(p) == '/') return res; /* no children */
+
+ while (true) {
+ ImxmlString next_tag = xml_tag(p);
+ if (next_tag.IMXML_STRITEMS == NULL) {
+ res = next_tag;
+ return res;
+ }
+ /* We don't check for matching tags, only if it's a closing tag.
+ * We expect valid XML. */
+ if (next_tag.IMXML_STRITEMS[0] == '/') {
+ if (depth == 0) {
+ res = next_tag;
+ return res;
+ }
+ depth -= 1;
+ continue;
+ }
+ if (xml_has_children(p)) {
+ depth += 1;
+ }
+ }
+}
+
+/* NOTE This is some fairly basic utf-8/unicode handling that assumes valid
+ * XML/UTF-8, doesn't reject surrogate range, doesn't check bounds, etc */
+static inline ImxmlStrLen imxml_utf8_encode_ (uint32_t const cp, ImxmlChar* const out) {
+ if (cp <= 0x7FU) {
+ out[0] = (ImxmlChar)cp;
+ return 1;
+ }
+ if (cp <= 0x7FFU) {
+ out[0] = (ImxmlChar)(0xC0U | (cp >> 6U));
+ out[1] = (ImxmlChar)(0x80U | (cp & 0x3FU));
+ return 2;
+ }
+ if (cp <= 0xFFFFU) {
+ out[0] = (ImxmlChar)(0xE0U | (cp >> 12U));
+ out[1] = (ImxmlChar)(0x80U | ((cp >> 6U) & 0x3FU));
+ out[2] = (ImxmlChar)(0x80U | (cp & 0x3FU));
+ return 3;
+ }
+ if (cp <= 0x10FFFFU) {
+ out[0] = (ImxmlChar)(0xF0U | (cp >> 18U));
+ out[1] = (ImxmlChar)(0x80U | ((cp >> 12U) & 0x3FU));
+ out[2] = (ImxmlChar)(0x80U | ((cp >> 6U) & 0x3FU));
+ out[3] = (ImxmlChar)(0x80U | (cp & 0x3FU));
+ return 4;
+ }
+ /* Out of Unicode range: emit U+FFFD replacement character. */
+ out[0] = (ImxmlChar)0xEFU;
+ out[1] = (ImxmlChar)0xBFU;
+ out[2] = (ImxmlChar)0xBDU;
+ return 3;
+}
+static THREADLOCAL ImxmlChar imxml_codepoint_buf_[4];
+static inline ImxmlString imxml_parse_codepoint_base10_ (ImxmlString const s) {
+ ImxmlString res;
+ ImxmlStrLen i;
+ uint32_t cp = 0;
+ for (i = 0; i < s.IMXML_STRCOUNT; ++i) {
+ ImxmlChar c = s.IMXML_STRITEMS[i];
+ if (c < '0' || c > '9') break;
+ cp = (cp * 10U) + (uint32_t)(c - '0');
+ }
+ i = imxml_utf8_encode_(cp, imxml_codepoint_buf_);
+ res.IMXML_STRCOUNT = i;
+ res.IMXML_STRITEMS = imxml_codepoint_buf_;
+ return res;
+}
+static inline ImxmlString imxml_parse_codepoint_base16_ (ImxmlString const s) {
+ uint32_t cp = 0;
+ ImxmlStrLen i;
+ ImxmlString res;
+ for (i = 0; i < s.IMXML_STRCOUNT; ++i) {
+ ImxmlChar c = s.IMXML_STRITEMS[i];
+ uint32_t digit;
+ if (c >= '0' && c <= '9') {
+ digit = (uint32_t)(c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ digit = (uint32_t)(c - 'a') + 10U;
+ } else if (c >= 'A' && c <= 'F') {
+ digit = (uint32_t)(c - 'A') + 10U;
+ } else {
+ break;
+ }
+ cp = (cp * 16U) + digit;
+ }
+ i = imxml_utf8_encode_(cp, imxml_codepoint_buf_);
+ res.IMXML_STRCOUNT = i;
+ res.IMXML_STRITEMS = imxml_codepoint_buf_;
+ return res;
+}
+
+IMXMLAPI ImxmlString xml_entity_resolve (ImxmlString entity_name, ImxmlEntityGetFunc get_entity) {
+ ImxmlString s;
+ if (entity_name.IMXML_STRCOUNT > 1 /*needs at least #N*/) {
+ if (*entity_name.IMXML_STRITEMS == '#') {
+ if (*(entity_name.IMXML_STRITEMS + 1) == 'x') {
+ ImxmlString str;
+ str.IMXML_STRITEMS = entity_name.IMXML_STRITEMS + 2;
+ str.IMXML_STRCOUNT = entity_name.IMXML_STRCOUNT - 2;
+ s = imxml_parse_codepoint_base16_(str);
+ return s;
+ } else {
+ ImxmlString str;
+ str.IMXML_STRITEMS = entity_name.IMXML_STRITEMS + 1;
+ str.IMXML_STRCOUNT = entity_name.IMXML_STRCOUNT - 1;
+ s = imxml_parse_codepoint_base10_(str);
+ return s;
+ }
+ }
+ }
+
+ imxml_strlit89(s, "lt");
+ if (imxml_streql(entity_name, s)) {
+ imxml_strlit89(s, "<");
+ return s;
+ }
+ imxml_strlit89(s, "gt");
+ if (imxml_streql(entity_name, s)) {
+ imxml_strlit89(s, ">");
+ return s;
+ }
+ imxml_strlit89(s, "amp");
+ if (imxml_streql(entity_name, s)) {
+ imxml_strlit89(s, "&");
+ return s;
+ }
+ imxml_strlit89(s, "quot");
+ if (imxml_streql(entity_name, s)) {
+ imxml_strlit89(s, "\"");
+ return s;
+ }
+ imxml_strlit89(s, "apos");
+ if (imxml_streql(entity_name, s)) {
+ imxml_strlit89(s, "'");
+ return s;
+ }
+
+ if (get_entity) {
+ s = get_entity(entity_name);
+ } else {
+ s.IMXML_STRITEMS = 0;
+ s.IMXML_STRCOUNT = 0;
+ }
+ return s;
+}
+
+IMXMLAPI ImxmlString xml_content_to_string (XmlContent content, ImxmlEntityGetFunc get_entity) {
+ ImxmlString res = {0};
+ switch (content.type) {
+ case XML_CONTENT_TEXT: {
+ res = content.data;
+ return res;
+ }
+ case XML_CONTENT_ENTITY: {
+ #ifdef IMXML_SUPPORT_ENTITIES
+ res = xml_entity_resolve(content.data, get_entity);
+ return res;
+ #else
+ (void)get_entity;
+ res = content.data;
+ return res;
+ #endif
+ }
+ case XML_CONTENT_TAG:
+ case XML_CONTENT_COMMENT:
+ case XML_CONTENT_NONE: {
+ return res;
+ }
+ default: {
+ ASSUME(false);
+ }
+ }
+}
+
+IMXMLAPI bool xml_is_closing_tag_for (ImxmlString tag, ImxmlString opening_tag) {
+ ImxmlString str;
+ str.IMXML_STRITEMS = tag.IMXML_STRITEMS+1;
+ str.IMXML_STRCOUNT = tag.IMXML_STRCOUNT-1;
+ return *tag.IMXML_STRITEMS == '/' && imxml_streql(opening_tag, str);
+}
+
+IMXMLAPI XmlContent xml_content (XmlParser* const p) {
+ /* kinda hacky, but necessary for NRVO */
+ XmlContent res;
+ res.type = XML_CONTENT_NONE;
+
+ imxml_exit_tag(p);
+
+ if (*p->head == '<') {
+ p->head += 1;
+
+ #ifdef IMXML_SUPPORT_COMMENTS
+ if (imxml_memeql_(p->head, "!--", 3)) {
+ p->head += 3;
+ res.data.IMXML_STRITEMS = p->head;
+ if (UNLIKELY(!xml_skip_comment(p))) return res;
+ res.data.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - 3 - res.data.IMXML_STRITEMS);
+ res.type = XML_CONTENT_COMMENT;
+ /* we return the actual comment text, since it doesn't cost much,
+ * and it may be useful in some cases */
+ return res;
+ }
+ #endif
+
+ #ifdef IMXML_SUPPORT_CDATA
+ if (imxml_memeql_(p->head, "![CDATA[", 8)) {
+ p->head += 8;
+ res.data.IMXML_STRITEMS = p->head;
+ if (UNLIKELY(!xml_skip_cdata(p))) return res;
+ res.data.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - 3 - res.data.IMXML_STRITEMS);
+ res.type = XML_CONTENT_TEXT;
+ return res;
+ }
+ #endif
+
+ /* tag */
+ res.type = XML_CONTENT_TAG;
+
+ /* not optimal, but whatever */
+ p->head -= 1;
+ p->in_tag = *p->head == '>';
+ res.data = xml_tag(p);
+
+ return res;
+ }
+
+ #ifdef IMXML_SUPPORT_ENTITIES
+ if (*p->head == '&') {
+ p->head += 1;
+ res.data.IMXML_STRITEMS = p->head;
+ if (UNLIKELY(!imxml_seek_next_char_dense_raw_text_(p, ';'))) return res;
+ res.data.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - res.data.IMXML_STRITEMS);
+ p->head += 1;
+ res.type = XML_CONTENT_ENTITY;
+ return res;
+ }
+ #endif
+
+ /* text */
+ res.data.IMXML_STRITEMS = p->head;
+ #ifdef IMXML_SUPPORT_ENTITIES
+ if (UNLIKELY(!imxml_seek_next_char_default_raw_text_multi_(p, "<&", 2))) return res;
+ #else
+ if (UNLIKELY(!imxml_seek_next_char_default_raw_text_(p, '<'))) return res;
+ #endif /*IMXML_SUPPORT_ENTITIES*/
+ res.type = XML_CONTENT_TEXT;
+ res.data.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - res.data.IMXML_STRITEMS);
+ return res;
+}
+
+IMXMLAPI bool xml_tag_expect_str (XmlParser* const p, ImxmlString expected) {
+ ImxmlString actual = xml_tag(p);
+ return imxml_streql(actual, expected);
+}
+IMXMLAPI bool xml_attr_expect_str (XmlParser* const p, ImxmlString expected) {
+ ImxmlString actual = xml_attr(p);
+ return imxml_streql(actual, expected);
+}
+
+IMXMLAPI bool xml_parse_doctype (XmlParser* const p, ImxmlEntityRegisterFunc register_entity) {
+ ImxmlString s;
+ imxml_strlit89(s, "!DOCTYPE");
+ if (!xml_tag_expect_str(p, s)) return false;
+
+ while (is_whitespace(*p->head)) {
+ p->head += 1;
+ }
+ /*ImxmlChar* docname_start = p->head;*/
+ if (!imxml_seek_next_char_dense_raw_text_with_comments_multi_(p, " \t\r\n>[", 6)) return false;
+ /*ImxmlChar* docname_end = p->head;*/
+
+ while (true) {
+ #ifdef IMXML_CHECK_BOUNDS
+ if (*p->head == 0) return false;
+ #endif
+ if (*p->head == '>') {
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ p->depth = 0;
+ #endif
+ return true;
+ }
+ if (*p->head == '[') {
+ #ifdef IMXML_SUPPORT_CUSTOM_ENTITIES
+ ImxmlString entity_key;
+ ImxmlString entity_value;
+ /* parse entities */
+ while (imxml_seek_next_char_dense_raw_text_with_comments_multi_(p, "<]", 2)) {
+ #ifdef IMXML_CHECK_BOUNDS
+ if (*p->head == 0) return false;
+ #endif
+ if (*p->head == ']') break;
+
+ imxml_strlit89(s, "!ENTITY");
+ if (!xml_tag_expect_str(p, s)) return false;
+
+ while (is_whitespace(*p->head)) {
+ p->head += 1;
+ }
+
+ {
+ ImxmlCharSequence const entity_key_start = p->head;
+ if (!imxml_seek_next_char_dense_raw_text_multi_(p, " \t\r\n", 4)) return false;
+ entity_key.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - entity_key_start);
+ entity_key.IMXML_STRITEMS = entity_key_start;
+ }
+
+ if (!imxml_seek_next_char_dense_raw_text_multi_(p, "\"'", 2)) return false;
+ p->head += 1;
+ {
+ ImxmlCharSequence const entity_value_start = p->head;
+ if (*(p->head - 1) == '"') {
+ if (!xml_skip_dstring(p)) return false;
+ } else {
+ ASSERT(*(p->head - 1) == '\'');
+ if (!xml_skip_sstring(p)) return false;
+ }
+ entity_value.IMXML_STRCOUNT = (ImxmlStrLen)(p->head - entity_value_start - 1);
+ entity_value.IMXML_STRITEMS = entity_value_start;
+ }
+
+ if (register_entity) register_entity(entity_key, entity_value);
+ }
+ continue;
+ #else
+ (void)register_entity;
+ if (!imxml_seek_next_char_default(p, ']')) return false;
+ #endif /*IMXML_SUPPORT_CUSTOM_ENTITIES*/
+ }
+ p->head += 1;
+ }
+
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ p->depth = 0;
+ #endif
+ return true;
+}
+
+IMXMLAPI bool xml_parse_header (XmlParser* const p, ImxmlEntityRegisterFunc register_entity) {
+ ImxmlString s;
+ imxml_strlit89(s, "?xml");
+ if (!xml_tag_expect_str(p, s)) return false;
+
+ {
+ ImxmlCharSequence const tmp = p->head;
+ if (!xml_parse_doctype(p, register_entity)) {
+ p->head = tmp;
+ }
+ }
+
+ #ifdef IMXML_SUPPORT_NAMESPACES
+ p->depth = 0;
+ #endif
+ return true;
+}
+
+static inline size_t imxml_round_up_ (size_t n, size_t target) {
+ return ((n + target - 1) / target) * target;
+}
+
+#ifdef IMXML_LINUX
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+IMXMLAPI char* imxml_linux_file_open (
+ char const* const file_name,
+ size_t* const mapped_size,
+ bool populate
+) {
+ int const flags = MAP_PRIVATE | MAP_FIXED | (populate ? MAP_POPULATE : 0);
+ char* file;
+ size_t map_size;
+ long page_size;
+ size_t file_size;
+ struct stat sb;
+ int fd = open(file_name, O_RDONLY);
+ if (fd < 0) goto err_open;
+ if (fstat(fd, &sb)) goto err_fstat;
+ file_size = (size_t)sb.st_size;
+ if (file_size == 0) goto err_fstat;
+
+ page_size = sysconf(_SC_PAGESIZE);
+ if (page_size == -1) goto err_sysconf;
+ map_size = imxml_round_up_(file_size + 64 /*64 in case AVX512*/, (size_t)page_size);
+
+ file = (char*)mmap(NULL, map_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (file == (void*)-1) goto err_mmap_padding;
+ *mapped_size = map_size;
+ if (mmap(file, file_size, PROT_READ, flags, fd, 0) == (void*)-1) goto err_mmap_file;
+ close(fd);
+
+ return file;
+
+ err_mmap_file:
+ munmap(file, map_size);
+ err_mmap_padding:
+ err_sysconf:
+ err_fstat:
+ close(fd);
+ err_open:
+ return 0;
+}
+IMXMLAPI void imxml_linux_file_close (char* const file, size_t const file_size) {
+ munmap(file, file_size);
+}
+#endif /*IMXML_LINUX*/
+
+#undef scalar_set1
+#undef scalar_loadu
+#undef scalar_cmpeq
+#undef scalar_or
+#undef scalar_movemask
+
+#undef mmx_loadu
+
+#undef IF_BOUNDS
+#undef IF_SINGLE_QUOTES
+#undef IF_DOUBLE_QUOTES
+#undef IF_COMMENTS
+#undef IF_CDATA
+#undef IF_COMMENTS_OR_CDATA
+#undef IF_SEEK_MULTIPLE
+#undef IF_NO_SEEK_MULTIPLE
+
+#undef SCALAR
+#undef MMX
+#undef SSE2
+#undef AVX2
+
+#endif /*IMXML_IMPLEMENTATION*/
+#ifdef __cplusplus
+}
+#endif
+#endif /*IMXML_H_*/
+
+/* === REVISION HISTORY ===
+ * 0.1.0 (2026-08-17) Experimental release
+*/
+
+/* === LICENSE ===
+ * Copyright 2026 Steven Van Dorp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+*/
+