leptris 1.9.231.1 → 1.9.232.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/CHANGELOG.md +19 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +35 -4
- 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/lib/leptris/xml/ffi.rb +4 -0
- data/lib/leptris/xml/node.rb +28 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +15 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +5 -1
- data/vendor-src/libleptris/src/include/leptris.h +17 -0
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +26 -6
- data/vendor-src/libleptris/src/leptris/dom/node.c +24 -7
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +22 -8
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +29 -8
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +30 -1
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +106 -6
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +68 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +56 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/codepoints-to-string.xml +808 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/deep-equal.xml +2709 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/distinct-values.xml +1223 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/index-of.xml +493 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/insert-before.xml +399 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/remove.xml +480 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/reverse.xml +653 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/string-to-codepoints.xml +452 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/subsequence.xml +1041 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +179 -1
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +27 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5716284785d7f3ff534b89dbacab287b7479ffe7ab66609db4c0df0cdab2ad0
|
|
4
|
+
data.tar.gz: fa2e4f4a8ab6e5e2aed150b6ee246c63a5711853c72c33179012db27d6122a4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2737c46a2462a96a9f98e17106fc7ca921d9f5718273b5a2a6abd424e6351feff2a3af1ac716ee5b54dda923cd68f9b8adc2f044935cc6838b24204474b727b
|
|
7
|
+
data.tar.gz: c3e1340928ec767dc669960b09e83c74f000b559c6a90607e5ec9a5316cc25077d6e7564c90fea50d4900180ce0fb6d1ceb33fc916753e252c7d452e17aaca64
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.232.0] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `Node#visit_entering` (libleptris #1332): the entering-only walk
|
|
13
|
+
— one C call yields `(node, true, depth)` exactly once per node,
|
|
14
|
+
halving the C→Ruby transitions against `#visit` for walks that
|
|
15
|
+
never acted on the leaving half. Same order and depths as
|
|
16
|
+
`#visit`; the native face falls back to the filtering visit on
|
|
17
|
+
pins predating #1332.
|
|
18
|
+
|
|
19
|
+
### Performance — engine sync (1.9.232)
|
|
20
|
+
|
|
21
|
+
- Vendored libleptris 1.9.232: the per-eval registry leak fix, the
|
|
22
|
+
range NaN fix, string-literal character-reference expansion, and
|
|
23
|
+
the plan-walker prefixed-name fix — moxml/lutaml hydration of
|
|
24
|
+
`w:*` elements resolves correctly with no binding-side change —
|
|
25
|
+
plus 907 new QT3-gated engine behaviors.
|
|
26
|
+
|
|
8
27
|
## [1.9.231.0] - 2026-09-23
|
|
9
28
|
|
|
10
29
|
### Fixed
|
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.232"
|
|
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
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -971,10 +971,39 @@ static VALUE nf_visit_binding(VALUE self, VALUE document, VALUE addr)
|
|
|
971
971
|
st.klass_memo_flag =
|
|
972
972
|
rb_obj_class(document) == c_iteration_scope ? Qfalse : Qtrue;
|
|
973
973
|
st.for_visit = 1;
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
974
|
+
/* ALWAYS the two-event engine call: this seam backs the PUBLIC
|
|
975
|
+
* Node#visit, whose contract (specs since libleptris 1.9.20)
|
|
976
|
+
* is (node, entering, depth) enter/leave PAIRS. Entering-only
|
|
977
|
+
* walks belong to visit_entering_binding below — swapping the
|
|
978
|
+
* call here silently dropped every leaving callback and broke
|
|
979
|
+
* the contract. */
|
|
980
|
+
f_node_visit(st.self_ptr, visit_cb, &st);
|
|
981
|
+
if (st.err != Qnil)
|
|
982
|
+
rb_exc_raise(st.err);
|
|
983
|
+
return Qnil;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/* Entering-only walk (libleptris #1332): one callback per node.
|
|
987
|
+
* Yields the same (node, true, depth) shape as visit_binding — the
|
|
988
|
+
* entering argument is constant true here, but keeping the 3-arity
|
|
989
|
+
* yield lets a block be shared between the two walk modes. */
|
|
990
|
+
static VALUE nf_visit_entering_binding(VALUE self, VALUE document,
|
|
991
|
+
VALUE addr)
|
|
992
|
+
{
|
|
993
|
+
struct trav_state st;
|
|
994
|
+
|
|
995
|
+
(void)self;
|
|
996
|
+
if (!f_node_visit_entering)
|
|
997
|
+
return nf_visit_binding(self, document, addr);
|
|
998
|
+
resolve_binding_classes();
|
|
999
|
+
st.document = document;
|
|
1000
|
+
st.err = Qnil;
|
|
1001
|
+
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
1002
|
+
st.cache = binding_cache_of(document);
|
|
1003
|
+
st.klass_memo_flag =
|
|
1004
|
+
rb_obj_class(document) == c_iteration_scope ? Qfalse : Qtrue;
|
|
1005
|
+
st.for_visit = 1;
|
|
1006
|
+
f_node_visit_entering(st.self_ptr, visit_cb, &st);
|
|
978
1007
|
if (st.err != Qnil)
|
|
979
1008
|
rb_exc_raise(st.err);
|
|
980
1009
|
return Qnil;
|
|
@@ -2739,6 +2768,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2739
2768
|
nf_traverse_binding, 2);
|
|
2740
2769
|
rb_define_module_function(m_native, "visit_binding",
|
|
2741
2770
|
nf_visit_binding, 2);
|
|
2771
|
+
rb_define_module_function(m_native, "visit_entering_binding",
|
|
2772
|
+
nf_visit_entering_binding, 2);
|
|
2742
2773
|
rb_define_module_function(m_native, "fast_text_content",
|
|
2743
2774
|
nf_fast_text_content, 1);
|
|
2744
2775
|
rb_define_module_function(m_native, "fast_comment_content",
|
|
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/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -447,6 +447,10 @@ attach_function :leptris_parse_string,
|
|
|
447
447
|
# node walks the document child chain.
|
|
448
448
|
attach_function :leptris_node_visit,
|
|
449
449
|
[:leptris_node_ref, :pointer, :pointer], :void
|
|
450
|
+
# Entering-only walk (libleptris #1332): one callback per
|
|
451
|
+
# node. Rides the engine pin that ships it.
|
|
452
|
+
attach_function :leptris_node_visit_entering,
|
|
453
|
+
[:leptris_node_ref, :pointer, :pointer], :void
|
|
450
454
|
# Document-level PIs (v1.6.0): not tree nodes — enumerate via
|
|
451
455
|
# these accessors only.
|
|
452
456
|
attach_function :leptris_document_pi_count,
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -545,6 +545,34 @@ class Leptris::XML::Node
|
|
|
545
545
|
self
|
|
546
546
|
end
|
|
547
547
|
|
|
548
|
+
# Visits the subtree with ONE C call, yielding ONCE per node
|
|
549
|
+
# (leptris_node_visit_entering, libleptris #1332): every node —
|
|
550
|
+
# elements included — yields (node, true, depth) exactly once, on
|
|
551
|
+
# entering. Halves the C-callback invocations against #visit for
|
|
552
|
+
# walks that never acted on the leaving half (the family-walk
|
|
553
|
+
# shape); the halving happens in the engine, not in a per-node
|
|
554
|
+
# Ruby filter. Same order and depths as #visit; read-only.
|
|
555
|
+
#
|
|
556
|
+
# root.visit_entering { |node, entering, depth| ... }
|
|
557
|
+
def visit_entering(&block)
|
|
558
|
+
return enum_for(:visit_entering) unless block
|
|
559
|
+
ensure_alive!
|
|
560
|
+
if @addr_reads_fast
|
|
561
|
+
Leptris::XML::Native.visit_entering_binding(@document,
|
|
562
|
+
@c_address, &block)
|
|
563
|
+
return self
|
|
564
|
+
end
|
|
565
|
+
document = @document
|
|
566
|
+
visitor = ::FFI::Function.new(
|
|
567
|
+
:void, [:pointer, :pointer, :int, :int], blocking: true) do |_, node_ptr, entering, depth|
|
|
568
|
+
block.call(
|
|
569
|
+
Leptris::XML::Node.wrap(node_ptr, document),
|
|
570
|
+
entering == 1, depth)
|
|
571
|
+
end
|
|
572
|
+
Leptris::XML::FFI.leptris_node_visit_entering(c_ptr, visitor, nil)
|
|
573
|
+
self
|
|
574
|
+
end
|
|
575
|
+
|
|
548
576
|
# Walks the subtree in post-order DFS (matches Nokogiri's
|
|
549
577
|
# semantics): the receiver, its descendants, nothing else.
|
|
550
578
|
#
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.232] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- QT3 corpus slice 4 — sequence-edit + codepoint families, 501 gated cases; seq member fidelity fixes (xquery)
|
|
8
|
+
- entering-only visit mode — leptris_node_visit_entering (#1332) (dom)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- range A to B with an empty operand yields the empty sequence (xpath)
|
|
13
|
+
- expand character and predefined entity references in string literals (xpath)
|
|
14
|
+
- prefixed wire names bind namespaced elements (XSD-002 fallout) (descriptor)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## [1.9.231] - 2026-09-23
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
@@ -98,7 +98,11 @@ typedef struct {
|
|
|
98
98
|
} leptris_attr_plan;
|
|
99
99
|
|
|
100
100
|
typedef struct {
|
|
101
|
-
const char* wire_name; /* XML element name as it appears on the wire
|
|
101
|
+
const char* wire_name; /* XML element name as it appears on the wire:
|
|
102
|
+
* the prefixed form ("w:b") binds that literal
|
|
103
|
+
* prefix+local pair; the bare local form ("b")
|
|
104
|
+
* binds by local name and lets ns_form do the
|
|
105
|
+
* namespace work */
|
|
102
106
|
uint8_t kind; /* LeptrisPlanKind */
|
|
103
107
|
uint8_t type_tag; /* host-defined; echoed back verbatim */
|
|
104
108
|
int32_t child_plan_index; /* NESTED: index into leptris_plan_spec.plans;
|
|
@@ -1195,6 +1195,23 @@ LEPTRIS_API void leptris_node_visit(LeptrisNodeRef root,
|
|
|
1195
1195
|
LeptrisNodeVisitor visitor,
|
|
1196
1196
|
void* user_data);
|
|
1197
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* Entering-only subtree visitation (issue #1332)
|
|
1200
|
+
*
|
|
1201
|
+
* Same walk as leptris_node_visit — document order, same depths —
|
|
1202
|
+
* but the callback fires ONCE per node, on entering. Elements are
|
|
1203
|
+
* not re-visited after their subtree. Halves the C-callback
|
|
1204
|
+
* invocations for consumers that only act on entering=1 (the lazy
|
|
1205
|
+
* family-walk shape).
|
|
1206
|
+
*
|
|
1207
|
+
* @param root Subtree root (any node kind, or a document node)
|
|
1208
|
+
* @param visitor Callback per node (may be NULL — no-op)
|
|
1209
|
+
* @param user_data Opaque context for the callback
|
|
1210
|
+
*/
|
|
1211
|
+
LEPTRIS_API void leptris_node_visit_entering(LeptrisNodeRef root,
|
|
1212
|
+
LeptrisNodeVisitor visitor,
|
|
1213
|
+
void* user_data);
|
|
1214
|
+
|
|
1198
1215
|
/**
|
|
1199
1216
|
* Count the document's top-level processing instructions
|
|
1200
1217
|
*
|
|
@@ -440,6 +440,29 @@ LEPTRIS_API void leptris_plan_free(LeptrisPlan plan) { dp_plan_free(plan); }
|
|
|
440
440
|
/* ---- walk ------------------------------------------------------- */
|
|
441
441
|
|
|
442
442
|
/* Namespace binding for a child element against a row's target form. */
|
|
443
|
+
/* Child-row wire-name match. The ABI documents wire_name as "the
|
|
444
|
+
* XML element name as it appears on the wire": a row may carry the
|
|
445
|
+
* PREFIXED form ("w:b") or the bare local name ("b"). Rows with a
|
|
446
|
+
* colon bind prefix+local (literal prefix, per the wire contract);
|
|
447
|
+
* bare rows keep the historical local-name match with the row's
|
|
448
|
+
* ns_form doing the namespace work. The old matcher compared the
|
|
449
|
+
* local name only, so every prefixed row — the shape hosts
|
|
450
|
+
* compiling from xsd:QName schemas emit — silently bound nothing
|
|
451
|
+
* and whole subtrees hydrated empty (XSD-002 fallout). */
|
|
452
|
+
static int dp_wire_name_matches(LeptrisElement child,
|
|
453
|
+
const char* wire_name) {
|
|
454
|
+
const char* local = NULL;
|
|
455
|
+
const char* prefix = NULL;
|
|
456
|
+
leptris_element_expanded_name(child, &local, &prefix, NULL);
|
|
457
|
+
if (!wire_name || !local) return 0;
|
|
458
|
+
const char* colon = strchr(wire_name, ':');
|
|
459
|
+
if (!colon) return strcmp(local, wire_name) == 0;
|
|
460
|
+
size_t plen = (size_t)(colon - wire_name);
|
|
461
|
+
return prefix != NULL && strlen(prefix) == plen &&
|
|
462
|
+
strncmp(prefix, wire_name, plen) == 0 &&
|
|
463
|
+
strcmp(local, colon + 1) == 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
443
466
|
static int dp_ns_binds(LeptrisElement elem, uint8_t ns_form,
|
|
444
467
|
const char* ns_uri, int lenient) {
|
|
445
468
|
const char* local = NULL;
|
|
@@ -617,9 +640,8 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
617
640
|
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
618
641
|
continue;
|
|
619
642
|
LeptrisElement child = (LeptrisElement)n;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if (!local || strcmp(local, row->wire_name) != 0) continue;
|
|
643
|
+
if (!dp_wire_name_matches(child, row->wire_name))
|
|
644
|
+
continue;
|
|
623
645
|
if (!dp_ns_binds(child, target->ns_form, target->ns_uri,
|
|
624
646
|
lenient))
|
|
625
647
|
continue;
|
|
@@ -666,9 +688,7 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
666
688
|
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
667
689
|
continue;
|
|
668
690
|
LeptrisElement child = (LeptrisElement)n;
|
|
669
|
-
|
|
670
|
-
leptris_element_expanded_name(child, &local, NULL, NULL);
|
|
671
|
-
if (!local || strcmp(local, row->wire_name) != 0) continue;
|
|
691
|
+
if (!dp_wire_name_matches(child, row->wire_name)) continue;
|
|
672
692
|
/* #1272: same-wire-name claim — if a previous row bound
|
|
673
693
|
* this node, skip it. */
|
|
674
694
|
int rank = -1;
|
|
@@ -389,18 +389,20 @@ void leptris_node_set_next_sibling(LeptrisNode* node, LeptrisNode* sibling) {
|
|
|
389
389
|
* lazily from the callback instead of materializing per-level
|
|
390
390
|
* NodeSet/Array allocations. */
|
|
391
391
|
static void node_visit_rec(LeptrisNode* n, int depth,
|
|
392
|
-
LeptrisNodeVisitor visitor, void* user_data
|
|
392
|
+
LeptrisNodeVisitor visitor, void* user_data,
|
|
393
|
+
int entering_only) {
|
|
393
394
|
int is_elem = (n->type == LEPTRIS_NODE_TYPE_ELEMENT);
|
|
394
395
|
visitor(user_data, (LeptrisNodeRef)n, 1, depth);
|
|
395
396
|
if (!is_elem) return;
|
|
396
397
|
for (LeptrisNode* c = leptris_elem_first_child((LeptrisElement)n); c;
|
|
397
398
|
c = leptris_node_get_next_sibling(c))
|
|
398
|
-
node_visit_rec(c, depth + 1, visitor, user_data);
|
|
399
|
-
|
|
399
|
+
node_visit_rec(c, depth + 1, visitor, user_data, entering_only);
|
|
400
|
+
if (!entering_only)
|
|
401
|
+
visitor(user_data, (LeptrisNodeRef)n, 0, depth);
|
|
400
402
|
}
|
|
401
403
|
|
|
402
|
-
void
|
|
403
|
-
|
|
404
|
+
static void node_visit_impl(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
405
|
+
void* user_data, int entering_only) {
|
|
404
406
|
if (!root || !visitor) return;
|
|
405
407
|
if (root->type == LEPTRIS_NODE_TYPE_DOCUMENT) {
|
|
406
408
|
/* Document root: children = the document child chain (#580),
|
|
@@ -413,8 +415,23 @@ void leptris_node_visit(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
|
413
415
|
if (!start) start = (LeptrisNode*)d->root;
|
|
414
416
|
for (LeptrisNode* c = start; c;
|
|
415
417
|
c = leptris_node_get_next_sibling(c))
|
|
416
|
-
node_visit_rec(c, 0, visitor, user_data);
|
|
418
|
+
node_visit_rec(c, 0, visitor, user_data, entering_only);
|
|
417
419
|
return;
|
|
418
420
|
}
|
|
419
|
-
node_visit_rec((LeptrisNode*)root, 0, visitor, user_data
|
|
421
|
+
node_visit_rec((LeptrisNode*)root, 0, visitor, user_data,
|
|
422
|
+
entering_only);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
void leptris_node_visit(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
426
|
+
void* user_data) {
|
|
427
|
+
node_visit_impl(root, visitor, user_data, 0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Issue #1332: entering-only mode — one callback per node. The
|
|
431
|
+
* family-walk consumers all discard the leaving half of
|
|
432
|
+
* leptris_node_visit; this skips those invocations at the source. */
|
|
433
|
+
void leptris_node_visit_entering(LeptrisNodeRef root,
|
|
434
|
+
LeptrisNodeVisitor visitor,
|
|
435
|
+
void* user_data) {
|
|
436
|
+
node_visit_impl(root, visitor, user_data, 1);
|
|
420
437
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <string.h>
|
|
13
13
|
#include <stdio.h>
|
|
14
14
|
#include <stdlib.h>
|
|
15
|
+
#include <limits.h>
|
|
15
16
|
/* ---- ISO 8601 dayTimeDuration value model (op:duration batch,
|
|
16
17
|
* lever 6 stage-2). Durations ride the STRING lexicon; these
|
|
17
18
|
* helpers give arithmetic and comparisons a typed view. Strictly
|
|
@@ -2037,8 +2038,8 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2037
2038
|
struct leptris_xpath_result* hi_r =
|
|
2038
2039
|
evaluate_expr(ctx, ast->children[1]);
|
|
2039
2040
|
if (!hi_r) { xpath_result_free(lo_r); return NULL; }
|
|
2040
|
-
|
|
2041
|
-
|
|
2041
|
+
double lod = xpath_to_number(lo_r);
|
|
2042
|
+
double hid = xpath_to_number(hi_r);
|
|
2042
2043
|
xpath_result_free(lo_r);
|
|
2043
2044
|
xpath_result_free(hi_r);
|
|
2044
2045
|
|
|
@@ -2046,12 +2047,25 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2046
2047
|
if (!out) return NULL;
|
|
2047
2048
|
out->owns_synthetic_text = 1;
|
|
2048
2049
|
out->is_sequence = 1;
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2050
|
+
/* XPath 2.0+: an empty (NaN) operand yields the empty
|
|
2051
|
+
* sequence — never cast NaN to long (UB: arm64 gives 0,
|
|
2052
|
+
* x86 gives INT32/64_MIN, so the "range" silently became
|
|
2053
|
+
* a 75/100000-item sequence; cbcl-codepoints-to-string-020).
|
|
2054
|
+
* Huge finite values clamp instead of UB-casting. */
|
|
2055
|
+
if (!isnan(lod) && !isnan(hid)) {
|
|
2056
|
+
long lo = lod > 9.0e18 ? LONG_MAX
|
|
2057
|
+
: lod < -9.0e18 ? LONG_MIN
|
|
2058
|
+
: (long)lod;
|
|
2059
|
+
long hi = hid > 9.0e18 ? LONG_MAX
|
|
2060
|
+
: hid < -9.0e18 ? LONG_MIN
|
|
2061
|
+
: (long)hid;
|
|
2062
|
+
for (long v = lo; v <= hi && v - lo < 100000; v++) {
|
|
2063
|
+
char buf[28];
|
|
2064
|
+
int l = snprintf(buf, sizeof buf, "\x03N%ld", v);
|
|
2065
|
+
XPathTextNode* tn = synth_text(buf, (size_t)l);
|
|
2066
|
+
if (!tn) break;
|
|
2067
|
+
xpath_nodeset_add(out, tn);
|
|
2068
|
+
}
|
|
2055
2069
|
}
|
|
2056
2070
|
struct leptris_xpath_result* result =
|
|
2057
2071
|
xpath_result_new(XPATH_RESULT_NODESET);
|
|
@@ -763,7 +763,12 @@ static int de_item_equal(DeItem* a, DeItem* b) {
|
|
|
763
763
|
if (a->kind != b->kind) return 0;
|
|
764
764
|
switch (a->kind) {
|
|
765
765
|
case 0: return de_node_equal(a->node, b->node);
|
|
766
|
-
case 1:
|
|
766
|
+
case 1: {
|
|
767
|
+
/* deep-equal is atomic-eq based: NaN compares equal to
|
|
768
|
+
* NaN (F&O 17.4.1 — unlike the eq operator). */
|
|
769
|
+
if (isnan(a->num) && isnan(b->num)) return 1;
|
|
770
|
+
return a->num == b->num;
|
|
771
|
+
}
|
|
767
772
|
case 2: {
|
|
768
773
|
const char* as = a->str ? a->str : (a->borrow ? a->borrow : "");
|
|
769
774
|
const char* bs = b->str ? b->str : (b->borrow ? b->borrow : "");
|
|
@@ -843,17 +848,33 @@ static struct leptris_xpath_result* xpath_func_deep_equal(XPathContext* context,
|
|
|
843
848
|
return NULL;
|
|
844
849
|
}
|
|
845
850
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
size_t
|
|
851
|
+
/* Growable item arrays: corpus sequences run to tens of
|
|
852
|
+
* thousands of members (cbcl roundtrips over 65536 to 100000);
|
|
853
|
+
* a fixed cap silently compares a prefix. */
|
|
854
|
+
size_t capa = a->type == XPATH_RESULT_NODESET && a->value.nodeset_value
|
|
855
|
+
? a->value.nodeset_value->count : 1;
|
|
856
|
+
size_t capb = b->type == XPATH_RESULT_NODESET && b->value.nodeset_value
|
|
857
|
+
? b->value.nodeset_value->count : 1;
|
|
858
|
+
DeItem* ia = (DeItem*)malloc((capa ? capa : 1) * sizeof(DeItem));
|
|
859
|
+
DeItem* ib = (DeItem*)malloc((capb ? capb : 1) * sizeof(DeItem));
|
|
860
|
+
if (!ia || !ib) {
|
|
861
|
+
free(ia);
|
|
862
|
+
free(ib);
|
|
863
|
+
xpath_result_free(a);
|
|
864
|
+
xpath_result_free(b);
|
|
865
|
+
return NULL;
|
|
866
|
+
}
|
|
867
|
+
size_t na = de_collect(a, ia, capa);
|
|
868
|
+
size_t nb = de_collect(b, ib, capb);
|
|
850
869
|
|
|
851
870
|
int equal = na == nb;
|
|
852
|
-
for (size_t i = 0; equal && i < na
|
|
871
|
+
for (size_t i = 0; equal && i < na; i++)
|
|
853
872
|
equal = de_item_equal(&ia[i], &ib[i]);
|
|
854
873
|
|
|
855
|
-
for (size_t i = 0; i < na
|
|
856
|
-
for (size_t i = 0; i < nb
|
|
874
|
+
for (size_t i = 0; i < na; i++) de_item_clear(&ia[i]);
|
|
875
|
+
for (size_t i = 0; i < nb; i++) de_item_clear(&ib[i]);
|
|
876
|
+
free(ia);
|
|
877
|
+
free(ib);
|
|
857
878
|
xpath_result_free(a);
|
|
858
879
|
xpath_result_free(b);
|
|
859
880
|
|
|
@@ -59,7 +59,20 @@ static void seq_push_str(struct leptris_xpath_result* seq, const char* s) {
|
|
|
59
59
|
|
|
60
60
|
static void seq_push_num(struct leptris_xpath_result* seq, double d) {
|
|
61
61
|
char* s = xpath_number_to_string(d);
|
|
62
|
-
|
|
62
|
+
if (!s) return;
|
|
63
|
+
/* "\x03N" marks numeric members — same discipline as the
|
|
64
|
+
* sequence constructor: per-member type checks (deep-equal,
|
|
65
|
+
* instance of) must see a NUMBER, not a string that happens
|
|
66
|
+
* to look like one. String consumers strip the marker. */
|
|
67
|
+
size_t sl = strlen(s);
|
|
68
|
+
char* marked = (char*)malloc(sl + 3);
|
|
69
|
+
if (marked) {
|
|
70
|
+
marked[0] = '\x03';
|
|
71
|
+
marked[1] = 'N';
|
|
72
|
+
memcpy(marked + 2, s, sl + 1);
|
|
73
|
+
seq_push_str(seq, marked);
|
|
74
|
+
free(marked);
|
|
75
|
+
}
|
|
63
76
|
free(s);
|
|
64
77
|
}
|
|
65
78
|
|
|
@@ -71,6 +84,11 @@ static char* scalar_str(const struct leptris_xpath_result* r) {
|
|
|
71
84
|
return leptris_strdup(r->value.string_value
|
|
72
85
|
? r->value.string_value : "");
|
|
73
86
|
case XPATH_RESULT_NUMBER: {
|
|
87
|
+
/* int64 fidelity: an xs:integer-sourced number must
|
|
88
|
+
* string exactly — the double roundtrip prints
|
|
89
|
+
* 830993497117024304 as 8.30993e+17. */
|
|
90
|
+
if (r->is_int)
|
|
91
|
+
return xpath_int_to_string(r->int_value);
|
|
74
92
|
char* s = xpath_number_to_string(r->value.number_value);
|
|
75
93
|
return s ? s : leptris_strdup("");
|
|
76
94
|
}
|
|
@@ -222,6 +240,13 @@ static struct leptris_xpath_result* fn_subsequence(XPathContext* ctx,
|
|
|
222
240
|
if (sv) leptris_xpath_result_free(sv);
|
|
223
241
|
struct leptris_xpath_result* out = seq_new();
|
|
224
242
|
if (out) {
|
|
243
|
+
/* F&O: a negative $length yields the empty sequence
|
|
244
|
+
* (K-SeqSubsequenceFunc-5) — len < 0 is NOT the "to end"
|
|
245
|
+
* sentinel here. */
|
|
246
|
+
if (n >= 3 && ld < 0) {
|
|
247
|
+
free_items(items, cnt);
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
225
250
|
long start = (long)sd;
|
|
226
251
|
long len = (n >= 3) ? (ld != ld ? -1 : (long)ld) : -1;
|
|
227
252
|
seq_from_items(out, items, cnt, start, len);
|
|
@@ -257,6 +282,10 @@ static struct leptris_xpath_result* fn_insert_before(XPathContext* ctx,
|
|
|
257
282
|
size_t icnt;
|
|
258
283
|
char** ins = collect_items(ctx, args, n, 2, &icnt);
|
|
259
284
|
struct leptris_xpath_result* out = seq_new();
|
|
285
|
+
/* F&O: position < 1 inserts at 1; a position beyond the tail
|
|
286
|
+
* inserts at the end (K-SeqInsertBeforeFunc-5/10/14/15). */
|
|
287
|
+
if (at < 1) at = 1;
|
|
288
|
+
if (at > (long)cnt + 1) at = (long)cnt + 1;
|
|
260
289
|
if (out && ins) {
|
|
261
290
|
for (long k = 1; k <= (long)(cnt + icnt); k++) {
|
|
262
291
|
if (k == at)
|