leptris 1.9.201.4-arm-linux → 1.9.207.0-arm-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/Rakefile +8 -2
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +25 -0
- data/lib/leptris/xml/dtd.rb +113 -0
- data/lib/leptris/xml/ffi.rb +75 -0
- data/lib/leptris/xml.rb +1 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/libleptris/CHANGELOG.md +79 -0
- data/vendor-src/libleptris/CLAUDE.md +1 -1
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/README.adoc +26 -0
- data/vendor-src/libleptris/TODO.max-perf/2-3-oneshot-tokenizer-design.md +98 -0
- data/vendor-src/libleptris/cli/commands/validate.c +4 -2
- data/vendor-src/libleptris/docs/guide/parsing.md +30 -0
- data/vendor-src/libleptris/scripts/check_export_surface.py +20 -0
- data/vendor-src/libleptris/src/CMakeLists.txt +44 -1
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +7 -1
- data/vendor-src/libleptris/src/include/leptris/dom/document.h +7 -1
- data/vendor-src/libleptris/src/include/leptris/dom/element.h +7 -1
- data/vendor-src/libleptris/src/include/leptris/dom/serialize.h +7 -1
- data/vendor-src/libleptris/src/include/leptris/dtd.h +46 -9
- data/vendor-src/libleptris/src/include/leptris/error.h +63 -1
- data/vendor-src/libleptris/src/include/leptris/html.h +79 -0
- data/vendor-src/libleptris/src/include/leptris/sax/sax.h +20 -1
- data/vendor-src/libleptris/src/include/leptris/types.h +29 -1
- data/vendor-src/libleptris/src/include/leptris/xpath/xpath.h +7 -1
- data/vendor-src/libleptris/src/include/leptris.h +59 -37
- data/vendor-src/libleptris/src/leptris/core.c +28 -0
- data/vendor-src/libleptris/src/leptris/dom/diag.h +2 -13
- data/vendor-src/libleptris/src/leptris/dom/digest.c +5 -1
- data/vendor-src/libleptris/src/leptris/dom/doctype.c +27 -0
- data/vendor-src/libleptris/src/leptris/dom/element.c +54 -9
- data/vendor-src/libleptris/src/leptris/dom/element.h +5 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +46 -6
- data/vendor-src/libleptris/src/leptris/dtd/model.h +6 -0
- data/vendor-src/libleptris/src/leptris/dtd/parser.c +49 -12
- data/vendor-src/libleptris/src/leptris/error.c +22 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +61 -6
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +156 -42
- data/vendor-src/libleptris/src/leptris/leptris.c +11 -2
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +23 -1
- data/vendor-src/libleptris/src/leptris/sax/pull.c +62 -18
- data/vendor-src/libleptris/src/leptris/sax/sax_internal.h +7 -0
- data/vendor-src/libleptris/src/leptris/sax/streaming.c +155 -4
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +19 -2
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +126 -0
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +653 -151
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +4 -2
- data/vendor-src/libleptris/src/leptris/xslt/xslt_functions.c +9 -5
- data/vendor-src/libleptris/test/dom/test_digest.cpp +36 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +24 -0
- data/vendor-src/libleptris/test/dom/test_node_surface_parity.cpp +81 -0
- data/vendor-src/libleptris/test/dtd/test_dtd_validate.cpp +113 -0
- data/vendor-src/libleptris/test/html/test_html.cpp +51 -0
- data/vendor-src/libleptris/test/parser/test_parser.cpp +43 -0
- data/vendor-src/libleptris/test/sax/test_pull.cpp +32 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/replace.xml +865 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/string-join.xml +449 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/tokenize.xml +639 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +74 -6
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +7 -1
|
@@ -44,7 +44,17 @@ extern "C" {
|
|
|
44
44
|
# define LEPTRIS_API
|
|
45
45
|
# endif
|
|
46
46
|
# else
|
|
47
|
-
|
|
47
|
+
/* GCC LTO internalizes even visibility("default") symbols with
|
|
48
|
+
* no in-library caller (#1154/#1196/#1204 class: the CLI under
|
|
49
|
+
* -flto, ppc64le shared builds). `externally_visible` is the
|
|
50
|
+
* GCC-side partner of `used`; clang accepts and ignores it, so
|
|
51
|
+
* it is scoped to GCC to keep -Wall clean elsewhere. */
|
|
52
|
+
# if defined(__GNUC__) && !defined(__clang__)
|
|
53
|
+
# define LEPTRIS_API __attribute__((visibility("default"), used, \
|
|
54
|
+
externally_visible))
|
|
55
|
+
# else
|
|
56
|
+
# define LEPTRIS_API __attribute__((visibility("default"), used))
|
|
57
|
+
# endif
|
|
48
58
|
# endif
|
|
49
59
|
#endif
|
|
50
60
|
#endif /* LEPTRIS_FOR_BINDGEN */
|
|
@@ -793,42 +803,7 @@ LEPTRIS_API LeptrisStatus leptris_document_set_root(LeptrisDocument doc,
|
|
|
793
803
|
*/
|
|
794
804
|
LEPTRIS_API LeptrisDocument leptris_parse_string(const char* xml, size_t length, LeptrisStatus* status);
|
|
795
805
|
|
|
796
|
-
|
|
797
|
-
* Parse an HTML string into a document (issue #659)
|
|
798
|
-
*
|
|
799
|
-
* Tolerant HTML4/5 parse into the standard DOM: implied end tags
|
|
800
|
-
* (p/li/td/tr/th/dt/dd/option/...), void elements, raw-text
|
|
801
|
-
* <script>/<style>, minimized + unquoted attribute values,
|
|
802
|
-
* case-insensitive tag/attribute names, and the HTML named-entity
|
|
803
|
-
* table. Behaviors follow libxml2's HTMLparser as exposed by
|
|
804
|
-
* Nokogiri (fragment shape: no synthesized <html>/<head>/<body>,
|
|
805
|
-
* no implied <tbody>). Malformed input never fails the parse — it
|
|
806
|
-
* degrades to text; a document with no nodes at all is an error.
|
|
807
|
-
* The result serializes and queries (XPath/XSLT) like any
|
|
808
|
-
* libleptris document.
|
|
809
|
-
*
|
|
810
|
-
* @param html HTML input (must be valid UTF-8)
|
|
811
|
-
* @param length Length of input in bytes
|
|
812
|
-
* @param status Output status code (can be NULL)
|
|
813
|
-
* @return Document handle or NULL on error
|
|
814
|
-
*
|
|
815
|
-
* Memory: Caller must call leptris_document_free() when done
|
|
816
|
-
* Thread safety: Not thread-safe. One document per thread.
|
|
817
|
-
*/
|
|
818
|
-
LEPTRIS_API LeptrisDocument leptris_parse_html_string(const char* html,
|
|
819
|
-
size_t length,
|
|
820
|
-
LeptrisStatus* status);
|
|
821
|
-
|
|
822
|
-
/* HTML4/libxml2-compatibility mode (#659): identical tolerant
|
|
823
|
-
* tokenizer and Nokogiri document shape, except leading
|
|
824
|
-
* script/style content stays in <body> (libxml2's shape —
|
|
825
|
-
* title/meta/link/base still lift into the implied head). Bindings
|
|
826
|
-
* that must match Nokogiri byte-for-byte pin this entry;
|
|
827
|
-
* leptris_parse_html_string is the WHATWG-conformant engine.
|
|
828
|
-
* Memory contract identical to leptris_parse_html_string. */
|
|
829
|
-
LEPTRIS_API LeptrisDocument leptris_parse_html4_string(const char* html,
|
|
830
|
-
size_t length,
|
|
831
|
-
LeptrisStatus* status);
|
|
806
|
+
#include "leptris/html.h"
|
|
832
807
|
|
|
833
808
|
/* ---- RELAX NG (#878) ------------------------------------------------
|
|
834
809
|
* Phase 1: parse a RELAX NG XML-syntax schema into the compiled
|
|
@@ -1092,6 +1067,18 @@ LEPTRIS_API LeptrisStatus leptris_document_set_encoding(
|
|
|
1092
1067
|
LEPTRIS_API LeptrisStatus leptris_document_set_standalone(
|
|
1093
1068
|
LeptrisDocument doc, int standalone);
|
|
1094
1069
|
|
|
1070
|
+
/**
|
|
1071
|
+
* Un-set the XML declaration (#1229): the document stops being
|
|
1072
|
+
* declaration-bearing and serialization emits none — exactly as if
|
|
1073
|
+
* the input had none. Clears the version/encoding strings and the
|
|
1074
|
+
* standalone marker. Idempotent.
|
|
1075
|
+
*
|
|
1076
|
+
* @param doc Document handle
|
|
1077
|
+
* @return LEPTRIS_OK, or LEPTRIS_ERROR_NULL_ARG
|
|
1078
|
+
*/
|
|
1079
|
+
LEPTRIS_API LeptrisStatus leptris_document_clear_declaration(
|
|
1080
|
+
LeptrisDocument doc);
|
|
1081
|
+
|
|
1095
1082
|
/**
|
|
1096
1083
|
* Programmatic DOCTYPE (#1094): create and set the document's
|
|
1097
1084
|
* DOCTYPE (root name + optional PUBLIC/SYSTEM external
|
|
@@ -1105,6 +1092,19 @@ LEPTRIS_API LeptrisDoctype leptris_document_set_doctype(
|
|
|
1105
1092
|
LeptrisDocument doc, const char* name,
|
|
1106
1093
|
const char* public_id, const char* system_id);
|
|
1107
1094
|
|
|
1095
|
+
/**
|
|
1096
|
+
* Un-set the document's DOCTYPE (#1229). The LeptrisDoctype node
|
|
1097
|
+
* stays pool-owned and readable until leptris_document_free; it
|
|
1098
|
+
* simply leaves the document children chain and the serializer's
|
|
1099
|
+
* view. leptris_document_internal_subset returns NULL afterwards.
|
|
1100
|
+
*
|
|
1101
|
+
* @param doc Document handle
|
|
1102
|
+
* @return LEPTRIS_OK; LEPTRIS_ERROR_NOT_FOUND when no DOCTYPE is
|
|
1103
|
+
* set; LEPTRIS_ERROR_NULL_ARG for a NULL document.
|
|
1104
|
+
*/
|
|
1105
|
+
LEPTRIS_API LeptrisStatus leptris_document_remove_doctype(
|
|
1106
|
+
LeptrisDocument doc);
|
|
1107
|
+
|
|
1108
1108
|
/**
|
|
1109
1109
|
* Get the document's internal DTD subset — the DOCTYPE declaration
|
|
1110
1110
|
* (TODO 148 Phase 2).
|
|
@@ -3537,6 +3537,28 @@ LEPTRIS_API leptris_allocation_function leptris_get_memory_allocation_function(v
|
|
|
3537
3537
|
*/
|
|
3538
3538
|
LEPTRIS_API leptris_deallocation_function leptris_get_memory_deallocation_function(void);
|
|
3539
3539
|
|
|
3540
|
+
/**
|
|
3541
|
+
* Allocate a buffer from the engine's heap (#1219).
|
|
3542
|
+
*
|
|
3543
|
+
* FFI callers that must hand the engine a buffer it later frees —
|
|
3544
|
+
* notably DTD parameter-entity loaders (leptris_dtd_set_pe_loader)
|
|
3545
|
+
* — allocate through this entry so allocator/CRT mismatches cannot
|
|
3546
|
+
* corrupt the heap (the engine DLL may link a different CRT than
|
|
3547
|
+
* the host language runtime). Routes through the custom allocator
|
|
3548
|
+
* when one is installed (leptris_set_memory_management_functions),
|
|
3549
|
+
* malloc otherwise; the engine releases loader buffers through the
|
|
3550
|
+
* matching deallocation path.
|
|
3551
|
+
*
|
|
3552
|
+
* @param len Bytes to allocate
|
|
3553
|
+
* @return Buffer from the engine's heap (release with
|
|
3554
|
+
* `leptris_free_string`), or NULL when len is 0 or the
|
|
3555
|
+
* allocation fails
|
|
3556
|
+
*
|
|
3557
|
+
* Memory: Caller-owned unless handed to the engine (PE-loader
|
|
3558
|
+
* buffers are consumed and freed by the parser).
|
|
3559
|
+
*/
|
|
3560
|
+
LEPTRIS_API char* leptris_alloc_buffer(size_t len);
|
|
3561
|
+
|
|
3540
3562
|
/* ============================================================================
|
|
3541
3563
|
* Per-Document Allocator Hooks (TODO 74)
|
|
3542
3564
|
*
|
|
@@ -96,6 +96,14 @@ LEPTRIS_API leptris_deallocation_function leptris_get_memory_deallocation_functi
|
|
|
96
96
|
return g_leptris_dealloc_function;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* #1219: engine-heap buffer for FFI callers whose buffers the
|
|
100
|
+
* engine later frees (DTD PE loaders). The hook pair keeps the
|
|
101
|
+
* custom-allocator configuration symmetric. */
|
|
102
|
+
LEPTRIS_API char* leptris_alloc_buffer(size_t len) {
|
|
103
|
+
if (len == 0) return NULL;
|
|
104
|
+
return (char*)leptris_alloc_hook(len);
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
LEPTRIS_API LeptrisStatus leptris_document_set_allocators(
|
|
100
108
|
LeptrisDocument doc,
|
|
101
109
|
leptris_allocation_function alloc,
|
|
@@ -199,3 +207,23 @@ LEPTRIS_API LeptrisStatus leptris_document_set_standalone(
|
|
|
199
207
|
doc->had_declaration = 1;
|
|
200
208
|
return LEPTRIS_OK;
|
|
201
209
|
}
|
|
210
|
+
|
|
211
|
+
/* #1229: the remove-half — back to as-if-the-input-had-none. The
|
|
212
|
+
* setters' malloc'd strings are released here (document_free checks
|
|
213
|
+
* for NULL), and the standalone marker returns to unset. */
|
|
214
|
+
LEPTRIS_API LeptrisStatus leptris_document_clear_declaration(
|
|
215
|
+
struct leptris_document* doc) {
|
|
216
|
+
if (!doc) return LEPTRIS_ERROR_NULL_ARG;
|
|
217
|
+
if (doc->xml_version) {
|
|
218
|
+
leptris_free(doc->xml_version);
|
|
219
|
+
doc->xml_version = NULL;
|
|
220
|
+
}
|
|
221
|
+
if (doc->encoding) {
|
|
222
|
+
leptris_free(doc->encoding);
|
|
223
|
+
doc->encoding = NULL;
|
|
224
|
+
}
|
|
225
|
+
doc->standalone = -1;
|
|
226
|
+
doc->decl_encoding_verbatim = 0;
|
|
227
|
+
doc->had_declaration = 0;
|
|
228
|
+
return LEPTRIS_OK;
|
|
229
|
+
}
|
|
@@ -17,20 +17,9 @@
|
|
|
17
17
|
extern "C" {
|
|
18
18
|
#endif
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
LEPTRIS_DIAG_INVALID = 0,
|
|
22
|
-
LEPTRIS_DIAG_NOT_ALLOWED_ANYWHERE, /* name never valid in model */
|
|
23
|
-
LEPTRIS_DIAG_NOT_ALLOWED_HERE, /* valid name, wrong position */
|
|
24
|
-
LEPTRIS_DIAG_NOT_ALLOWED_YET, /* later; missing required first */
|
|
25
|
-
LEPTRIS_DIAG_INCOMPLETE, /* content ends before required */
|
|
26
|
-
LEPTRIS_DIAG_MISSING_REQUIRED_ATTR,
|
|
27
|
-
LEPTRIS_DIAG_ATTR_NOT_ALLOWED,
|
|
28
|
-
LEPTRIS_DIAG_ATTR_VALUE_INVALID,
|
|
29
|
-
LEPTRIS_DIAG_CHAR_CONTENT_INVALID, /* data/value/list mismatch */
|
|
30
|
-
LEPTRIS_DIAG_COUNT_
|
|
31
|
-
} LeptrisDiagKind;
|
|
20
|
+
/* LeptrisDiagKind lives in leptris/types.h (public, #1200). */
|
|
32
21
|
|
|
33
|
-
typedef struct {
|
|
22
|
+
typedef struct LeptrisDiag {
|
|
34
23
|
LeptrisDiagKind kind;
|
|
35
24
|
char* offender_name; /* captured at emit time (strdup'd);
|
|
36
25
|
* NULL when the diag has no offender.
|
|
@@ -181,7 +181,11 @@ static uint64_t digest_element(LeptrisElement e, LeptrisDigestFlags flags) {
|
|
|
181
181
|
attrs[n].value_len = leptris_attr_value_sv(a).length;
|
|
182
182
|
n++;
|
|
183
183
|
}
|
|
184
|
-
|
|
184
|
+
/* ATTR_ORDER (#1200): document order is canonical — the
|
|
185
|
+
* walk above already collected in document order, so
|
|
186
|
+
* skip the sort and attribute order feeds the hash. */
|
|
187
|
+
if (!(flags & LEPTRIS_DIGEST_ATTR_ORDER))
|
|
188
|
+
qsort(attrs, n, sizeof(DigestAttr), digest_attr_cmp);
|
|
185
189
|
}
|
|
186
190
|
h = digest_mix_u64(h, n);
|
|
187
191
|
for (size_t i = 0; i < n; i++) {
|
|
@@ -74,6 +74,33 @@ LEPTRIS_API LeptrisDoctype leptris_document_set_doctype(
|
|
|
74
74
|
return (LeptrisDoctype)dt;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/* #1229: the remove-half of set_doctype. The node is pool-owned, so
|
|
78
|
+
* it is unlinked (document children chain, when linked) — never
|
|
79
|
+
* freed; it stays readable until leptris_document_free. */
|
|
80
|
+
LEPTRIS_API LeptrisStatus leptris_document_remove_doctype(
|
|
81
|
+
LeptrisDocument doc) {
|
|
82
|
+
if (!doc) return LEPTRIS_ERROR_NULL_ARG;
|
|
83
|
+
LeptrisDoctypeNode* dt = (LeptrisDoctypeNode*)doc->doctype;
|
|
84
|
+
if (!dt) return LEPTRIS_ERROR_NOT_FOUND;
|
|
85
|
+
LeptrisNode* prev = NULL;
|
|
86
|
+
for (LeptrisNode* c = (LeptrisNode*)doc->doc_children_head; c; ) {
|
|
87
|
+
LeptrisNode* next = leptris_node_get_next_sibling(c);
|
|
88
|
+
if (c == (LeptrisNode*)dt) {
|
|
89
|
+
if (prev)
|
|
90
|
+
leptris_node_set_next_sibling(prev, next);
|
|
91
|
+
else
|
|
92
|
+
doc->doc_children_head = next;
|
|
93
|
+
if (doc->doc_children_tail == c)
|
|
94
|
+
doc->doc_children_tail = prev;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
prev = c;
|
|
98
|
+
c = next;
|
|
99
|
+
}
|
|
100
|
+
doc->doctype = NULL;
|
|
101
|
+
return LEPTRIS_OK;
|
|
102
|
+
}
|
|
103
|
+
|
|
77
104
|
LEPTRIS_API const char* leptris_doctype_get_name(LeptrisDoctypeNode* doctype) {
|
|
78
105
|
return doctype ? doctype->name : NULL;
|
|
79
106
|
}
|
|
@@ -687,19 +687,50 @@ const char* leptris_element_lookup_namespace(LeptrisElement elem,
|
|
|
687
687
|
}
|
|
688
688
|
|
|
689
689
|
/* Children manipulation */
|
|
690
|
-
void
|
|
691
|
-
|
|
690
|
+
void leptris_element_append_child_hint_doc(LeptrisElement elem,
|
|
691
|
+
LeptrisNode* child,
|
|
692
|
+
struct leptris_document* doc,
|
|
693
|
+
LeptrisNode* hint);
|
|
692
694
|
void leptris_element_append_child_internal(LeptrisElement elem, LeptrisNode* child) {
|
|
693
695
|
if (!elem || !child) return;
|
|
694
|
-
|
|
695
|
-
|
|
696
|
+
leptris_element_append_child_hint_doc(elem, child,
|
|
697
|
+
leptris_element_get_document(elem),
|
|
698
|
+
NULL);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* #1225: caller-supplied tail hint. The HTML builder tracks each
|
|
702
|
+
* open element's last child itself — the shared direct-mapped
|
|
703
|
+
* tail cache loses a persistent parent to an address collision
|
|
704
|
+
* every ~1KB of fresh pool allocations, and each eviction costs
|
|
705
|
+
* an O(children) walk (quadratic on table/text-heavy pages). The
|
|
706
|
+
* hint is validated (parent back-pointer + no next sibling) and
|
|
707
|
+
* silently ignored when stale — callers stay correct through the
|
|
708
|
+
* cache/walk fallback. */
|
|
709
|
+
void leptris_element_append_child_tail(LeptrisElement elem,
|
|
710
|
+
LeptrisNode* child,
|
|
711
|
+
struct leptris_document* doc,
|
|
712
|
+
LeptrisNode* hint) {
|
|
713
|
+
if (!elem || !child) return;
|
|
714
|
+
if (hint &&
|
|
715
|
+
(leptris_node_parent_inline(hint) != (LeptrisElement)elem ||
|
|
716
|
+
leptris_node_get_next_sibling(hint) != NULL))
|
|
717
|
+
hint = NULL;
|
|
718
|
+
leptris_element_append_child_hint_doc(elem, child, doc, hint);
|
|
696
719
|
}
|
|
697
720
|
|
|
698
|
-
/* doc is the caller-resolved document (NULL for detached trees) —
|
|
699
|
-
* the public mutation path resolves it once instead of paying the
|
|
700
|
-
* root walk + map lookup on every append (TODO 195c). */
|
|
701
721
|
void leptris_element_append_child_internal_doc(LeptrisElement elem, LeptrisNode* child,
|
|
702
722
|
struct leptris_document* doc) {
|
|
723
|
+
leptris_element_append_child_hint_doc(elem, child, doc, NULL);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* doc is the caller-resolved document (NULL for detached trees) —
|
|
727
|
+
* the public mutation path resolves it once instead of paying the
|
|
728
|
+
* root walk + map lookup on every append (TODO 195c). hint is a
|
|
729
|
+
* pre-validated last-child hint (NULL = none). */
|
|
730
|
+
void leptris_element_append_child_hint_doc(LeptrisElement elem,
|
|
731
|
+
LeptrisNode* child,
|
|
732
|
+
struct leptris_document* doc,
|
|
733
|
+
LeptrisNode* hint) {
|
|
703
734
|
if (!elem || !child) return;
|
|
704
735
|
|
|
705
736
|
/* SAFETY: Verify node type field is valid before accessing it
|
|
@@ -746,6 +777,12 @@ void leptris_element_append_child_internal_doc(LeptrisElement elem, LeptrisNode*
|
|
|
746
777
|
mut_tail = s->child;
|
|
747
778
|
}
|
|
748
779
|
|
|
780
|
+
/* An empty child list must win over any cached tail: a NULL
|
|
781
|
+
* first_child with a non-NULL tail is a stale cache (or stale
|
|
782
|
+
* tail pointer), and appending through it splices the child
|
|
783
|
+
* onto a detached node while first_child stays NULL (#1220). */
|
|
784
|
+
int list_empty = (leptris_elem_first_child(elem) == NULL);
|
|
785
|
+
|
|
749
786
|
/* For element children, set up linked list structure */
|
|
750
787
|
if (child->type == LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
751
788
|
LeptrisElement child_elem = (LeptrisElement)child;
|
|
@@ -757,7 +794,11 @@ void leptris_element_append_child_internal_doc(LeptrisElement elem, LeptrisNode*
|
|
|
757
794
|
/* Append to end of children list.
|
|
758
795
|
* last_child may point to any node type (text/comment/etc.) so
|
|
759
796
|
* we set its next_sibling via the type-dispatching setter. */
|
|
760
|
-
LeptrisNode* last_node =
|
|
797
|
+
LeptrisNode* last_node =
|
|
798
|
+
list_empty ? NULL
|
|
799
|
+
: (hint ? hint
|
|
800
|
+
: (mut_tail ? mut_tail
|
|
801
|
+
: leptris_elem_last_child(elem)));
|
|
761
802
|
if (last_node) {
|
|
762
803
|
/* Lane 18 P3: sequential appends' tail is an element —
|
|
763
804
|
* the offset store directly; the type-dispatching node
|
|
@@ -776,7 +817,11 @@ void leptris_element_append_child_internal_doc(LeptrisElement elem, LeptrisNode*
|
|
|
776
817
|
elem->child_count++;
|
|
777
818
|
} else {
|
|
778
819
|
/* For non-element children (text, cdata, comment, pi), append to linked list */
|
|
779
|
-
LeptrisNode* last =
|
|
820
|
+
LeptrisNode* last =
|
|
821
|
+
list_empty ? NULL
|
|
822
|
+
: (hint ? hint
|
|
823
|
+
: (mut_tail ? mut_tail
|
|
824
|
+
: leptris_elem_last_child(elem)));
|
|
780
825
|
if (last) {
|
|
781
826
|
leptris_node_set_next_sibling(last, (LeptrisNode*)child);
|
|
782
827
|
leptris_elem_set_last_child(elem, (LeptrisNode*)child);
|
|
@@ -1062,6 +1062,11 @@ const char* leptris_element_lookup_namespace(LeptrisElement elem,
|
|
|
1062
1062
|
void leptris_element_append_child_internal(LeptrisElement elem, LeptrisNode* child);
|
|
1063
1063
|
void leptris_element_append_child_internal_doc(LeptrisElement elem, LeptrisNode* child,
|
|
1064
1064
|
struct leptris_document* doc);
|
|
1065
|
+
/* #1225: validated last-child hint skips the tail-cache/walk. */
|
|
1066
|
+
void leptris_element_append_child_tail(LeptrisElement elem,
|
|
1067
|
+
LeptrisNode* child,
|
|
1068
|
+
struct leptris_document* doc,
|
|
1069
|
+
LeptrisNode* hint);
|
|
1065
1070
|
void leptris_element_prepend_child_internal(LeptrisElement elem, LeptrisNode* child);
|
|
1066
1071
|
|
|
1067
1072
|
/* Bulk allocation for subtree copy (10-15% faster for large subtrees) */
|
|
@@ -51,7 +51,14 @@ static char* mut_name_carve(struct leptris_document* doc,
|
|
|
51
51
|
* The store writes the full word — reserve it so the block-bound
|
|
52
52
|
* check accounts for the write, never just the logical size. */
|
|
53
53
|
if (name_len <= 8) need = sizeof(struct leptris_document*) + 9;
|
|
54
|
-
|
|
54
|
+
/* Keep every slot pointer-aligned: namebp reads the doc
|
|
55
|
+
* backpointer as ((struct leptris_document**)name)[-1]. The
|
|
56
|
+
* raw sizes above (17B for the S8 path) walk the cursor
|
|
57
|
+
* through every alignment mod 8 — misaligned pointer stores
|
|
58
|
+
* (UB, faults on alignment-strict targets). */
|
|
59
|
+
need = (need + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
|
|
60
|
+
if (!doc->mut_name_cursor ||
|
|
61
|
+
doc->mut_name_cursor + need > doc->mut_name_end) {
|
|
55
62
|
struct leptris_mut_name_block* blk =
|
|
56
63
|
(struct leptris_mut_name_block*)malloc(
|
|
57
64
|
sizeof(struct leptris_mut_name_block) + MUT_NAME_BLOCK_BYTES);
|
|
@@ -107,7 +114,12 @@ static char* mut_str_carve(struct leptris_document* doc, const char* s,
|
|
|
107
114
|
size_t len) {
|
|
108
115
|
if (len + 1 > MUT_NAME_BLOCK_BYTES / 4) return NULL;
|
|
109
116
|
size_t need = 8 + len + 1;
|
|
110
|
-
|
|
117
|
+
/* Round the stride like mut_name_carve: this carve shares the
|
|
118
|
+
* name block's cursor, and an unrounded advance here misaligns
|
|
119
|
+
* every later namebp slot (doc backpointer stores). */
|
|
120
|
+
need = (need + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
|
|
121
|
+
if (!doc->mut_name_cursor ||
|
|
122
|
+
doc->mut_name_cursor + need > doc->mut_name_end) {
|
|
111
123
|
struct leptris_mut_name_block* blk =
|
|
112
124
|
(struct leptris_mut_name_block*)malloc(
|
|
113
125
|
sizeof(struct leptris_mut_name_block) + MUT_NAME_BLOCK_BYTES);
|
|
@@ -536,11 +548,39 @@ LeptrisStatus leptris_element_remove_child(LeptrisElement parent, LeptrisElement
|
|
|
536
548
|
LeptrisStatus leptris_element_remove_all_children(LeptrisElement elem) {
|
|
537
549
|
if (!elem) return LEPTRIS_ERROR_NULL_ARG;
|
|
538
550
|
|
|
539
|
-
/* Walk
|
|
540
|
-
|
|
551
|
+
/* Walk EVERY child. The element-filtered getters skip text and
|
|
552
|
+
* other non-element nodes, which left their parent backpointers
|
|
553
|
+
* dangling; a later append then validated a stale mutation-tail
|
|
554
|
+
* entry against the detached child and spliced the new subtree
|
|
555
|
+
* onto it instead of the emptied element (#1220: HTML set_text
|
|
556
|
+
* produced an empty <p/>). */
|
|
557
|
+
LeptrisNode* child =
|
|
558
|
+
leptris_node_first_child_internal((LeptrisNode*)elem);
|
|
541
559
|
while (child) {
|
|
542
|
-
|
|
543
|
-
|
|
560
|
+
LeptrisNode* next = leptris_node_get_next_sibling(child);
|
|
561
|
+
switch (child->type) {
|
|
562
|
+
case LEPTRIS_NODE_TYPE_ELEMENT:
|
|
563
|
+
leptris_elem_set_parent((LeptrisElement)child, NULL);
|
|
564
|
+
break;
|
|
565
|
+
case LEPTRIS_NODE_TYPE_TEXT:
|
|
566
|
+
leptris_textnode_set_parent((LeptrisTextNode*)child, NULL);
|
|
567
|
+
break;
|
|
568
|
+
case LEPTRIS_NODE_TYPE_COMMENT:
|
|
569
|
+
leptris_comment_set_parent((LeptrisCommentNode*)child, NULL);
|
|
570
|
+
break;
|
|
571
|
+
case LEPTRIS_NODE_TYPE_CDATA:
|
|
572
|
+
leptris_cdata_set_parent((LeptrisCDATANode*)child, NULL);
|
|
573
|
+
break;
|
|
574
|
+
case LEPTRIS_NODE_TYPE_PI:
|
|
575
|
+
leptris_pi_set_parent((LeptrisPINode*)child, NULL);
|
|
576
|
+
break;
|
|
577
|
+
case LEPTRIS_NODE_TYPE_ENTITY_REF:
|
|
578
|
+
leptris_entity_ref_set_parent(
|
|
579
|
+
(LeptrisEntityRefNode*)child, NULL);
|
|
580
|
+
break;
|
|
581
|
+
default:
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
544
584
|
child = next;
|
|
545
585
|
}
|
|
546
586
|
|
|
@@ -130,6 +130,12 @@ struct LeptrisDTD {
|
|
|
130
130
|
* from the document parser). ttdtd_free destroys the pool only
|
|
131
131
|
* when this flag is set. */
|
|
132
132
|
int owns_pool;
|
|
133
|
+
/* #1211: a declaration matched its keyword but the parser could
|
|
134
|
+
* not deliver the promised structure (malformed ELEMENT/ATTLIST/
|
|
135
|
+
* ENTITY/NOTATION). leptris_dtd_parse frees and returns NULL.
|
|
136
|
+
* Lenient paths — comments, PIs, unresolved external PEs,
|
|
137
|
+
* duplicates — never set this. */
|
|
138
|
+
int parse_failed;
|
|
133
139
|
|
|
134
140
|
/* External parameter-entity loader (application-owned I/O).
|
|
135
141
|
* NULL = external PEs are not resolvable and are skipped.
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
* Uses stateless parsing with hash table storage for O(1) lookup.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
#include "../../include/leptris/dtd.h"
|
|
9
10
|
#include "model.h"
|
|
10
11
|
#include "../memory/pool.h"
|
|
12
|
+
/* Engine-heap free for PE-loader buffers (#1219); declared
|
|
13
|
+
* locally — the full internal header tangles include order. */
|
|
14
|
+
extern void leptris_free_hook(void* ptr);
|
|
11
15
|
#include <stdlib.h>
|
|
12
16
|
#include <string.h>
|
|
13
17
|
#include <ctype.h>
|
|
@@ -187,11 +191,16 @@ static DTDEntityDecl* dtd_parse_entity(DTDParser* p) {
|
|
|
187
191
|
entity->notation_name = dtd_parse_name(p);
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
/* Skip to '>'
|
|
194
|
+
/* Skip to '>' - unterminated (input exhausted first) is a
|
|
195
|
+
* malformed declaration: the NULL-on-error contract (#1211). */
|
|
191
196
|
while (!dtd_at_end(p) && dtd_peek(p) != '>') {
|
|
192
197
|
dtd_advance(p);
|
|
193
198
|
}
|
|
194
|
-
if (dtd_peek(p) == '>')
|
|
199
|
+
if (dtd_peek(p) == '>') {
|
|
200
|
+
dtd_advance(p);
|
|
201
|
+
} else {
|
|
202
|
+
return NULL;
|
|
203
|
+
}
|
|
195
204
|
|
|
196
205
|
return entity;
|
|
197
206
|
}
|
|
@@ -226,6 +235,7 @@ static DTDElementDecl* dtd_parse_element(DTDParser* p) {
|
|
|
226
235
|
int depth = 0;
|
|
227
236
|
int in_quote = 0;
|
|
228
237
|
char quote_char = 0;
|
|
238
|
+
int closed = 0;
|
|
229
239
|
|
|
230
240
|
while (!dtd_at_end(p)) {
|
|
231
241
|
char c = dtd_peek(p);
|
|
@@ -240,12 +250,15 @@ static DTDElementDecl* dtd_parse_element(DTDParser* p) {
|
|
|
240
250
|
} else if (c == ')') {
|
|
241
251
|
depth--;
|
|
242
252
|
dtd_advance(p);
|
|
243
|
-
if (depth == 0) break;
|
|
253
|
+
if (depth == 0) { closed = 1; break; }
|
|
244
254
|
dtd_advance(p);
|
|
245
255
|
continue;
|
|
246
256
|
}
|
|
247
257
|
dtd_advance(p);
|
|
248
258
|
}
|
|
259
|
+
/* #1211: running out of input before the model closes is a
|
|
260
|
+
* malformed declaration — the NULL-on-error contract. */
|
|
261
|
+
if (!closed) return NULL;
|
|
249
262
|
|
|
250
263
|
size_t len = p->pos - start;
|
|
251
264
|
elem->content_model = (char*)leptris_pool_alloc(p->pool, len + 1);
|
|
@@ -262,11 +275,16 @@ static DTDElementDecl* dtd_parse_element(DTDParser* p) {
|
|
|
262
275
|
}
|
|
263
276
|
}
|
|
264
277
|
|
|
265
|
-
/* Skip to '>'
|
|
278
|
+
/* Skip to '>' - unterminated (input exhausted first) is a
|
|
279
|
+
* malformed declaration: the NULL-on-error contract (#1211). */
|
|
266
280
|
while (!dtd_at_end(p) && dtd_peek(p) != '>') {
|
|
267
281
|
dtd_advance(p);
|
|
268
282
|
}
|
|
269
|
-
if (dtd_peek(p) == '>')
|
|
283
|
+
if (dtd_peek(p) == '>') {
|
|
284
|
+
dtd_advance(p);
|
|
285
|
+
} else {
|
|
286
|
+
return NULL;
|
|
287
|
+
}
|
|
270
288
|
|
|
271
289
|
return elem;
|
|
272
290
|
}
|
|
@@ -305,11 +323,16 @@ static DTDNotationDecl* dtd_parse_notation(DTDParser* p) {
|
|
|
305
323
|
}
|
|
306
324
|
}
|
|
307
325
|
|
|
308
|
-
/* Skip to '>'
|
|
326
|
+
/* Skip to '>' - unterminated (input exhausted first) is a
|
|
327
|
+
* malformed declaration: the NULL-on-error contract (#1211). */
|
|
309
328
|
while (!dtd_at_end(p) && dtd_peek(p) != '>') {
|
|
310
329
|
dtd_advance(p);
|
|
311
330
|
}
|
|
312
|
-
if (dtd_peek(p) == '>')
|
|
331
|
+
if (dtd_peek(p) == '>') {
|
|
332
|
+
dtd_advance(p);
|
|
333
|
+
} else {
|
|
334
|
+
return NULL;
|
|
335
|
+
}
|
|
313
336
|
|
|
314
337
|
return notation;
|
|
315
338
|
}
|
|
@@ -357,7 +380,7 @@ LeptrisDTD* leptris_dtd_parse_internal_subset(const char* dtd_content, size_t le
|
|
|
357
380
|
* @param len Length in bytes
|
|
358
381
|
* @return 1 on success, -1 on invalid arguments
|
|
359
382
|
*/
|
|
360
|
-
void leptris_dtd_set_pe_loader(LeptrisDTD* dtd,
|
|
383
|
+
LEPTRIS_API void leptris_dtd_set_pe_loader(LeptrisDTD* dtd,
|
|
361
384
|
char* (*loader)(void* user_data,
|
|
362
385
|
const char* system_id,
|
|
363
386
|
size_t* out_len),
|
|
@@ -367,7 +390,7 @@ void leptris_dtd_set_pe_loader(LeptrisDTD* dtd,
|
|
|
367
390
|
dtd->pe_loader_user = user_data;
|
|
368
391
|
}
|
|
369
392
|
|
|
370
|
-
int leptris_dtd_parse_external_subset(LeptrisDTD* dtd, const char* content,
|
|
393
|
+
LEPTRIS_API int leptris_dtd_parse_external_subset(LeptrisDTD* dtd, const char* content,
|
|
371
394
|
size_t len) {
|
|
372
395
|
if (!dtd || !content || len == 0) return -1;
|
|
373
396
|
dtd_parse_into(dtd, content, len, 0);
|
|
@@ -443,7 +466,7 @@ if (text[i] == '%' && i + 1 < len) {
|
|
|
443
466
|
memcpy(grown + w + loaded, text + j + 1,
|
|
444
467
|
len - j - 1);
|
|
445
468
|
grown[w + loaded + (len - j - 1)] = '\0';
|
|
446
|
-
|
|
469
|
+
leptris_free_hook(got);
|
|
447
470
|
/* Recurse once for nested refs in the
|
|
448
471
|
* loaded content. */
|
|
449
472
|
char* rec = dtd_substitute_decl_pes(
|
|
@@ -451,7 +474,7 @@ if (text[i] == '%' && i + 1 < len) {
|
|
|
451
474
|
w + loaded + (len - j - 1), depth + 1);
|
|
452
475
|
return rec ? rec : grown;
|
|
453
476
|
}
|
|
454
|
-
|
|
477
|
+
leptris_free_hook(got);
|
|
455
478
|
}
|
|
456
479
|
}
|
|
457
480
|
if (val) {
|
|
@@ -587,16 +610,22 @@ static void dtd_parse_into(LeptrisDTD* dtd, const char* dtd_content,
|
|
|
587
610
|
DTDEntityDecl* entity = dtd_parse_entity(&parser);
|
|
588
611
|
if (entity) {
|
|
589
612
|
ttdtd_add_entity(dtd, entity);
|
|
613
|
+
} else {
|
|
614
|
+
dtd->parse_failed = 1; /* #1211 */
|
|
590
615
|
}
|
|
591
616
|
} else if (dtd_match(&parser, "<!ELEMENT")) {
|
|
592
617
|
DTDElementDecl* elem = dtd_parse_element(&parser);
|
|
593
618
|
if (elem) {
|
|
594
619
|
ttdtd_add_element(dtd, elem);
|
|
620
|
+
} else {
|
|
621
|
+
dtd->parse_failed = 1; /* #1211 */
|
|
595
622
|
}
|
|
596
623
|
} else if (dtd_match(&parser, "<!NOTATION")) {
|
|
597
624
|
DTDNotationDecl* notation = dtd_parse_notation(&parser);
|
|
598
625
|
if (notation) {
|
|
599
626
|
ttdtd_add_notation(dtd, notation);
|
|
627
|
+
} else {
|
|
628
|
+
dtd->parse_failed = 1; /* #1211 */
|
|
600
629
|
}
|
|
601
630
|
} else if (dtd_match(&parser, "<!ATTLIST")) {
|
|
602
631
|
/* Parse one or more <!ATTLIST element-name attr-decl+>
|
|
@@ -609,6 +638,7 @@ static void dtd_parse_into(LeptrisDTD* dtd, const char* dtd_content,
|
|
|
609
638
|
char* elem_name = dtd_parse_name(&parser);
|
|
610
639
|
if (!elem_name) {
|
|
611
640
|
/* Malformed; skip to '>' */
|
|
641
|
+
dtd->parse_failed = 1; /* #1211 */
|
|
612
642
|
while (!dtd_at_end(&parser) && dtd_peek(&parser) != '>') {
|
|
613
643
|
dtd_advance(&parser);
|
|
614
644
|
}
|
|
@@ -811,7 +841,7 @@ void leptris_dtd_free(LeptrisDTD* dtd) {
|
|
|
811
841
|
* leptris_dtd_parse_internal_subset() directly with the document's
|
|
812
842
|
* pool instead.
|
|
813
843
|
*/
|
|
814
|
-
LeptrisDTD* leptris_dtd_parse(const char* dtd_content, size_t len) {
|
|
844
|
+
LEPTRIS_API LeptrisDTD* leptris_dtd_parse(const char* dtd_content, size_t len) {
|
|
815
845
|
LeptrisMemoryPool* pool = leptris_pool_create();
|
|
816
846
|
if (!pool) return NULL;
|
|
817
847
|
LeptrisDTD* dtd = leptris_dtd_parse_internal_subset(dtd_content, len, pool);
|
|
@@ -823,5 +853,12 @@ LeptrisDTD* leptris_dtd_parse(const char* dtd_content, size_t len) {
|
|
|
823
853
|
* Document-pool DTDs (created via the internal parser path) leave
|
|
824
854
|
* owns_pool=0; their pool is destroyed with the document. */
|
|
825
855
|
dtd->owns_pool = 1;
|
|
856
|
+
/* #1211: the documented NULL-on-error contract — malformed
|
|
857
|
+
* declarations (keyword matched, structure undeliverable) free
|
|
858
|
+
* the whole pool; lenient skips never set parse_failed. */
|
|
859
|
+
if (dtd->parse_failed) {
|
|
860
|
+
leptris_pool_destroy(pool);
|
|
861
|
+
return NULL;
|
|
862
|
+
}
|
|
826
863
|
return dtd;
|
|
827
864
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include "common/port.h" /* LEPTRIS_THREAD_LOCAL (TODO.concurrency/01) */
|
|
8
8
|
#include <stdio.h>
|
|
9
9
|
#include <string.h>
|
|
10
|
+
#include "dom/diag.h" /* LeptrisDiag layout (#1200) */
|
|
10
11
|
|
|
11
12
|
/* Error state — THREAD-LOCAL since TODO.concurrency/01: concurrent
|
|
12
13
|
* parses (one document per thread) no longer race the channel. The
|
|
@@ -131,4 +132,25 @@ LEPTRIS_API const char* leptris_status_string(LeptrisStatus status) {
|
|
|
131
132
|
case LEPTRIS_ERROR_IO: return "I/O error";
|
|
132
133
|
default: return "Unknown error";
|
|
133
134
|
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ---- #1200: recover-class parse diagnostics ------------------- */
|
|
138
|
+
|
|
139
|
+
LEPTRIS_API size_t leptris_document_parse_diag_count(LeptrisDocument doc) {
|
|
140
|
+
return doc ? (size_t)doc->parse_diag_count : 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
LEPTRIS_API int leptris_document_parse_diag(LeptrisDocument doc,
|
|
144
|
+
size_t index,
|
|
145
|
+
LeptrisDiagKind* kind,
|
|
146
|
+
char* message,
|
|
147
|
+
size_t message_cap) {
|
|
148
|
+
if (!doc || index >= (size_t)doc->parse_diag_count) return 0;
|
|
149
|
+
struct LeptrisDiag* d = (struct LeptrisDiag*)doc->parse_diags + index;
|
|
150
|
+
if (kind) *kind = d->kind;
|
|
151
|
+
if (message && message_cap) {
|
|
152
|
+
strncpy(message, d->message, message_cap - 1);
|
|
153
|
+
message[message_cap - 1] = '\0';
|
|
154
|
+
}
|
|
155
|
+
return 1;
|
|
134
156
|
}
|