summaryrefslogtreecommitdiff
path: root/imxml.h
diff options
context:
space:
mode:
Diffstat (limited to 'imxml.h')
-rw-r--r--imxml.h1934
1 files changed, 1934 insertions, 0 deletions
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.
+*/
+