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
|
@@ -437,7 +437,47 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
437
437
|
* Careful: first_attribute_off == 0 means empty list. We can't decode
|
|
438
438
|
* the pointer and check for NULL — at offset 0 the decoded pointer
|
|
439
439
|
* is `elem` itself, which is non-NULL. Check the offset field. */
|
|
440
|
+
/* #1200: duplicate-attribute detection. Prefilter on (len,
|
|
441
|
+
* first byte) — a mismatching pair costs two loads; the memcmp
|
|
442
|
+
* only runs on genuine candidates. First definition wins
|
|
443
|
+
* (libxml2 recover semantics, and the digest's first-wins
|
|
444
|
+
* becomes official); the duplicate never enters the chain, it
|
|
445
|
+
* costs its already-carved slot. */
|
|
440
446
|
if (elem->first_attribute_off != 0) {
|
|
447
|
+
int dup = 0;
|
|
448
|
+
for (struct leptris_attribute* a =
|
|
449
|
+
(struct leptris_attribute*)((char*)elem +
|
|
450
|
+
elem->first_attribute_off);
|
|
451
|
+
a; a = leptris_attr_next(a)) {
|
|
452
|
+
if (a->name_view.length == attr->name_view.length &&
|
|
453
|
+
a->name_view.data[0] == attr->name_view.data[0] &&
|
|
454
|
+
memcmp(a->name_view.data, attr->name_view.data,
|
|
455
|
+
attr->name_view.length) == 0) {
|
|
456
|
+
dup = 1;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (dup) {
|
|
461
|
+
if (p->doc) {
|
|
462
|
+
extern struct LeptrisDiag* leptris_diag_emit(
|
|
463
|
+
struct LeptrisDiag**, int*, int*,
|
|
464
|
+
LeptrisDiagKind, LeptrisNodeRef,
|
|
465
|
+
const char*, ...);
|
|
466
|
+
char qname[130];
|
|
467
|
+
size_t ql = attr->name_view.length < 129
|
|
468
|
+
? attr->name_view.length : 129;
|
|
469
|
+
memcpy(qname, attr->name_view.data, ql);
|
|
470
|
+
qname[ql] = '\0';
|
|
471
|
+
leptris_diag_emit(&p->doc->parse_diags,
|
|
472
|
+
&p->doc->parse_diag_count,
|
|
473
|
+
&p->doc->parse_diag_cap,
|
|
474
|
+
LEPTRIS_DIAG_RECOVER, NULL,
|
|
475
|
+
"Attribute %s redefined", qname);
|
|
476
|
+
}
|
|
477
|
+
/* 1 = duplicate skipped (keep first): the caller skips
|
|
478
|
+
* the raw-attr registration so BOTH surfaces agree. */
|
|
479
|
+
return 1;
|
|
480
|
+
}
|
|
441
481
|
/* Cache should always be valid mid-parse. Fall back to walk
|
|
442
482
|
* only if cache is NULL (defensive — shouldn't happen). */
|
|
443
483
|
struct leptris_attribute* tail = p->current_elem_last_attr;
|
|
@@ -845,13 +885,16 @@ static int dp_parse_attrs(DParser* p, LeptrisElement elem) {
|
|
|
845
885
|
continue;
|
|
846
886
|
}
|
|
847
887
|
|
|
848
|
-
/* Regular attribute — zero-copy name/value, bulk-allocated
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
888
|
+
/* Regular attribute — zero-copy name/value, bulk-allocated
|
|
889
|
+
* struct. 1 = duplicate skipped: neither surface sees it. */
|
|
890
|
+
int arc = dp_add_attr_inline(p, elem, name_start, name_len,
|
|
891
|
+
val_start, val_len, has_amp,
|
|
892
|
+
has_ws, attr_colon);
|
|
893
|
+
if (arc < 0) return -1;
|
|
894
|
+
if (arc == 0 &&
|
|
895
|
+
dp_raw_attr(p, elem, name_start, name_len,
|
|
896
|
+
val_start, val_len) != 0)
|
|
852
897
|
return -1;
|
|
853
|
-
if (dp_raw_attr(p, elem, name_start, name_len,
|
|
854
|
-
val_start, val_len) != 0) return -1;
|
|
855
898
|
}
|
|
856
899
|
/* for (;;): every exit is a return inside the loop (0 = tag
|
|
857
900
|
* closed, 1 = self-closing, -1 = error) — there is no
|
|
@@ -1571,9 +1614,21 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1571
1614
|
if (close_local_len <= 8 &&
|
|
1572
1615
|
(p.probe_slack ||
|
|
1573
1616
|
close_local + 8 <= p.end + 1)) {
|
|
1617
|
+
/* The mask follows MEMORY order: memcpy fills the low
|
|
1618
|
+
* bytes on little-endian and the high bytes on
|
|
1619
|
+
* big-endian, so each dialect must mask the end its
|
|
1620
|
+
* load populated. The unconditional low-bytes mask
|
|
1621
|
+
* compared bytes past the name on big-endian and
|
|
1622
|
+
* failed every close tag (#1197). */
|
|
1623
|
+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
1624
|
+
uint64_t mask = (close_local_len == 8)
|
|
1625
|
+
? ~(uint64_t)0
|
|
1626
|
+
: (~(uint64_t)0 << (64 - close_local_len * 8));
|
|
1627
|
+
#else
|
|
1574
1628
|
uint64_t mask = (close_local_len == 8)
|
|
1575
1629
|
? ~(uint64_t)0
|
|
1576
1630
|
: ((1ull << (close_local_len * 8)) - 1);
|
|
1631
|
+
#endif
|
|
1577
1632
|
uint64_t a, b;
|
|
1578
1633
|
memcpy(&a, open_name, 8);
|
|
1579
1634
|
memcpy(&b, close_local, 8);
|
|
@@ -2722,6 +2722,13 @@ typedef struct {
|
|
|
2722
2722
|
/* #659 foreign content: per-slot namespace of the open stack
|
|
2723
2723
|
* (0 HTML, 1 SVG, 2 MathML) — parallel to open[]. */
|
|
2724
2724
|
uint8_t open_ns[256];
|
|
2725
|
+
/* #1225: last child of each open element — the builder's own
|
|
2726
|
+
* O(1) append tail. The doc-level direct-mapped tail cache
|
|
2727
|
+
* loses a persistent parent to an address collision roughly
|
|
2728
|
+
* every 1KB of fresh pool allocations; each miss walks the
|
|
2729
|
+
* whole child chain, which is quadratic on table/text-heavy
|
|
2730
|
+
* pages. Slots are refreshed on push (NULL) and by hb_put. */
|
|
2731
|
+
LeptrisNodeRef open_tail[256];
|
|
2725
2732
|
size_t depth;
|
|
2726
2733
|
LeptrisElement root; /* first top-level element */
|
|
2727
2734
|
LeptrisNodeRef top_head; /* top-level chain: text, comments, root */
|
|
@@ -3054,30 +3061,130 @@ static int h_is_formatting(const char* n) {
|
|
|
3054
3061
|
|
|
3055
3062
|
/* WHATWG "special" category (13.2.4.2) — the adoption agency's
|
|
3056
3063
|
* furthest-block candidates. */
|
|
3064
|
+
/* Generated tag classifier (#1218 hot path): one first-char
|
|
3065
|
+
* bucket walk replaces the ~78-entry strcmp scans that the
|
|
3066
|
+
* sampler pinned at ~21% of WHATWG parse (h_is_special_ww +
|
|
3067
|
+
* h_reconstructs). Keep in sync with the WHATWG special and
|
|
3068
|
+
* no-formatting sets. */
|
|
3069
|
+
typedef struct {
|
|
3070
|
+
char name[12];
|
|
3071
|
+
uint8_t len;
|
|
3072
|
+
uint8_t special; /* WHATWG 13.2.4 special set */
|
|
3073
|
+
uint8_t no_fmt; /* never reconstructed (formatting) */
|
|
3074
|
+
} HTagInfo;
|
|
3075
|
+
|
|
3076
|
+
static const HTagInfo h_tag_infos[] = {
|
|
3077
|
+
{"address", 7, 1, 1},
|
|
3078
|
+
{"applet", 6, 1, 0},
|
|
3079
|
+
{"area", 4, 1, 0},
|
|
3080
|
+
{"article", 7, 1, 1},
|
|
3081
|
+
{"aside", 5, 1, 1},
|
|
3082
|
+
{"base", 4, 1, 1},
|
|
3083
|
+
{"basefont", 8, 1, 1},
|
|
3084
|
+
{"bgsound", 7, 1, 1},
|
|
3085
|
+
{"blockquote", 10, 1, 1},
|
|
3086
|
+
{"body", 4, 1, 1},
|
|
3087
|
+
{"br", 2, 1, 0},
|
|
3088
|
+
{"button", 6, 1, 0},
|
|
3089
|
+
{"caption", 7, 1, 1},
|
|
3090
|
+
{"center", 6, 1, 1},
|
|
3091
|
+
{"col", 3, 1, 1},
|
|
3092
|
+
{"colgroup", 8, 1, 1},
|
|
3093
|
+
{"dd", 2, 1, 1},
|
|
3094
|
+
{"details", 7, 1, 1},
|
|
3095
|
+
{"dialog", 6, 1, 1},
|
|
3096
|
+
{"dir", 3, 1, 1},
|
|
3097
|
+
{"div", 3, 1, 1},
|
|
3098
|
+
{"dl", 2, 1, 1},
|
|
3099
|
+
{"dt", 2, 1, 1},
|
|
3100
|
+
{"embed", 5, 1, 0},
|
|
3101
|
+
{"fieldset", 8, 1, 1},
|
|
3102
|
+
{"figcaption", 10, 1, 1},
|
|
3103
|
+
{"figure", 6, 1, 1},
|
|
3104
|
+
{"footer", 6, 1, 1},
|
|
3105
|
+
{"form", 4, 1, 1},
|
|
3106
|
+
{"frame", 5, 1, 1},
|
|
3107
|
+
{"frameset", 8, 1, 1},
|
|
3108
|
+
{"h1", 2, 1, 1},
|
|
3109
|
+
{"h2", 2, 1, 1},
|
|
3110
|
+
{"h3", 2, 1, 1},
|
|
3111
|
+
{"h4", 2, 1, 1},
|
|
3112
|
+
{"h5", 2, 1, 1},
|
|
3113
|
+
{"h6", 2, 1, 1},
|
|
3114
|
+
{"head", 4, 1, 1},
|
|
3115
|
+
{"header", 6, 1, 1},
|
|
3116
|
+
{"hgroup", 6, 1, 1},
|
|
3117
|
+
{"hr", 2, 1, 1},
|
|
3118
|
+
{"html", 4, 1, 1},
|
|
3119
|
+
{"iframe", 6, 1, 0},
|
|
3120
|
+
{"img", 3, 1, 0},
|
|
3121
|
+
{"input", 5, 1, 0},
|
|
3122
|
+
{"keygen", 6, 1, 0},
|
|
3123
|
+
{"li", 2, 1, 1},
|
|
3124
|
+
{"link", 4, 1, 1},
|
|
3125
|
+
{"listing", 7, 1, 1},
|
|
3126
|
+
{"main", 4, 1, 1},
|
|
3127
|
+
{"marquee", 7, 1, 0},
|
|
3128
|
+
{"menu", 4, 1, 1},
|
|
3129
|
+
{"meta", 4, 1, 1},
|
|
3130
|
+
{"nav", 3, 1, 1},
|
|
3131
|
+
{"noembed", 7, 1, 0},
|
|
3132
|
+
{"noframes", 8, 1, 1},
|
|
3133
|
+
{"noscript", 8, 1, 0},
|
|
3134
|
+
{"object", 6, 1, 0},
|
|
3135
|
+
{"ol", 2, 1, 1},
|
|
3136
|
+
{"p", 1, 1, 1},
|
|
3137
|
+
{"param", 5, 1, 0},
|
|
3138
|
+
{"plaintext", 9, 1, 1},
|
|
3139
|
+
{"pre", 3, 1, 1},
|
|
3140
|
+
{"script", 6, 1, 1},
|
|
3141
|
+
{"search", 6, 1, 1},
|
|
3142
|
+
{"section", 7, 1, 1},
|
|
3143
|
+
{"select", 6, 1, 0},
|
|
3144
|
+
{"source", 6, 1, 0},
|
|
3145
|
+
{"style", 5, 1, 1},
|
|
3146
|
+
{"summary", 7, 1, 1},
|
|
3147
|
+
{"table", 5, 1, 1},
|
|
3148
|
+
{"tbody", 5, 1, 1},
|
|
3149
|
+
{"td", 2, 1, 1},
|
|
3150
|
+
{"template", 8, 1, 1},
|
|
3151
|
+
{"textarea", 8, 1, 1},
|
|
3152
|
+
{"tfoot", 5, 1, 1},
|
|
3153
|
+
{"th", 2, 1, 1},
|
|
3154
|
+
{"thead", 5, 1, 1},
|
|
3155
|
+
{"title", 5, 1, 1},
|
|
3156
|
+
{"tr", 2, 1, 1},
|
|
3157
|
+
{"track", 5, 1, 0},
|
|
3158
|
+
{"ul", 2, 1, 1},
|
|
3159
|
+
{"wbr", 3, 1, 0},
|
|
3160
|
+
{"xmp", 3, 1, 0},
|
|
3161
|
+
};
|
|
3162
|
+
|
|
3163
|
+
/* bucket[first_char] = start index; bucket[first_char+1] = end */
|
|
3164
|
+
static const uint8_t h_tag_bucket[27] = {
|
|
3165
|
+
0, 5, 12, 16, 23, 24, 31, 31, 42,
|
|
3166
|
+
45, 45, 46, 49, 53, 57, 59, 63, 63,
|
|
3167
|
+
63, 70, 81, 82, 82, 83, 84, 84, 84,
|
|
3168
|
+
};
|
|
3169
|
+
|
|
3170
|
+
/* Resolve a (lowercased) tag name's flags; NULL when unknown. */
|
|
3171
|
+
static const HTagInfo* h_tag_lookup(const char* n) {
|
|
3172
|
+
if (!n) return NULL;
|
|
3173
|
+
unsigned char c = (unsigned char)n[0];
|
|
3174
|
+
if (c < 'a' || c > 'z') return NULL;
|
|
3175
|
+
for (int i = h_tag_bucket[c - 'a'];
|
|
3176
|
+
i < h_tag_bucket[c - 'a' + 1]; i++) {
|
|
3177
|
+
const HTagInfo* t = &h_tag_infos[i];
|
|
3178
|
+
if ((uint8_t)n[t->len] == 0 && t->name[t->len - 1] == n[t->len - 1] &&
|
|
3179
|
+
memcmp(n, t->name, t->len) == 0)
|
|
3180
|
+
return t;
|
|
3181
|
+
}
|
|
3182
|
+
return NULL;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3057
3185
|
static int h_is_special_ww(const char* n) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
"base", "basefont", "bgsound", "blockquote", "body",
|
|
3061
|
-
"br", "button", "caption", "center", "col",
|
|
3062
|
-
"colgroup", "dd", "details", "dialog", "dir",
|
|
3063
|
-
"div",
|
|
3064
|
-
"dl", "dt", "embed", "fieldset", "figcaption",
|
|
3065
|
-
"figure", "footer", "form", "frame", "frameset",
|
|
3066
|
-
"h1", "h2", "h3", "h4", "h5",
|
|
3067
|
-
"h6", "head", "header", "hgroup", "hr",
|
|
3068
|
-
"html", "iframe", "img", "input", "keygen",
|
|
3069
|
-
"li", "link", "listing", "main", "marquee",
|
|
3070
|
-
"menu", "meta", "nav", "noembed", "noframes",
|
|
3071
|
-
"noscript", "object", "ol", "p", "param",
|
|
3072
|
-
"plaintext", "pre", "script", "search", "section",
|
|
3073
|
-
"select", "source", "style", "summary", "table",
|
|
3074
|
-
"tbody", "td", "template", "textarea", "tfoot",
|
|
3075
|
-
"th", "thead", "title", "tr", "track",
|
|
3076
|
-
"ul", "wbr", "xmp", NULL};
|
|
3077
|
-
if (!n) return 0;
|
|
3078
|
-
for (int i = 0; k[i]; i++)
|
|
3079
|
-
if (strcmp(n, k[i]) == 0) return 1;
|
|
3080
|
-
return 0;
|
|
3186
|
+
const HTagInfo* t = h_tag_lookup(n);
|
|
3187
|
+
return t && t->special;
|
|
3081
3188
|
}
|
|
3082
3189
|
|
|
3083
3190
|
#define H_NS_HTML 0
|
|
@@ -3249,22 +3356,8 @@ static int h_input_type_hidden(const char* q, const char* end) {
|
|
|
3249
3356
|
* head set, the block-level set (they close p instead), the table
|
|
3250
3357
|
* family, and raw-text elements. */
|
|
3251
3358
|
static int h_reconstructs(const char* n) {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
"bgsound", "link", "meta", "script", "style", "template",
|
|
3255
|
-
"title", "noframes", "address", "article", "aside",
|
|
3256
|
-
"blockquote", "center", "details", "dialog", "dir",
|
|
3257
|
-
"div", "dl", "fieldset", "figcaption", "figure",
|
|
3258
|
-
"footer", "header", "hgroup", "main", "menu", "nav",
|
|
3259
|
-
"ol", "p", "search", "section", "summary", "ul",
|
|
3260
|
-
"h1", "h2", "h3", "h4", "h5", "h6",
|
|
3261
|
-
"pre", "listing", "form", "li", "dd", "dt",
|
|
3262
|
-
"plaintext", "textarea", "table", "hr", "caption", "col",
|
|
3263
|
-
"colgroup", "frame", "tbody", "tfoot", "thead", "td",
|
|
3264
|
-
"th", "tr", NULL};
|
|
3265
|
-
for (int i = 0; no[i]; i++)
|
|
3266
|
-
if (strcmp(n, no[i]) == 0) return 0;
|
|
3267
|
-
return 1;
|
|
3359
|
+
const HTagInfo* t = h_tag_lookup(n);
|
|
3360
|
+
return !(t && t->no_fmt);
|
|
3268
3361
|
}
|
|
3269
3362
|
|
|
3270
3363
|
/* ---- #659 foreign content (WHATWG 12.2.6.5) ---- */
|
|
@@ -3658,6 +3751,24 @@ static void h_insert_before(HBuilder* b, LeptrisElement parent,
|
|
|
3658
3751
|
parent->child_count++;
|
|
3659
3752
|
}
|
|
3660
3753
|
|
|
3754
|
+
/* O(1) append for open-stack parents (#1225): route through the
|
|
3755
|
+
* builder's per-slot tail. Non-open parents fall back to the
|
|
3756
|
+
* shared internal (whose tail cache + walk still apply). The hint
|
|
3757
|
+
* is validated inside leptris_element_append_child_tail, so any
|
|
3758
|
+
* stale slot is silently ignored — correctness never depends on
|
|
3759
|
+
* the bookkeeping. */
|
|
3760
|
+
static void hb_put(HBuilder* b, LeptrisElement parent, LeptrisNodeRef n) {
|
|
3761
|
+
for (size_t i = b->depth; i-- > 0;) {
|
|
3762
|
+
if (b->open[i] == parent) {
|
|
3763
|
+
leptris_element_append_child_tail(parent, n, b->doc,
|
|
3764
|
+
b->open_tail[i]);
|
|
3765
|
+
b->open_tail[i] = n;
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
leptris_element_append_child_internal_doc(parent, n, b->doc);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3661
3772
|
static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
3662
3773
|
/* "in frameset" (13.2.6.4.18): non-whitespace text is IGNORED
|
|
3663
3774
|
* with a frameset open ANYWHERE on top; whitespace inserts
|
|
@@ -3932,7 +4043,7 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
3932
4043
|
return;
|
|
3933
4044
|
}
|
|
3934
4045
|
}
|
|
3935
|
-
|
|
4046
|
+
hb_put(b, top, n);
|
|
3936
4047
|
} else {
|
|
3937
4048
|
h_top_append(b, n);
|
|
3938
4049
|
}
|
|
@@ -3999,6 +4110,7 @@ static LeptrisElement h_open_foreign(HBuilder* b, const char* name,
|
|
|
3999
4110
|
if (b->depth == 0 && !b->root) b->root = e;
|
|
4000
4111
|
if (b->depth < 256) {
|
|
4001
4112
|
b->open[b->depth] = e;
|
|
4113
|
+
b->open_tail[b->depth] = NULL;
|
|
4002
4114
|
b->open_ns[b->depth] = (uint8_t)ns;
|
|
4003
4115
|
b->depth++;
|
|
4004
4116
|
}
|
|
@@ -4036,6 +4148,7 @@ static LeptrisElement h_open_element(HBuilder* b, const char* name) {
|
|
|
4036
4148
|
if (b->depth == 0 && !b->root) b->root = e;
|
|
4037
4149
|
if (b->depth < 256) {
|
|
4038
4150
|
b->open[b->depth] = e;
|
|
4151
|
+
b->open_tail[b->depth] = NULL;
|
|
4039
4152
|
b->open_ns[b->depth] = H_NS_HTML;
|
|
4040
4153
|
b->depth++;
|
|
4041
4154
|
}
|
|
@@ -4236,6 +4349,7 @@ static LeptrisElement h_afe_open_clone(HBuilder* b, LeptrisElement src) {
|
|
|
4236
4349
|
if (b->depth == 0 && !b->root) b->root = c;
|
|
4237
4350
|
if (b->depth < 256) {
|
|
4238
4351
|
b->open[b->depth] = c;
|
|
4352
|
+
b->open_tail[b->depth] = NULL;
|
|
4239
4353
|
b->open_ns[b->depth] = H_NS_HTML;
|
|
4240
4354
|
b->depth++;
|
|
4241
4355
|
}
|
|
@@ -4518,6 +4632,7 @@ static LeptrisElement h_open_named(HBuilder* b, const char* name,
|
|
|
4518
4632
|
if (b->depth == 0 && !b->root) b->root = e;
|
|
4519
4633
|
if (push && b->depth < 256) {
|
|
4520
4634
|
b->open[b->depth] = e;
|
|
4635
|
+
b->open_tail[b->depth] = NULL;
|
|
4521
4636
|
b->open_ns[b->depth] = H_NS_HTML;
|
|
4522
4637
|
b->depth++;
|
|
4523
4638
|
}
|
|
@@ -4532,8 +4647,7 @@ static LeptrisElement h_new_child(HBuilder* b, LeptrisElement parent,
|
|
|
4532
4647
|
LeptrisElement e = leptris_element_create_with_view(nv, b->pool);
|
|
4533
4648
|
if (!e) return NULL;
|
|
4534
4649
|
leptris_root_doc_register(e, b->doc);
|
|
4535
|
-
|
|
4536
|
-
b->doc);
|
|
4650
|
+
hb_put(b, parent, (LeptrisNodeRef)e);
|
|
4537
4651
|
return e;
|
|
4538
4652
|
}
|
|
4539
4653
|
|
|
@@ -836,6 +836,13 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
|
|
|
836
836
|
/* The pool will be destroyed below, freeing all elements */
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
/* Parse diagnostics (#1200): the recover-channel list. */
|
|
840
|
+
{
|
|
841
|
+
extern void leptris_diag_free(struct LeptrisDiag**, int*, int*);
|
|
842
|
+
leptris_diag_free(&doc->parse_diags, &doc->parse_diag_count,
|
|
843
|
+
&doc->parse_diag_cap);
|
|
844
|
+
}
|
|
845
|
+
|
|
839
846
|
/* Free document fields */
|
|
840
847
|
if (doc->encoding) {
|
|
841
848
|
LEPTRIS_FREE(doc->encoding);
|
|
@@ -921,9 +928,11 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
|
|
|
921
928
|
leptris_root_doc_unregister((LeptrisElement)doc->new_dom_root);
|
|
922
929
|
}
|
|
923
930
|
/* #1038: fallback-path registrations (detached elements) die
|
|
924
|
-
* with the doc too — see leptris_root_doc_unregister_doc.
|
|
931
|
+
* with the doc too — see leptris_root_doc_unregister_doc. The
|
|
932
|
+
* declaration must match root_doc_map.c's size_t return or the
|
|
933
|
+
* amalgamated single-TU build fails (#1217). */
|
|
925
934
|
{
|
|
926
|
-
extern
|
|
935
|
+
extern size_t leptris_root_doc_unregister_doc(
|
|
927
936
|
struct leptris_document*);
|
|
928
937
|
leptris_root_doc_unregister_doc(doc);
|
|
929
938
|
}
|
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
#ifndef LEPTRIS_INTERNAL_H
|
|
10
10
|
#define LEPTRIS_INTERNAL_H
|
|
11
11
|
|
|
12
|
+
/* Internal entry points must not leak into the shared-library ABI.
|
|
13
|
+
* Windows already hides them (no __declspec(dllexport)) and macOS
|
|
14
|
+
* build flags hide non-exported globals, but plain ELF keeps
|
|
15
|
+
* default visibility — leptris_set_error escaped into the Linux
|
|
16
|
+
* dynamic table while being absent from the PE exports, which no
|
|
17
|
+
* cross-platform FFI mirror can attach against (#1197 release
|
|
18
|
+
* cascade). */
|
|
19
|
+
#if defined(_WIN32) || !(defined(__GNUC__) || defined(__clang__))
|
|
20
|
+
#define LEPTRIS_LOCAL
|
|
21
|
+
#else
|
|
22
|
+
#define LEPTRIS_LOCAL __attribute__((visibility("hidden")))
|
|
23
|
+
#endif
|
|
24
|
+
|
|
12
25
|
#include <stddef.h>
|
|
13
26
|
#include <stdint.h>
|
|
14
27
|
|
|
@@ -104,6 +117,8 @@ struct leptris_mut_attr_block {
|
|
|
104
117
|
char bytes[];
|
|
105
118
|
};
|
|
106
119
|
|
|
120
|
+
struct LeptrisDiag;
|
|
121
|
+
|
|
107
122
|
struct leptris_document {
|
|
108
123
|
struct leptris_element* root; /* Root element (legacy API) */
|
|
109
124
|
/* Snapshot of this document's last failing parse message
|
|
@@ -111,6 +126,13 @@ struct leptris_document {
|
|
|
111
126
|
* thread-local channel). */
|
|
112
127
|
char last_error_message[256];
|
|
113
128
|
|
|
129
|
+
/* Recover-class parse diagnostics (#1200): the DOM parse's
|
|
130
|
+
* parity surface with the SAX lane's recover errors. Pool-free
|
|
131
|
+
* list (leptris_diag_* owns it); released in document_free. */
|
|
132
|
+
struct LeptrisDiag* parse_diags;
|
|
133
|
+
int parse_diag_count;
|
|
134
|
+
int parse_diag_cap;
|
|
135
|
+
|
|
114
136
|
/* EXSLT-style extension pack enabled via leptris_exslt_enable
|
|
115
137
|
* (TODO.concurrency/06). */
|
|
116
138
|
int exslt_enabled;
|
|
@@ -883,7 +905,7 @@ static inline int leptris_strcmp(const char* s1, const char* s2) {
|
|
|
883
905
|
* ============================================================================ */
|
|
884
906
|
|
|
885
907
|
/* Set error with basic message */
|
|
886
|
-
void leptris_set_error(leptris_error_code code, const char* message);
|
|
908
|
+
LEPTRIS_LOCAL void leptris_set_error(leptris_error_code code, const char* message);
|
|
887
909
|
|
|
888
910
|
/* Set error with line/column position */
|
|
889
911
|
void leptris_set_parse_error_position(int line, int column);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* by the slice, not the document. All event strings are owned by the
|
|
9
9
|
* puller and remain valid until the NEXT leptris_pull_next call. */
|
|
10
10
|
#include "../../include/leptris/sax/sax.h"
|
|
11
|
+
#include "sax_internal.h"
|
|
11
12
|
#include <stdio.h>
|
|
12
13
|
#include <stdlib.h>
|
|
13
14
|
#include <string.h>
|
|
@@ -22,6 +23,11 @@ typedef struct pull_event {
|
|
|
22
23
|
/* START_ELEMENT attributes: owned flat copies [n1,v1,n2,v2...] */
|
|
23
24
|
char** attrs;
|
|
24
25
|
size_t attr_count;
|
|
26
|
+
/* One-shot buffer pulls (TODO.max-perf/2-3 slice 1): strings
|
|
27
|
+
* borrow the sax parser's stable storage (input buffer + scratch,
|
|
28
|
+
* alive until parser free) — queue_reset_event frees only the
|
|
29
|
+
* attrs array itself. */
|
|
30
|
+
int borrowed;
|
|
25
31
|
} pull_event;
|
|
26
32
|
|
|
27
33
|
struct leptris_pull_parser {
|
|
@@ -57,9 +63,14 @@ struct leptris_pull_parser {
|
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
static void queue_reset_event(pull_event* e) {
|
|
60
|
-
|
|
66
|
+
if (!e->borrowed) {
|
|
67
|
+
free(e->name);
|
|
68
|
+
free(e->text);
|
|
69
|
+
}
|
|
61
70
|
if (e->attrs) {
|
|
62
|
-
|
|
71
|
+
if (!e->borrowed)
|
|
72
|
+
for (size_t i = 0; i < e->attr_count * 2; i++)
|
|
73
|
+
free(e->attrs[i]);
|
|
63
74
|
free(e->attrs);
|
|
64
75
|
}
|
|
65
76
|
memset(e, 0, sizeof(*e));
|
|
@@ -73,6 +84,18 @@ static char* pull_strdup_n(const char* s, size_t n) {
|
|
|
73
84
|
return c;
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
/* Event strings: borrow in one-shot mode (stable until parser free),
|
|
88
|
+
* strdup otherwise (streaming file mode — the source chunk dies at
|
|
89
|
+
* the next feed). */
|
|
90
|
+
static char* ev_string(pull_event* e, const struct leptris_pull_parser* p,
|
|
91
|
+
const char* s, size_t n) {
|
|
92
|
+
if (p->sax->one_shot) {
|
|
93
|
+
e->borrowed = 1;
|
|
94
|
+
return (char*)(s ? s : "");
|
|
95
|
+
}
|
|
96
|
+
return pull_strdup_n(s ? s : "", n);
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
static pull_event* queue_push(struct leptris_pull_parser* p) {
|
|
77
100
|
size_t used = (p->tail + p->cap - p->head) % p->cap;
|
|
78
101
|
if (used + 1 == p->cap) { /* full: grow */
|
|
@@ -104,16 +127,25 @@ static void cb_start_element(void* ud, const char* name, const char** attrs) {
|
|
|
104
127
|
pull_event* e = queue_push(p);
|
|
105
128
|
if (!e) return;
|
|
106
129
|
e->type = LEPTRIS_PULL_START_ELEMENT;
|
|
107
|
-
e->name =
|
|
130
|
+
e->name = ev_string(e, p, name, strlen(name));
|
|
108
131
|
if (attrs) {
|
|
109
132
|
size_t n = 0;
|
|
110
133
|
while (attrs[n]) n += 2; /* flat [name, value, ..., NULL] */
|
|
111
134
|
e->attr_count = n / 2;
|
|
112
135
|
e->attrs = (char**)calloc(n + 1, sizeof(char*));
|
|
113
136
|
if (e->attrs) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
if (p->sax->one_shot) {
|
|
138
|
+
/* Names/values are frame/scratch-owned (parser
|
|
139
|
+
* lifetime); the array itself stays queue-owned. */
|
|
140
|
+
e->borrowed = 1;
|
|
141
|
+
for (size_t i = 0; i < n; i++)
|
|
142
|
+
e->attrs[i] = (char*)(attrs[i] ? attrs[i] : "");
|
|
143
|
+
} else {
|
|
144
|
+
for (size_t i = 0; i < n; i++)
|
|
145
|
+
e->attrs[i] = pull_strdup_n(
|
|
146
|
+
attrs[i] ? attrs[i] : "",
|
|
147
|
+
attrs[i] ? strlen(attrs[i]) : 0);
|
|
148
|
+
}
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
}
|
|
@@ -123,7 +155,7 @@ static void cb_end_element(void* ud, const char* name) {
|
|
|
123
155
|
pull_event* e = queue_push(p);
|
|
124
156
|
if (!e) return;
|
|
125
157
|
e->type = LEPTRIS_PULL_END_ELEMENT;
|
|
126
|
-
e->name =
|
|
158
|
+
e->name = ev_string(e, p, name, strlen(name));
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
static void cb_characters(void* ud, const char* text, size_t len) {
|
|
@@ -131,7 +163,10 @@ static void cb_characters(void* ud, const char* text, size_t len) {
|
|
|
131
163
|
pull_event* e = queue_push(p);
|
|
132
164
|
if (!e) return;
|
|
133
165
|
e->type = LEPTRIS_PULL_TEXT;
|
|
134
|
-
|
|
166
|
+
/* Text spans are raw input-buffer slices — no NUL terminator in
|
|
167
|
+
* stable storage, so they always copy even in one-shot mode
|
|
168
|
+
* (the public event contract is NUL-terminated strings). */
|
|
169
|
+
e->text = pull_strdup_n(text ? text : "", len);
|
|
135
170
|
e->text_len = len;
|
|
136
171
|
}
|
|
137
172
|
|
|
@@ -140,7 +175,7 @@ static void cb_comment(void* ud, const char* comment) {
|
|
|
140
175
|
pull_event* e = queue_push(p);
|
|
141
176
|
if (!e) return;
|
|
142
177
|
e->type = LEPTRIS_PULL_COMMENT;
|
|
143
|
-
e->text =
|
|
178
|
+
e->text = ev_string(e, p, comment, strlen(comment));
|
|
144
179
|
e->text_len = strlen(comment);
|
|
145
180
|
}
|
|
146
181
|
|
|
@@ -149,7 +184,7 @@ static void cb_cdata(void* ud, const char* cdata) {
|
|
|
149
184
|
pull_event* e = queue_push(p);
|
|
150
185
|
if (!e) return;
|
|
151
186
|
e->type = LEPTRIS_PULL_CDATA;
|
|
152
|
-
e->text =
|
|
187
|
+
e->text = ev_string(e, p, cdata, strlen(cdata));
|
|
153
188
|
e->text_len = strlen(cdata);
|
|
154
189
|
}
|
|
155
190
|
|
|
@@ -158,9 +193,9 @@ static void cb_pi(void* ud, const char* target, const char* data) {
|
|
|
158
193
|
pull_event* e = queue_push(p);
|
|
159
194
|
if (!e) return;
|
|
160
195
|
e->type = LEPTRIS_PULL_PI;
|
|
161
|
-
e->name =
|
|
196
|
+
e->name = ev_string(e, p, target, strlen(target));
|
|
162
197
|
if (data) {
|
|
163
|
-
e->text =
|
|
198
|
+
e->text = ev_string(e, p, data, strlen(data));
|
|
164
199
|
e->text_len = strlen(data);
|
|
165
200
|
}
|
|
166
201
|
}
|
|
@@ -177,9 +212,9 @@ static void cb_start_prefix(void* ud, const char* prefix, const char* uri) {
|
|
|
177
212
|
pull_event* e = queue_push(p);
|
|
178
213
|
if (!e) return;
|
|
179
214
|
e->type = LEPTRIS_PULL_START_PREFIX;
|
|
180
|
-
e->name =
|
|
181
|
-
|
|
182
|
-
e->text =
|
|
215
|
+
e->name = ev_string(e, p, prefix ? prefix : "",
|
|
216
|
+
prefix ? strlen(prefix) : 0);
|
|
217
|
+
e->text = ev_string(e, p, uri ? uri : "", uri ? strlen(uri) : 0);
|
|
183
218
|
e->text_len = uri ? strlen(uri) : 0;
|
|
184
219
|
}
|
|
185
220
|
|
|
@@ -188,8 +223,8 @@ static void cb_end_prefix(void* ud, const char* prefix) {
|
|
|
188
223
|
pull_event* e = queue_push(p);
|
|
189
224
|
if (!e) return;
|
|
190
225
|
e->type = LEPTRIS_PULL_END_PREFIX;
|
|
191
|
-
e->name =
|
|
192
|
-
|
|
226
|
+
e->name = ev_string(e, p, prefix ? prefix : "",
|
|
227
|
+
prefix ? strlen(prefix) : 0);
|
|
193
228
|
}
|
|
194
229
|
|
|
195
230
|
static void cb_error(void* ud, const char* message, int line, int column) {
|
|
@@ -240,6 +275,10 @@ LEPTRIS_API LeptrisPullParser leptris_pull_new(const char* xml, size_t len) {
|
|
|
240
275
|
if (!p) return NULL;
|
|
241
276
|
p->input = xml;
|
|
242
277
|
p->len = len;
|
|
278
|
+
/* Memory source: single-feed parse — enable the one-shot fast
|
|
279
|
+
* paths (TODO.max-perf/2-3 slice 1). File mode keeps the
|
|
280
|
+
* chunked-streaming paths. */
|
|
281
|
+
leptris_sax_parser_set_one_shot(p->sax, 1);
|
|
243
282
|
return p;
|
|
244
283
|
}
|
|
245
284
|
|
|
@@ -279,8 +318,13 @@ LEPTRIS_API const LeptrisPullEvent* leptris_pull_next(LeptrisPullParser pull) {
|
|
|
279
318
|
chunk_ptr = pull->file_buf;
|
|
280
319
|
is_final = (chunk < PULL_SLICE);
|
|
281
320
|
} else {
|
|
321
|
+
/* In-memory source: the whole buffer is caller-owned and
|
|
322
|
+
* resident, so slicing buys nothing — ONE feed makes the
|
|
323
|
+
* parse one-shot (no token ever straddles a chunk
|
|
324
|
+
* boundary), the precondition for zero-copy slice
|
|
325
|
+
* emission (TODO.max-perf/2-3, slice 1). File mode keeps
|
|
326
|
+
* the bounded-memory slicing. */
|
|
282
327
|
chunk = pull->len - pull->pos;
|
|
283
|
-
if (chunk > PULL_SLICE) chunk = PULL_SLICE;
|
|
284
328
|
chunk_ptr = pull->input + pull->pos;
|
|
285
329
|
pull->pos += chunk;
|
|
286
330
|
is_final = pull->pos == pull->len;
|
|
@@ -154,6 +154,13 @@ struct LeptrisSAXParser {
|
|
|
154
154
|
* state machine. */
|
|
155
155
|
int streaming;
|
|
156
156
|
|
|
157
|
+
/* One-shot parse (TODO.max-perf/2-3 slice 1): the whole input
|
|
158
|
+
* arrives in a single feed and stays alive for the parser's
|
|
159
|
+
* lifetime (pull buffer mode). Tokens can never straddle a
|
|
160
|
+
* chunk boundary, so name scans run in place with one direct
|
|
161
|
+
* copy into the frame instead of carry+scratch+frame. */
|
|
162
|
+
int one_shot;
|
|
163
|
+
|
|
157
164
|
/* Emitted start_document once we've committed to parsing --
|
|
158
165
|
* used to avoid emitting twice across feed() calls. */
|
|
159
166
|
int start_emitted;
|