diff options
Diffstat (limited to 'imxml.h')
| -rw-r--r-- | imxml.h | 1934 |
1 files changed, 1934 insertions, 0 deletions
@@ -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. E), + * 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. +*/ + |
