leptris 1.9.273.1 → 1.9.277.0
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/Rakefile +1 -1
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +76 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +51 -7
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +23 -0
- data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +38 -6
- data/vendor-src/libleptris/src/leptris/xslt/xslt_internal.h +4 -0
- data/vendor-src/libleptris/src/leptris/xslt/xslt_parse.c +11 -2
- data/vendor-src/libleptris/src/leptris/xslt/xslt_public.c +62 -0
- data/vendor-src/libleptris/test/xpath/test_xslt.cpp +61 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ed9c7b8afac75281839aedc850f425345d336f7893313c46a4b98fbda74118e
|
|
4
|
+
data.tar.gz: 939f5e15e2d829b2126ad81702d2d1cd09943b60047878722ad4f80992db416c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 550f5d8637884ccef52b572c2b2bb2ecbd7aaec569b30017bd0efb89960b013766d351157d2b562cb4ceb027fdc873e6d973286ca6ddc8e5f0ea5b601e886686
|
|
7
|
+
data.tar.gz: 673a248b9d8947656d9aac9316b7eddc08b11d05e1db2f36d2e71ea77a72d92c241e5f71485cd5941394e061a5a76a933dcb77796758248f9ab73a02d54dcede
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.277"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.277] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `element_copy`'s direct attribute placement keeps the `attr_count` increment that
|
|
8
|
+
`leptris_element_add_attribute` maintained — the count accessor and the O(1) attribute-index
|
|
9
|
+
fast path read that field, and a 2-attribute copy read as 0 (caught by the ASAN leg's
|
|
10
|
+
deep-copy suite; leptris#682, leptris#1450).
|
|
11
|
+
|
|
12
|
+
### Performance
|
|
13
|
+
|
|
14
|
+
- `element_copy` places unprefixed attribute nodes directly instead of routing pooled copies
|
|
15
|
+
through the mutation API, which re-derived what the source struct already carries (a second
|
|
16
|
+
string copy, the FNV hash recompute, the entity scan). The source's lazy-hash sentinel and
|
|
17
|
+
entity flag carry over verbatim. 100-book subtree copy: 0.050 -> 0.033 ms — ~2.7–2.9x faster
|
|
18
|
+
than lxml's deepcopy of the same fixture (leptris#682).
|
|
19
|
+
- Zero-argument `position()` and `last()` read the evaluation context directly instead of the
|
|
20
|
+
function-registry round trip (name lookup, argument array, extension fallback chain).
|
|
21
|
+
Positional predicates re-evaluate these per candidate, so the by-name lookup dominated
|
|
22
|
+
per-eval cost there. Values mirror the registry handlers exactly; core functions cannot be
|
|
23
|
+
shadowed; arity errors keep the registry path (leptris#682).
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## [1.9.276] - 2026-09-30
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- leptris_xslt_apply materializes streamed output into the returned document (xslt)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## [1.9.275] - 2026-09-30
|
|
36
|
+
|
|
37
|
+
### Performance
|
|
38
|
+
|
|
39
|
+
- `leptris_element_copy` routes the source views' lengths instead of re-deriving them: the
|
|
40
|
+
deep-copy path paid four strlens per attribute (two `leptris_pool_strdup` copies plus two
|
|
41
|
+
`leptris_sv_from_cstr` reconstructions) and one per element name before copying everything
|
|
42
|
+
again — a profile put `strlen` at 38% of a 100-book subtree copy. Length-based pooled
|
|
43
|
+
copies (which also intern recurring attribute names) take the same fixture from
|
|
44
|
+
0.099 to 0.050 ms per copy (~2x; ~1.9x faster than lxml's deepcopy). Behavior is
|
|
45
|
+
byte-identical; the #696/#721/#213 copy-semantics suites pin the output (leptris#682,
|
|
46
|
+
leptris#1445).
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## [1.9.274] - 2026-09-30
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- The XSLT streaming gate now also rejects sheets that declare a non-default `xsl:output`
|
|
55
|
+
encoding — output-encoding conversion is a serializer feature the stream emitters don't
|
|
56
|
+
carry, so those sheets stay on the result-tree path in every build configuration
|
|
57
|
+
(leptris#1443, bug-169).
|
|
58
|
+
- Stream text emission escapes literal CR as ` `, matching the attribute emitter and the
|
|
59
|
+
result-tree serializer (bug-105).
|
|
60
|
+
- A dynamic HTML output method (result rooted at an unprefixed `<html>`) now bails the
|
|
61
|
+
streaming path and restarts the transform on the result-tree path instead of emitting
|
|
62
|
+
XML-method self-closing tags (bug-28-).
|
|
63
|
+
|
|
64
|
+
### Performance
|
|
65
|
+
|
|
66
|
+
- XSLT streaming output is now active for every plain stylesheet. The eligibility gate
|
|
67
|
+
required an explicit `xsl:mode on-no-match` value, but sheets without one default to the
|
|
68
|
+
same legacy text built-ins as the admitted `text-only-copy` — the engine was dark for all
|
|
69
|
+
plain sheets while the result-tree path paid ~3x (text-node allocation, serialize-back,
|
|
70
|
+
document teardown). Measured vs in-process libxslt 1.1.43 (best-of-9, arm64):
|
|
71
|
+
value-of ×50000 17.24 → 1.07 ms (9.8x faster), dispatch 2000 books 2.99 → 1.55 ms
|
|
72
|
+
(2.7x), dispatch-heavy 120 templates 5.64 → 1.75 ms (2.0x), predicate dispatch
|
|
73
|
+
4.66 → 2.00 ms (26.7x), transform //book[@price>100] ~157 µs vs 423.6 µs (2.7x),
|
|
74
|
+
100-book subtree copy 0.099 → 0.060 ms (1.6x). No row remains behind libxslt
|
|
75
|
+
(leptris#682, leptris#1443).
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
3
79
|
## [1.9.273] - 2026-09-29
|
|
4
80
|
|
|
5
81
|
### Fixed
|
|
@@ -2058,27 +2058,71 @@ static LeptrisElement copy_subtree_detached(LeptrisElement source,
|
|
|
2058
2058
|
char* name_copy = leptris_sv_to_cstr_pooled(&name_view, pool);
|
|
2059
2059
|
if (!name_copy) return NULL;
|
|
2060
2060
|
LeptrisElement copy = leptris_element_create_with_view(
|
|
2061
|
-
|
|
2061
|
+
leptris_sv_from_ptr(name_copy, name_view.length), pool);
|
|
2062
2062
|
if (!copy) return NULL;
|
|
2063
2063
|
leptris_elem_set_parent(copy, parent_copy);
|
|
2064
2064
|
|
|
2065
2065
|
if (copy_element_namespaces_pooled(copy, source, pool))
|
|
2066
2066
|
doc->has_namespaces = 1;
|
|
2067
2067
|
|
|
2068
|
+
struct leptris_attribute* last_attr = NULL;
|
|
2068
2069
|
for (struct leptris_attribute* sa =
|
|
2069
2070
|
leptris_element_get_first_attribute(source);
|
|
2070
2071
|
sa; sa = leptris_attr_next(sa)) {
|
|
2071
2072
|
if (leptris_sv_is_empty(&sa->name_view)) continue;
|
|
2072
|
-
|
|
2073
|
+
/* Prefixed attrs carry the TODO-173 side cache (prefix +
|
|
2074
|
+
* namespace_uri) — rare; keep the full mutation-API path. */
|
|
2075
|
+
if (attr_get_ns_cache(sa)) {
|
|
2076
|
+
char* n = leptris_sv_to_cstr_pooled(&sa->name_view, pool);
|
|
2077
|
+
if (!n) continue;
|
|
2078
|
+
LeptrisStringView sav_ = leptris_attr_value_sv(sa);
|
|
2079
|
+
char* v = leptris_sv_is_empty(&sav_)
|
|
2080
|
+
? NULL
|
|
2081
|
+
: leptris_sv_to_cstr_pooled(&sav_, pool);
|
|
2082
|
+
LeptrisStringView nv = leptris_sv_from_ptr(
|
|
2083
|
+
n, sa->name_view.length);
|
|
2084
|
+
LeptrisStringView vv =
|
|
2085
|
+
v ? leptris_sv_from_ptr(v, sav_.length)
|
|
2086
|
+
: leptris_sv_from_cstr("");
|
|
2087
|
+
leptris_element_add_attribute(copy, nv, vv, pool);
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
/* Direct placement: the source attr is already decoded and
|
|
2091
|
+
* hashed — add_attribute's mutation-API work (second string
|
|
2092
|
+
* copy, hash recompute, entity scan) re-derives what the
|
|
2093
|
+
* source struct already carries. Materialize the views into
|
|
2094
|
+
* the destination pool (source views are source-document
|
|
2095
|
+
* lifetime), carry the lazy-hash sentinel and the entity
|
|
2096
|
+
* flag bit verbatim, and wire the chain the way the parser
|
|
2097
|
+
* does. */
|
|
2098
|
+
char* n = leptris_sv_to_cstr_pooled(&sa->name_view, pool);
|
|
2073
2099
|
if (!n) continue;
|
|
2074
2100
|
LeptrisStringView sav_ = leptris_attr_value_sv(sa);
|
|
2075
2101
|
char* v = leptris_sv_is_empty(&sav_)
|
|
2076
2102
|
? NULL
|
|
2077
|
-
:
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2103
|
+
: leptris_sv_to_cstr_pooled(&sav_, pool);
|
|
2104
|
+
struct leptris_attribute* attr =
|
|
2105
|
+
(struct leptris_attribute*)leptris_pool_alloc(
|
|
2106
|
+
pool, sizeof(struct leptris_attribute));
|
|
2107
|
+
if (!attr) continue;
|
|
2108
|
+
attr->name_view = leptris_sv_from_ptr(n, sa->name_view.length);
|
|
2109
|
+
leptris_attr_value_set_heap(
|
|
2110
|
+
attr, v ? leptris_sv_from_ptr(v, sav_.length)
|
|
2111
|
+
: leptris_sv_from_cstr(""));
|
|
2112
|
+
attr->name_hash = sa->name_hash;
|
|
2113
|
+
attr->ns_cache_off = 0;
|
|
2114
|
+
attr->next_cp = 0;
|
|
2115
|
+
if (copy->first_attribute_off != 0) {
|
|
2116
|
+
leptris_attr_set_next(last_attr, attr);
|
|
2117
|
+
} else {
|
|
2118
|
+
copy->first_attribute_off =
|
|
2119
|
+
(int32_t)((char*)attr - (char*)copy);
|
|
2120
|
+
}
|
|
2121
|
+
last_attr = attr;
|
|
2122
|
+
/* add_attribute's increment — attr_count is what the count
|
|
2123
|
+
* accessor and the O(1) index fast path read (uint8 wrap
|
|
2124
|
+
* at 255 latches the walk, same convention). */
|
|
2125
|
+
copy->attr_count++;
|
|
2082
2126
|
}
|
|
2083
2127
|
|
|
2084
2128
|
LeptrisNodeRef first = NULL;
|
|
@@ -846,6 +846,29 @@ static struct leptris_xpath_result* evaluate_function_call_impl(XPathContext* ct
|
|
|
846
846
|
return NULL;
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
+
/* Zero-arg context builtins (the per-eval hot pair):
|
|
850
|
+
* position() and last() read the eval context directly — no
|
|
851
|
+
* registry lookup, no argument array, no extension fallback
|
|
852
|
+
* chain. Positional predicates re-evaluate these for every
|
|
853
|
+
* candidate, so the by-name lookup dominated per-eval cost
|
|
854
|
+
* there. Values mirror the registry handlers exactly; core
|
|
855
|
+
* functions cannot be shadowed, so the registry entry and
|
|
856
|
+
* this path can never disagree. Arity errors stay on the
|
|
857
|
+
* registry path (child_count != 0 falls through). */
|
|
858
|
+
if (ast->child_count == 0) {
|
|
859
|
+
if (strcmp(func_name, "position") == 0 ||
|
|
860
|
+
strcmp(func_name, "last") == 0) {
|
|
861
|
+
struct leptris_xpath_result* r =
|
|
862
|
+
xpath_result_new(XPATH_RESULT_NUMBER);
|
|
863
|
+
if (r) {
|
|
864
|
+
r->value.number_value = (double)
|
|
865
|
+
(func_name[0] == 'p' ? ctx->context_position
|
|
866
|
+
: ctx->context_size);
|
|
867
|
+
}
|
|
868
|
+
return r;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
849
872
|
/* Get function registry */
|
|
850
873
|
if (!ctx->function_registry) {
|
|
851
874
|
snprintf(ctx->error_msg, sizeof(ctx->error_msg),
|
|
@@ -374,11 +374,12 @@ static void xslt_append_fragment_node(XsltExec* ex, LeptrisNodeRef n);
|
|
|
374
374
|
static void stream_emit_text(struct SerializeBuffer* b, const char* t) {
|
|
375
375
|
if (!t) return;
|
|
376
376
|
for (const char* p = t; *p; ) {
|
|
377
|
-
size_t run = strcspn(p, "
|
|
377
|
+
size_t run = strcspn(p, "&<>\r");
|
|
378
378
|
if (run) buffer_append_len(b, p, run);
|
|
379
379
|
if (!p[run]) break;
|
|
380
380
|
buffer_append(b, p[run] == '&' ? "&"
|
|
381
|
-
: p[run] == '<' ? "<"
|
|
381
|
+
: p[run] == '<' ? "<"
|
|
382
|
+
: p[run] == '>' ? ">" : " ");
|
|
382
383
|
p += run + 1;
|
|
383
384
|
}
|
|
384
385
|
}
|
|
@@ -856,6 +857,25 @@ static int op_result_elem(XsltExec* ex, const XsltInstr* in,
|
|
|
856
857
|
LeptrisElement parent = ex->pending_parent;
|
|
857
858
|
const char* out_name = apply_ns_alias(ex->sheet, in->name);
|
|
858
859
|
if (ex->streaming) {
|
|
860
|
+
/* Dynamic output-method parity (bug-28-): the tree path's
|
|
861
|
+
* serializer switches to the HTML method when the result
|
|
862
|
+
* root is an unprefixed <html> (effective_html_method's
|
|
863
|
+
* trigger); the stream emitters cover only the XML method.
|
|
864
|
+
* Instead of duplicating the html rules here, bail — the
|
|
865
|
+
* driver restarts the transform on the result-tree path. An
|
|
866
|
+
* explicit xsl:output method never bails (out_method_set
|
|
867
|
+
* disables the dynamic switch). */
|
|
868
|
+
if (!parent && !ex->sheet->out_method_set && out_name &&
|
|
869
|
+
!strchr(out_name, ':') &&
|
|
870
|
+
(!in->ns_uri || !in->ns_uri[0]) &&
|
|
871
|
+
strlen(out_name) == 4 &&
|
|
872
|
+
tolower((unsigned char)out_name[0]) == 'h' &&
|
|
873
|
+
tolower((unsigned char)out_name[1]) == 't' &&
|
|
874
|
+
tolower((unsigned char)out_name[2]) == 'm' &&
|
|
875
|
+
tolower((unsigned char)out_name[3]) == 'l') {
|
|
876
|
+
ex->stream_bail_html = 1;
|
|
877
|
+
return 1;
|
|
878
|
+
}
|
|
859
879
|
/* Gate guarantees: no namespaces, no attr-sets, no
|
|
860
880
|
* xsl:attribute children — the open tag is complete before
|
|
861
881
|
* the children run, so it emits whole and the empty case
|
|
@@ -4816,9 +4836,10 @@ void xslt_exec_free(XsltExec* ex) {
|
|
|
4816
4836
|
free(ex);
|
|
4817
4837
|
}
|
|
4818
4838
|
|
|
4819
|
-
XsltExec*
|
|
4820
|
-
|
|
4821
|
-
|
|
4839
|
+
static XsltExec* transform_doc_ex(const XsltStylesheet* sheet,
|
|
4840
|
+
LeptrisDocument sheet_doc,
|
|
4841
|
+
LeptrisDocument source,
|
|
4842
|
+
int allow_stream) {
|
|
4822
4843
|
if (!sheet || !source) return NULL;
|
|
4823
4844
|
register_ops();
|
|
4824
4845
|
|
|
@@ -4831,7 +4852,7 @@ XsltExec* xslt_transform_doc(const XsltStylesheet* sheet,
|
|
|
4831
4852
|
ex->current_size = 1; /* last() default context size */
|
|
4832
4853
|
ex->result = leptris_document_create();
|
|
4833
4854
|
if (!ex->result) { xslt_exec_free(ex); return NULL; }
|
|
4834
|
-
if (sheet->can_stream) {
|
|
4855
|
+
if (allow_stream && sheet->can_stream) {
|
|
4835
4856
|
ex->sbuf = buffer_create(0);
|
|
4836
4857
|
if (ex->sbuf) ex->streaming = 1;
|
|
4837
4858
|
}
|
|
@@ -4915,3 +4936,14 @@ XsltExec* xslt_transform_doc(const XsltStylesheet* sheet,
|
|
|
4915
4936
|
return ex;
|
|
4916
4937
|
}
|
|
4917
4938
|
|
|
4939
|
+
XsltExec* xslt_transform_doc(const XsltStylesheet* sheet,
|
|
4940
|
+
LeptrisDocument sheet_doc,
|
|
4941
|
+
LeptrisDocument source) {
|
|
4942
|
+
XsltExec* ex = transform_doc_ex(sheet, sheet_doc, source, 1);
|
|
4943
|
+
if (ex && ex->streaming && ex->stream_bail_html) {
|
|
4944
|
+
xslt_exec_free(ex);
|
|
4945
|
+
ex = transform_doc_ex(sheet, sheet_doc, source, 0);
|
|
4946
|
+
}
|
|
4947
|
+
return ex;
|
|
4948
|
+
}
|
|
4949
|
+
|
|
@@ -634,6 +634,10 @@ typedef struct xslt_exec {
|
|
|
634
634
|
* PI + control flow; everything else keeps the result tree). */
|
|
635
635
|
struct SerializeBuffer* sbuf;
|
|
636
636
|
int streaming;
|
|
637
|
+
/* Dynamic html-method bail (bug-28-): set when the stream path
|
|
638
|
+
* hits a fragment-level <html> root and the output method is
|
|
639
|
+
* dynamic — the driver restarts on the result-tree path. */
|
|
640
|
+
int stream_bail_html;
|
|
637
641
|
int cache_owner_set; int cache_owner_prev;
|
|
638
642
|
|
|
639
643
|
/* current(): the node being processed by the template rule or
|
|
@@ -2401,12 +2401,21 @@ XsltStylesheet* xslt_stylesheet_parse_root(LeptrisDocument doc,
|
|
|
2401
2401
|
* template (and the globals) must be stream-safe; any tree-
|
|
2402
2402
|
* building or post-hoc op keeps the sheet on the result-tree
|
|
2403
2403
|
* path. Conservative by construction — unknown kinds reject. */
|
|
2404
|
-
|
|
2404
|
+
/* Output ENCODING conversion (bug-169: xsl:output
|
|
2405
|
+
* encoding='iso-8859-1') is a serializer feature the stream
|
|
2406
|
+
* emitters don't carry — only the default (UTF-8) streams. */
|
|
2407
|
+
sheet->can_stream = !sheet->out_encoding &&
|
|
2408
|
+
!sheet->out_method_text && !sheet->out_method_html &&
|
|
2405
2409
|
!stream_indent_on(sheet) && sheet->out_cdata_count == 0 &&
|
|
2406
2410
|
sheet->charmap_count == 0 && sheet->out_standalone < 0 &&
|
|
2407
2411
|
!sheet->out_doctype_system && !sheet->out_doctype_public &&
|
|
2408
2412
|
sheet->version_major < 3 &&
|
|
2409
|
-
|
|
2413
|
+
/* 0 (unspecified — no xsl:mode element) executes the SAME
|
|
2414
|
+
* legacy text built-ins as 6 for 1.0/2.0 sheets (the >=3
|
|
2415
|
+
* remap to shallow-copy is excluded by version_major < 3
|
|
2416
|
+
* above); admitting it is what lets plain sheets stream. */
|
|
2417
|
+
(sheet->mode_on_no_match == 0 || sheet->mode_on_no_match == 1 ||
|
|
2418
|
+
sheet->mode_on_no_match == 6) &&
|
|
2410
2419
|
stream_instrs_ok(sheet->globals);
|
|
2411
2420
|
for (size_t i = 0; sheet->can_stream && i < sheet->mode_count; i++) {
|
|
2412
2421
|
for (size_t t = 0; sheet->can_stream && t < sheet->modes[i].n; t++) {
|
|
@@ -114,6 +114,9 @@ LEPTRIS_API void leptris_xslt_free(LeptrisXslt xslt) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
static int effective_html_method(const XsltStylesheet* sheet,
|
|
118
|
+
LeptrisElement peek);
|
|
119
|
+
|
|
117
120
|
LEPTRIS_API LeptrisDocument leptris_xslt_apply(LeptrisXslt xslt,
|
|
118
121
|
LeptrisDocument source) {
|
|
119
122
|
if (!xslt || !source) return NULL;
|
|
@@ -121,6 +124,65 @@ LEPTRIS_API LeptrisDocument leptris_xslt_apply(LeptrisXslt xslt,
|
|
|
121
124
|
xslt->sheet_doc, source);
|
|
122
125
|
if (!ex) return NULL;
|
|
123
126
|
if (ex->eval_error) { xslt_exec_free(ex); return NULL; }
|
|
127
|
+
/* #682 stream mode: the result bytes live in the exec buffer,
|
|
128
|
+
* not the result tree. The document-result face materializes
|
|
129
|
+
* them by FRAGMENT-parsing the buffer and lifting the children
|
|
130
|
+
* to document level — the exact pre-streaming result shape (a
|
|
131
|
+
* single root for element output; TOP-LEVEL TEXT NODES for
|
|
132
|
+
* value-of-only sheets, which no whole-document parse can
|
|
133
|
+
* represent). Empty streamed output keeps the empty result
|
|
134
|
+
* document; a fragment that fails to parse reports the
|
|
135
|
+
* transform failure. */
|
|
136
|
+
if (ex->streaming && ex->sbuf && ex->sbuf->size > 0) {
|
|
137
|
+
LeptrisDocument parsed = leptris_document_create();
|
|
138
|
+
if (!parsed) { xslt_exec_free(ex); return NULL; }
|
|
139
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
140
|
+
LeptrisElement synth = leptris_parse_fragment(
|
|
141
|
+
ex->sbuf->data, ex->sbuf->size, parsed, &st);
|
|
142
|
+
if (!synth) {
|
|
143
|
+
leptris_document_free(parsed);
|
|
144
|
+
xslt_exec_free(ex);
|
|
145
|
+
return NULL;
|
|
146
|
+
}
|
|
147
|
+
struct leptris_document* pd = (struct leptris_document*)parsed;
|
|
148
|
+
struct leptris_element* sy = (struct leptris_element*)synth;
|
|
149
|
+
/* Detach the synthetic's child run in one write, then chain
|
|
150
|
+
* it into the document children — the tree path's own
|
|
151
|
+
* wiring shape (xslt_exec's fragment doc does exactly
|
|
152
|
+
* this). The first ELEMENT child takes the root slots and
|
|
153
|
+
* the root-doc map registration; bare-text runs keep the
|
|
154
|
+
* pre-streaming top-level-text shape. */
|
|
155
|
+
LeptrisNodeRef run =
|
|
156
|
+
(LeptrisNodeRef)leptris_element_first_child_any(sy);
|
|
157
|
+
sy->first_child_off = 0;
|
|
158
|
+
int stamped = 0;
|
|
159
|
+
for (LeptrisNodeRef n = run; n; ) {
|
|
160
|
+
LeptrisNodeRef nx = leptris_node_next_sibling(n);
|
|
161
|
+
leptris_node_set_next_sibling(n, NULL);
|
|
162
|
+
if (!pd->doc_children_head) {
|
|
163
|
+
pd->doc_children_head = n;
|
|
164
|
+
pd->doc_children_tail = n;
|
|
165
|
+
} else {
|
|
166
|
+
leptris_node_set_next_sibling(
|
|
167
|
+
(LeptrisNodeRef)pd->doc_children_tail, n);
|
|
168
|
+
pd->doc_children_tail = n;
|
|
169
|
+
}
|
|
170
|
+
if (!stamped &&
|
|
171
|
+
leptris_node_get_type(n) == LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
172
|
+
pd->root = (LeptrisElement)n;
|
|
173
|
+
pd->new_dom_root = (LeptrisElement)n;
|
|
174
|
+
stamped = 1;
|
|
175
|
+
}
|
|
176
|
+
n = nx;
|
|
177
|
+
}
|
|
178
|
+
if (stamped) {
|
|
179
|
+
extern void leptris_root_doc_register(
|
|
180
|
+
LeptrisElement, struct leptris_document*);
|
|
181
|
+
leptris_root_doc_register(pd->root, pd);
|
|
182
|
+
}
|
|
183
|
+
xslt_exec_free(ex);
|
|
184
|
+
return parsed;
|
|
185
|
+
}
|
|
124
186
|
LeptrisDocument out = ex->result;
|
|
125
187
|
ex->result = NULL; /* ownership moved */
|
|
126
188
|
xslt_exec_free(ex);
|
|
@@ -31,6 +31,32 @@ std::string run(const char* sheet_body, const char* xml) {
|
|
|
31
31
|
return r;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/* The DOCUMENT-result face (leptris_xslt_apply): #682 streaming
|
|
35
|
+
* sheets emit into the exec buffer — the face must materialize the
|
|
36
|
+
* streamed markup as the returned document (root present), not
|
|
37
|
+
* hand back the empty result tree. */
|
|
38
|
+
std::string run_apply_doc(const char* sheet_body, const char* xml) {
|
|
39
|
+
std::string sheet = std::string("<xsl:stylesheet ") + KXSL +
|
|
40
|
+
" version='1.0'>" + sheet_body +
|
|
41
|
+
"</xsl:stylesheet>";
|
|
42
|
+
LeptrisXslt x = leptris_xslt_parse(sheet.c_str(), sheet.size());
|
|
43
|
+
if (!x) return "(compile-failed)";
|
|
44
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), nullptr);
|
|
45
|
+
if (!d) { leptris_xslt_free(x); return "(parse-failed)"; }
|
|
46
|
+
LeptrisDocument out = leptris_xslt_apply(x, d);
|
|
47
|
+
std::string r = "(null)";
|
|
48
|
+
if (out) {
|
|
49
|
+
LeptrisElement root = leptris_document_root(out);
|
|
50
|
+
r = root ? leptris_element_name(root) : "(no-root)";
|
|
51
|
+
char* xout = leptris_document_serialize(out, 0);
|
|
52
|
+
if (xout) { r += "|"; r += xout; leptris_free_string(xout); }
|
|
53
|
+
leptris_document_free(out);
|
|
54
|
+
}
|
|
55
|
+
leptris_document_free(d);
|
|
56
|
+
leptris_xslt_free(x);
|
|
57
|
+
return r;
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
/* Same, pinned to version='3.0' — XSLT 3.0 semantics (sequence
|
|
35
61
|
* display forms, versioned value-of, expand-text). */
|
|
36
62
|
std::string run30(const char* sheet_body, const char* xml) {
|
|
@@ -3011,6 +3037,26 @@ TEST(Xslt30, TryCatchErrorVariants) {
|
|
|
3011
3037
|
"(compile-failed)");
|
|
3012
3038
|
}
|
|
3013
3039
|
|
|
3040
|
+
/* #682 streaming gate: an UNSPECIFIED on-no-match (no xsl:mode
|
|
3041
|
+
* element — every plain 1.0/2.0 sheet) executes the same legacy
|
|
3042
|
+
* text built-ins as the explicit 'text-only-copy' value; both
|
|
3043
|
+
* output shapes must stay byte-identical so the stream gate can
|
|
3044
|
+
* admit the default alongside the explicit value (builtin_no_match:
|
|
3045
|
+
* v==6 falls to the same legacy walk). */
|
|
3046
|
+
TEST(Xslt30, UnspecifiedNoMatchMatchesTextOnlyCopy) {
|
|
3047
|
+
const char* body30 =
|
|
3048
|
+
"<xsl:template match='g/@n'><hit/></xsl:template>"
|
|
3049
|
+
"<xsl:template match='/'><out>"
|
|
3050
|
+
"<xsl:apply-templates select='*'/></out></xsl:template>";
|
|
3051
|
+
const char* doc = "<r><g n='1'><i>x</i></g>tail</r>";
|
|
3052
|
+
std::string unspecified = body(
|
|
3053
|
+
run(body30, doc));
|
|
3054
|
+
std::string textonly = body(run(
|
|
3055
|
+
(std::string("<xsl:mode on-no-match='text-only-copy'/>") +
|
|
3056
|
+
body30).c_str(), doc));
|
|
3057
|
+
EXPECT_EQ(unspecified, textonly);
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3014
3060
|
/* xsl:mode on-no-match (3.0 §6.7) — Saxon-HE 12.7 ground truth on
|
|
3015
3061
|
* <r><g n="1"><i>x</i></g>tail</r> with match="g/@n" -> <hit/>.
|
|
3016
3062
|
* The 3.0 DEFAULT is shallow-copy (copy the node, process its
|
|
@@ -4243,3 +4289,18 @@ TEST(XsltFull, DispatchIndexOverflowFiresAllTemplates) {
|
|
|
4243
4289
|
EXPECT_EQ(out, want);
|
|
4244
4290
|
}
|
|
4245
4291
|
}
|
|
4292
|
+
|
|
4293
|
+
|
|
4294
|
+
/* #682 follow-up: the document face materializes streamed output.
|
|
4295
|
+
* Plain value-of sheet (stream-eligible since the mode_on_no_match
|
|
4296
|
+
* gate admits 0). */
|
|
4297
|
+
TEST(XsltStreamApplyDoc, DocumentFaceMaterializesStreamedOutput) {
|
|
4298
|
+
std::string r = run_apply_doc(
|
|
4299
|
+
"<xsl:template match='/'><summary>"
|
|
4300
|
+
"<xsl:value-of select='//title'/></summary>"
|
|
4301
|
+
"</xsl:template>",
|
|
4302
|
+
"<doc><title>hello</title><item/><item/></doc>");
|
|
4303
|
+
EXPECT_EQ(r.find("(no-root)"), std::string::npos);
|
|
4304
|
+
EXPECT_NE(r.find("summary"), std::string::npos);
|
|
4305
|
+
EXPECT_NE(r.find("hello"), std::string::npos);
|
|
4306
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.277",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|