#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 #define THREADLOCAL // We disable thread-local by defining it as nothing #define IMXML_IMPLEMENTATION #include "imxml.h" #include // 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 and 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 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 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 `` 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 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 = "" 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 #if !defined(IMXML_CUSTOM_STDBOOL_H) && defined(__STDC_VERSION__) #include #endif #ifndef IMXML_CUSTOM_STDINT_H #include #endif #if !defined(IMXML_CUSTOM_SIMD_INTRINSICS) && !IMXML_SCALAR #if IMXML_AVX2 #include #else #include #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. `` 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 ("