leptris 1.9.282.0-x86_64-linux → 1.9.285.0-x86_64-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 +29 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +25 -3
- data/lib/leptris/xml/iterparse.rb +26 -6
- data/lib/leptris/xml/parse_options.rb +15 -0
- data/lib/leptris/xml/pull.rb +25 -5
- data/lib/leptris/xml/sax/parser.rb +12 -1
- data/lib/leptris/xml/sax/recorder.rb +11 -2
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +38 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/include/leptris/sax/sax.h +71 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +8 -0
- data/vendor-src/libleptris/src/leptris/sax/iterparse.c +56 -16
- data/vendor-src/libleptris/src/leptris/sax/parser.c +1 -0
- data/vendor-src/libleptris/src/leptris/sax/pull.c +13 -2
- data/vendor-src/libleptris/src/leptris/sax/recorder.c +6 -0
- data/vendor-src/libleptris/src/leptris/sax/sax_internal.h +5 -0
- data/vendor-src/libleptris/src/leptris/sax/streaming.c +14 -3
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +1 -0
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +134 -50
- data/vendor-src/libleptris/src/leptris/xpath/lexer.c +15 -1
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +15 -7
- data/vendor-src/libleptris/test/sax/test_iterparse.cpp +325 -0
- data/vendor-src/libleptris/test/sax/test_recorder.cpp +55 -0
- data/vendor-src/libleptris/test/xpath/test_xslt.cpp +5 -1
- data/vendor-src/libleptris/test/xquery/qt3/fn/contains-token.xml +393 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/default-collation.xml +71 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/default-language.xml +74 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/escape-html-uri.xml +312 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/false.xml +236 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/iri-to-uri.xml +436 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/normalize-unicode.xml +478 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/true.xml +236 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +72 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1cbf52fef11af3a2a989e2d9c976be1830e9778f9f80a074ab93cc5d7f740bcd
|
|
4
|
+
data.tar.gz: 636a0e2c00986bb22caa232b80b250175cfdf4906173e67e65846fb216fe95b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0ce1e3a7abe74fe2c2958a81bd1619839957adbe375a157caeed0807d79078d6d059872ab87bfe175072da70fdb82e4590600f50c3a859b9f8759a7166a00ea
|
|
7
|
+
data.tar.gz: 6e954ed7a59241c1d7490af5ecbeddbfd91ef847a99bd4a399c320260ae25c3845650f348ff48a07edbed14278d75c368537ebcb422834ae50406d310b426ebc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## [1.9.285.0] - 2026-10-01
|
|
2
|
+
|
|
3
|
+
### Added — streaming Door A opt-outs (libleptris 1.9.283, #1459)
|
|
4
|
+
|
|
5
|
+
- The 1.9.273 `ParseOptions` opt-outs now reach the streaming
|
|
6
|
+
faces: `options:` on `Iterparse.parse` / `parse_file`,
|
|
7
|
+
`Pull::Parser.parse` / `parse_file`, `SAX::Recorder.open`, and
|
|
8
|
+
`SAX::Parser` (`options:` + a `skip_flags=` setter applied
|
|
9
|
+
before the first feed on the IO path). Duplicate attributes are
|
|
10
|
+
admitted with the FIRST value winning (DOM parity — the pull
|
|
11
|
+
Hash view keeps the first of a redefined pair; the raw event
|
|
12
|
+
pair list is unchanged), error positions degrade to the last
|
|
13
|
+
tracked state. `ParseOptions.stream_flags` resolves only the two
|
|
14
|
+
Door A bits; nil keeps the flagless C twins byte-identical.
|
|
15
|
+
Six new attaches (audit 361/360).
|
|
16
|
+
|
|
17
|
+
### Fixed — engine sync (1.9.284-1.9.285)
|
|
18
|
+
|
|
19
|
+
- Vendored libleptris 1.9.284+1.9.285: iterparse full-document
|
|
20
|
+
mode materializes ONE intact tree (1.9.284) — and 1.9.285
|
|
21
|
+
completes it: TEXT at depth >= 2 pooled against a document
|
|
22
|
+
full mode never creates and silently vanished
|
|
23
|
+
(`<deep>x</deep>` yielded `<deep/>`); text now pools into
|
|
24
|
+
root_doc at every depth (leptris#1471, red/green pinned), plus
|
|
25
|
+
`fn:current-date/-time/-dateTime` clock fixes, XQuery
|
|
26
|
+
contains-token/collation accessors, and the XQuery lexer's
|
|
27
|
+
doubled-quote escapes. Tree-integrity and text-survival specs
|
|
28
|
+
pin both fixes on the binding side.
|
|
29
|
+
|
|
1
30
|
## [1.9.282.0] - 2026-10-01
|
|
2
31
|
|
|
3
32
|
### Fixed — engine sync (1.9.281)
|
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.285"
|
|
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/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -971,6 +971,11 @@ attach_function :leptris_parse_string,
|
|
|
971
971
|
# drains in bulk — callbacks become O(chunks), not O(events).
|
|
972
972
|
attach_function :leptris_sax_recorder_new,
|
|
973
973
|
[], :leptris_sax_recorder
|
|
974
|
+
# Door A opt-outs (1.9.283, #1459): recorder twin — error
|
|
975
|
+
# record positions degrade to the last tracked state when
|
|
976
|
+
# SKIP_SOURCE_POSITIONS is set.
|
|
977
|
+
attach_function :leptris_sax_recorder_new_flags,
|
|
978
|
+
[:uint], :leptris_sax_recorder
|
|
974
979
|
attach_function :leptris_sax_recorder_feed,
|
|
975
980
|
[:leptris_sax_recorder, :string, :size_t, :int], :int
|
|
976
981
|
attach_function :leptris_sax_recorder_records,
|
|
@@ -987,13 +992,24 @@ attach_function :leptris_parse_string,
|
|
|
987
992
|
attach_function :leptris_sax_parser_free,
|
|
988
993
|
[:leptris_sax_parser], :void
|
|
989
994
|
attach_function :leptris_sax_parser_set_streaming,
|
|
990
|
-
[:leptris_sax_parser, :int], :int
|
|
995
|
+
[:leptris_sax_parser, :int], :int # Door A opt-outs (1.9.283, #1459): set once before the first
|
|
996
|
+
# feed(); duplicate attributes admitted keep the first value
|
|
997
|
+
# (DOM parity), error positions degrade.
|
|
998
|
+
attach_function :leptris_sax_parser_set_skip_flags,
|
|
999
|
+
[:leptris_sax_parser, :uint], :void
|
|
991
1000
|
|
|
992
1001
|
# Pull parsing (v1.6.0): StAX-style cursor over a document.
|
|
993
1002
|
attach_function :leptris_pull_new,
|
|
994
1003
|
[:string, :size_t], :leptris_pull_parser
|
|
995
1004
|
attach_function :leptris_pull_new_file,
|
|
996
|
-
[:string], :leptris_pull_parser
|
|
1005
|
+
[:string], :leptris_pull_parser # Door A opt-outs on the streaming path (1.9.283, #1459):
|
|
1006
|
+
# LEPTRIS_PARSE_SKIP_DUP_DETECTION / SKIP_SOURCE_POSITIONS —
|
|
1007
|
+
# same bits the DOM parse flags consume. 0 matches the
|
|
1008
|
+
# flagless twins above.
|
|
1009
|
+
attach_function :leptris_pull_new_flags,
|
|
1010
|
+
[:string, :size_t, :uint], :leptris_pull_parser
|
|
1011
|
+
attach_function :leptris_pull_new_file_flags,
|
|
1012
|
+
[:string, :uint], :leptris_pull_parser
|
|
997
1013
|
attach_function :leptris_pull_next,
|
|
998
1014
|
[:leptris_pull_parser], :pointer
|
|
999
1015
|
attach_function :leptris_pull_next_batch,
|
|
@@ -1023,7 +1039,13 @@ attach_function :leptris_parse_string,
|
|
|
1023
1039
|
attach_function :leptris_iterparse_new_ex,
|
|
1024
1040
|
[:string, :size_t, :int], :leptris_iterparse
|
|
1025
1041
|
attach_function :leptris_iterparse_new_file_ex,
|
|
1026
|
-
[:string, :int], :leptris_iterparse
|
|
1042
|
+
[:string, :int], :leptris_iterparse # Door A opt-outs (1.9.283, #1459): flags as in the pull
|
|
1043
|
+
# twins; iterparse over huge files is exactly the workload
|
|
1044
|
+
# that never consumes diagnostics.
|
|
1045
|
+
attach_function :leptris_iterparse_new_ex_flags,
|
|
1046
|
+
[:string, :size_t, :int, :uint], :leptris_iterparse
|
|
1047
|
+
attach_function :leptris_iterparse_new_file_ex_flags,
|
|
1048
|
+
[:string, :int, :uint], :leptris_iterparse
|
|
1027
1049
|
attach_function :leptris_iterparse_ns_count,
|
|
1028
1050
|
[:leptris_iterparse], :size_t
|
|
1029
1051
|
attach_function :leptris_iterparse_ns_uri,
|
|
@@ -42,10 +42,22 @@ class Leptris::XML::Iterparse
|
|
|
42
42
|
# NOTE: an IO argument is read fully into memory before the C
|
|
43
43
|
# iterator starts — the bounded-memory path is parse_file (C-side
|
|
44
44
|
# file streaming). The C API takes one (xml, len) buffer.
|
|
45
|
-
|
|
45
|
+
# options: a ParseOptions carrying the Door A opt-outs
|
|
46
|
+
# (1.9.283, #1459) — duplicate attributes admitted keep the
|
|
47
|
+
# first value, error positions degrade to the last tracked
|
|
48
|
+
# state. Iterparse over huge files never consumes diagnostics.
|
|
49
|
+
def self.parse(xml_or_io, mode: :top_level, options: nil, &block)
|
|
46
50
|
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
52
|
+
handle =
|
|
53
|
+
if flags
|
|
54
|
+
Leptris::XML::FFI.leptris_iterparse_new_ex_flags(
|
|
55
|
+
xml, xml.bytesize, mode_code(mode), flags)
|
|
56
|
+
else
|
|
57
|
+
Leptris::XML::FFI.leptris_iterparse_new_ex(
|
|
58
|
+
xml, xml.bytesize, mode_code(mode))
|
|
59
|
+
end
|
|
60
|
+
iterator = new(handle)
|
|
49
61
|
# leptris_iterparse_new_ex retains the buffer and reads it
|
|
50
62
|
# lazily in bounded slices (#1207-class): without this
|
|
51
63
|
# reference the input String is collectable the moment parse
|
|
@@ -61,9 +73,17 @@ class Leptris::XML::Iterparse
|
|
|
61
73
|
iterator
|
|
62
74
|
end
|
|
63
75
|
|
|
64
|
-
def self.parse_file(path, mode: :top_level, &block)
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
def self.parse_file(path, mode: :top_level, options: nil, &block)
|
|
77
|
+
flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
78
|
+
handle =
|
|
79
|
+
if flags
|
|
80
|
+
Leptris::XML::FFI.leptris_iterparse_new_file_ex_flags(
|
|
81
|
+
path, mode_code(mode), flags)
|
|
82
|
+
else
|
|
83
|
+
Leptris::XML::FFI.leptris_iterparse_new_file_ex(
|
|
84
|
+
path, mode_code(mode))
|
|
85
|
+
end
|
|
86
|
+
iterator = new(handle)
|
|
67
87
|
return iterator unless block
|
|
68
88
|
begin
|
|
69
89
|
iterator.run(&block)
|
|
@@ -72,6 +72,21 @@ class Leptris::XML::ParseOptions
|
|
|
72
72
|
new(DTDATTR)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# Streaming-path resolver (1.9.283, #1459): the iterparse / pull /
|
|
76
|
+
# recorder / SAX-IO faces consume exactly the two Door A opt-out
|
|
77
|
+
# bits — everything else on a ParseOptions is DOM-parse surface.
|
|
78
|
+
# Returns nil when no opt-out is set, so the flagless C twins
|
|
79
|
+
# keep their zero-flag default byte-parity.
|
|
80
|
+
def self.stream_flags(options)
|
|
81
|
+
return nil unless options
|
|
82
|
+
unless options.is_a?(Leptris::XML::ParseOptions)
|
|
83
|
+
raise ArgumentError, "options must be a Leptris::XML::ParseOptions"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
mask = SKIP_DUP_DETECTION | SKIP_SOURCE_POSITIONS
|
|
87
|
+
flags = options.flags & mask
|
|
88
|
+
flags.zero? ? nil : flags
|
|
89
|
+
end
|
|
75
90
|
def self.skip_dup_detection
|
|
76
91
|
new(SKIP_DUP_DETECTION)
|
|
77
92
|
end
|
data/lib/leptris/xml/pull.rb
CHANGED
|
@@ -38,13 +38,29 @@ module Leptris::XML::Pull
|
|
|
38
38
|
}.freeze
|
|
39
39
|
|
|
40
40
|
class Parser
|
|
41
|
-
|
|
41
|
+
# options: Door A opt-outs (1.9.283, #1459) — same semantics
|
|
42
|
+
# as Document.parse's.
|
|
43
|
+
def self.parse(xml_or_io, options: nil)
|
|
42
44
|
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
43
|
-
|
|
45
|
+
flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
46
|
+
handle =
|
|
47
|
+
if flags
|
|
48
|
+
Leptris::XML::FFI.leptris_pull_new_flags(xml, xml.bytesize, flags)
|
|
49
|
+
else
|
|
50
|
+
Leptris::XML::FFI.leptris_pull_new(xml, xml.bytesize)
|
|
51
|
+
end
|
|
52
|
+
new(handle)
|
|
44
53
|
end
|
|
45
54
|
|
|
46
|
-
def self.parse_file(path)
|
|
47
|
-
|
|
55
|
+
def self.parse_file(path, options: nil)
|
|
56
|
+
flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
57
|
+
handle =
|
|
58
|
+
if flags
|
|
59
|
+
Leptris::XML::FFI.leptris_pull_new_file_flags(path, flags)
|
|
60
|
+
else
|
|
61
|
+
Leptris::XML::FFI.leptris_pull_new_file(path)
|
|
62
|
+
end
|
|
63
|
+
new(handle)
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
def initialize(handle)
|
|
@@ -187,7 +203,11 @@ module Leptris::XML::Pull
|
|
|
187
203
|
.force_encoding(Encoding::UTF_8)
|
|
188
204
|
value = buffer.get_pointer((2 * i + 1) * ptr_size).read_string
|
|
189
205
|
.force_encoding(Encoding::UTF_8)
|
|
190
|
-
|
|
206
|
+
# DOM parity (1.9.283, #1459): with SKIP_DUP_DETECTION the
|
|
207
|
+
# raw event still carries both pairs (libxml2-recover keeps
|
|
208
|
+
# every attribute); the Hash view keeps the FIRST value,
|
|
209
|
+
# like iterparse and the DOM path.
|
|
210
|
+
hash[name] = value unless hash.key?(name)
|
|
191
211
|
i += 1
|
|
192
212
|
end
|
|
193
213
|
hash
|
|
@@ -18,10 +18,19 @@ class Leptris::XML::SAX::Parser
|
|
|
18
18
|
# correctness-first fallback kept for engines older than 1.9.18
|
|
19
19
|
# and for consumers wanting the DOM view's shapes.
|
|
20
20
|
def initialize(handler = Leptris::XML::SAX::Document.new,
|
|
21
|
-
encoding = nil, streaming: true)
|
|
21
|
+
encoding = nil, streaming: true, options: nil)
|
|
22
22
|
@document = handler
|
|
23
23
|
@encoding = encoding
|
|
24
24
|
@streaming = streaming
|
|
25
|
+
@skip_flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Door A opt-outs (1.9.283, #1459) applied before the first
|
|
29
|
+
# feed on the IO path — set once, not thread-safe per the C
|
|
30
|
+
# contract.
|
|
31
|
+
def skip_flags=(flags)
|
|
32
|
+
@skip_flags = flags.to_i & (Leptris::XML::ParseOptions::SKIP_DUP_DETECTION |
|
|
33
|
+
Leptris::XML::ParseOptions::SKIP_SOURCE_POSITIONS)
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
# Swapping the handler invalidates the memoized callback struct so
|
|
@@ -163,6 +172,8 @@ class Leptris::XML::SAX::Parser
|
|
|
163
172
|
handler_struct.pointer, nil)
|
|
164
173
|
if parser_ptr.null?
|
|
165
174
|
raise Leptris::XML::Error, "leptris_sax_parser_create failed"
|
|
175
|
+
Leptris::XML::FFI.leptris_sax_parser_set_skip_flags(
|
|
176
|
+
parser_ptr, @skip_flags || 0)
|
|
166
177
|
end
|
|
167
178
|
begin
|
|
168
179
|
while (chunk = io.read(CHUNK_SIZE))
|
|
@@ -33,8 +33,17 @@ class Leptris::XML::SAX::Recorder
|
|
|
33
33
|
# not a Hash lookup per event.
|
|
34
34
|
KIND_BY_CODE = Array.new(KINDS.size) { |code| KINDS[code] }.freeze
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
# options: Door A opt-outs (1.9.283, #1459) — error record
|
|
37
|
+
# positions degrade to the last tracked state when
|
|
38
|
+
# SKIP_SOURCE_POSITIONS is set.
|
|
39
|
+
def self.open(options: nil)
|
|
40
|
+
flags = Leptris::XML::ParseOptions.stream_flags(options)
|
|
41
|
+
raw =
|
|
42
|
+
if flags
|
|
43
|
+
Leptris::XML::FFI.leptris_sax_recorder_new_flags(flags)
|
|
44
|
+
else
|
|
45
|
+
Leptris::XML::FFI.leptris_sax_recorder_new
|
|
46
|
+
end
|
|
38
47
|
if raw.null?
|
|
39
48
|
raise Leptris::XML::Error, "leptris_sax_recorder_new failed"
|
|
40
49
|
end
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.285] - 2026-10-01
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- full-document iterparse dropped TEXT at depth >= 2 — pool every node into root_doc (sax)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [1.9.284] - 2026-10-01
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- adopt contains-token + boolean/collation accessors — string tails batch 5 (xquery)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
- **iterparse full-document mode silently detached every child of the materialized root** (field-reported total child loss): `<sections><!-- c --><child/>...</sections>` yielded `sections` with 0 children — depth-1 elements were created as roots of throwaway subtree documents instead of appending to the parent. Full-document mode now materializes one intact tree; yields carry the whole subtree at every step. Shipped with an 8-test tree-integrity regression battery (reported shape class, deep subtrees, attributes, chunked/file sources, the v1 contract pinned unchanged, malformed-input error paths).
|
|
22
|
+
- **`fn:current-date` / `fn:current-time` / `fn:current-dateTime` computed from an uninitialized clock and smashed the stack on every call** — the POSIX `gmtime_r` arguments in `h_utc_now` were reversed (`(struct tm*, time_t*)` instead of `(const time_t*, struct tm*)`), so each call read a garbage epoch and wrote the 36-byte result over an 8-byte local. Values are now real (verified against the system clock); the Windows `gmtime_s` branch was always correct.
|
|
23
|
+
- **F&O 14.5-14.7 stability**: the current-date/-time/-dateTime trio now returns ONE instant per evaluation (context-stamped) instead of re-reading the clock per call — same-query comparisons (`current-time() eq current-time()`) can no longer straddle a second boundary. Verified by a 100k-iteration equality loop and a 2000-iteration spec.
|
|
24
|
+
|
|
25
|
+
## [1.9.283] - 2026-10-01
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **fn:normalize-unicode (F&O conformance)**: the single-argument form now normalizes with NFC (it previously returned the input unchanged); an unknown normalization form raises `FOCH0003` instead of silently falling back to NFC; form matching is case-insensitive with leading/trailing whitespace trimmed, and a zero-length form stays identity. Pinned by the QT3 corpus (29 run-and-agree) with a new runner-side dependency gate for the FULLY-NORMALIZED twins.
|
|
30
|
+
- **fn:iri-to-uri / fn:escape-html-uri (F&O conformance)**: `#` is IRI-legal and no longer percent-encodes; escape-html-uri now keeps printable US-ASCII (0x20–0x7E) and percent-encodes every other UTF-8 byte — it previously emitted HTML entities like `<`. Pinned by the QT3 corpora (38 + 30 run-and-agree).
|
|
31
|
+
- **XQuery lexer**: doubled-quote escapes (XQuery 3.1 A.1 EscapeQuot/EscapeApos) — `"a ""b"" c"` previously failed to parse; the literal decoder also collapses them.
|
|
32
|
+
- **fn:contains-token**: the token argument is trimmed of surrounding whitespace before matching (F&O 5.5.5).
|
|
33
|
+
- **fn:default-collation / fn:default-language** are now implemented (codepoint collation URI; `en`), and `format-integer` accepts its 3-argument language form.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Door A parse opt-outs on the streaming path** (#1459): `leptris_iterparse_new_ex_flags` / `leptris_iterparse_new_file_ex_flags`, `leptris_pull_new_flags` / `leptris_pull_new_file_flags`, `leptris_sax_recorder_new_flags`, and the `leptris_sax_parser_set_skip_flags` primitive — `LEPTRIS_PARSE_SKIP_DUP_DETECTION` and `LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS` now reach iterparse/pull/recorder, with duplicate attributes admitted keeping the first value (DOM parity).
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
3
41
|
## [1.9.282] - 2026-10-01
|
|
4
42
|
|
|
5
43
|
### Added
|
|
@@ -312,6 +312,21 @@ LEPTRIS_API int leptris_sax_parser_set_streaming(LeptrisSAXParser* parser, int s
|
|
|
312
312
|
LEPTRIS_API void leptris_sax_parser_set_one_shot(LeptrisSAXParser* parser,
|
|
313
313
|
int one_shot);
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Set the lane-18 Door A parse opt-outs (#1459): any combination of
|
|
317
|
+
* LEPTRIS_PARSE_SKIP_DUP_DETECTION (drop the duplicate-attribute
|
|
318
|
+
* redefinition scan — duplicates are admitted silently) and
|
|
319
|
+
* LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS (freeze per-byte line/column
|
|
320
|
+
* accounting; error reports degrade to the position of the last
|
|
321
|
+
* tracked state). 0 (the create default) keeps full conformance
|
|
322
|
+
* behavior. These are the same bits leptris_parse_flags consumes on
|
|
323
|
+
* the DOM path.
|
|
324
|
+
*
|
|
325
|
+
* Thread safety: Not thread-safe. Set once before the first feed().
|
|
326
|
+
*/
|
|
327
|
+
LEPTRIS_API void leptris_sax_parser_set_skip_flags(LeptrisSAXParser* parser,
|
|
328
|
+
unsigned flags);
|
|
329
|
+
|
|
315
330
|
/* ============================================================================
|
|
316
331
|
* Pull (StAX-style) API — TODO.bindings/04, issue #510 Tier 2
|
|
317
332
|
* ============================================================================ */
|
|
@@ -332,6 +347,18 @@ LEPTRIS_API void leptris_sax_parser_set_one_shot(LeptrisSAXParser* parser,
|
|
|
332
347
|
*/
|
|
333
348
|
LEPTRIS_API LeptrisPullParser leptris_pull_new(const char* xml, size_t len);
|
|
334
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Create a pull parser with the Door A parse opt-outs (#1459)
|
|
352
|
+
*
|
|
353
|
+
* Same event semantics as leptris_pull_new; `flags` is any
|
|
354
|
+
* combination of LEPTRIS_PARSE_SKIP_DUP_DETECTION and
|
|
355
|
+
* LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS (see
|
|
356
|
+
* leptris_sax_parser_set_skip_flags). 0 matches leptris_pull_new.
|
|
357
|
+
*/
|
|
358
|
+
LEPTRIS_API LeptrisPullParser leptris_pull_new_flags(const char* xml,
|
|
359
|
+
size_t len,
|
|
360
|
+
unsigned flags);
|
|
361
|
+
|
|
335
362
|
/**
|
|
336
363
|
* Create a pull parser streaming from a file (TODO.engine/01)
|
|
337
364
|
*
|
|
@@ -344,6 +371,14 @@ LEPTRIS_API LeptrisPullParser leptris_pull_new(const char* xml, size_t len);
|
|
|
344
371
|
*/
|
|
345
372
|
LEPTRIS_API LeptrisPullParser leptris_pull_new_file(const char* path);
|
|
346
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Create a file-backed pull parser with the Door A parse opt-outs
|
|
376
|
+
* (#1459) — flags as in leptris_pull_new_flags. 0 matches
|
|
377
|
+
* leptris_pull_new_file.
|
|
378
|
+
*/
|
|
379
|
+
LEPTRIS_API LeptrisPullParser leptris_pull_new_file_flags(const char* path,
|
|
380
|
+
unsigned flags);
|
|
381
|
+
|
|
347
382
|
/**
|
|
348
383
|
* Return the next event, feeding input as needed
|
|
349
384
|
*
|
|
@@ -450,6 +485,20 @@ LEPTRIS_API void leptris_pull_free(LeptrisPullParser pull);
|
|
|
450
485
|
*/
|
|
451
486
|
LEPTRIS_API LeptrisSaxRecorder leptris_sax_recorder_new(void);
|
|
452
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Create a chunked SAX event recorder with the Door A parse
|
|
490
|
+
* opt-outs (#1459)
|
|
491
|
+
*
|
|
492
|
+
* Same record semantics as leptris_sax_recorder_new; `flags` is any
|
|
493
|
+
* combination of LEPTRIS_PARSE_SKIP_DUP_DETECTION (no ERROR record
|
|
494
|
+
* for a redefined attribute) and
|
|
495
|
+
* LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS (ERROR record positions
|
|
496
|
+
* degrade to the last tracked state). 0 matches
|
|
497
|
+
* leptris_sax_recorder_new.
|
|
498
|
+
*/
|
|
499
|
+
LEPTRIS_API LeptrisSaxRecorder leptris_sax_recorder_new_flags(
|
|
500
|
+
unsigned flags);
|
|
501
|
+
|
|
453
502
|
/**
|
|
454
503
|
* Feed one input chunk and buffer the events it produces
|
|
455
504
|
*
|
|
@@ -564,6 +613,28 @@ LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file(const char* path);
|
|
|
564
613
|
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file_ex(
|
|
565
614
|
const char* path, LeptrisIterparseMode mode);
|
|
566
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Create an incremental tree-iterator with explicit yield mode and
|
|
618
|
+
* the Door A parse opt-outs (#1459)
|
|
619
|
+
*
|
|
620
|
+
* Same yield semantics as leptris_iterparse_new_ex; `flags` is any
|
|
621
|
+
* combination of LEPTRIS_PARSE_SKIP_DUP_DETECTION (duplicate
|
|
622
|
+
* attributes are admitted instead of failing the walk — the first
|
|
623
|
+
* value surfaces) and LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS (error
|
|
624
|
+
* positions degrade to the last tracked state). 0 matches
|
|
625
|
+
* leptris_iterparse_new_ex. Iterparse over huge files is exactly
|
|
626
|
+
* the workload that never consumes diagnostics.
|
|
627
|
+
*/
|
|
628
|
+
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_ex_flags(
|
|
629
|
+
const char* xml, size_t len, LeptrisIterparseMode mode,
|
|
630
|
+
unsigned flags);
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* File-backed twin of leptris_iterparse_new_ex_flags
|
|
634
|
+
*/
|
|
635
|
+
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file_ex_flags(
|
|
636
|
+
const char* path, LeptrisIterparseMode mode, unsigned flags);
|
|
637
|
+
|
|
567
638
|
/**
|
|
568
639
|
* Return the next completed top-level element
|
|
569
640
|
*
|
|
@@ -774,6 +774,14 @@ typedef struct xpath_context {
|
|
|
774
774
|
* 1e-18, INF/-INF) instead of the libxml2-parity XPath 1.0
|
|
775
775
|
* printer. Set by the XQuery eval entry. */
|
|
776
776
|
int xquery_spelling;
|
|
777
|
+
|
|
778
|
+
/* F&O 14.5-14.7 stability: current-date/-time/-dateTime return
|
|
779
|
+
* the same instant for the whole evaluation. First caller
|
|
780
|
+
* stamps it; the CURRENT_FN trio reads the cache (K2-Codepoint
|
|
781
|
+
* Equal-1 compares two current-time() renders in one query —
|
|
782
|
+
* a clock re-read straddling a second boundary flaked CI). */
|
|
783
|
+
int now_cached;
|
|
784
|
+
long now_sec; /* time_t as long (keeps the header self-contained) */
|
|
777
785
|
} XPathContext;
|
|
778
786
|
|
|
779
787
|
/* XPath operator types - From ext/leptris/xpath.h */
|
|
@@ -47,6 +47,7 @@ struct leptris_iterparse {
|
|
|
47
47
|
LeptrisElement stack[IT_MAX_DEPTH];
|
|
48
48
|
int depth; /* open element depth (root=1) */
|
|
49
49
|
LeptrisIterparseMode mode;
|
|
50
|
+
int keep_first_dup; /* SKIP_DUP_DETECTION: DOM-parity (#1459) */
|
|
50
51
|
char* text_buf;
|
|
51
52
|
size_t text_len, text_cap;
|
|
52
53
|
LeptrisElement done; /* completed element handed out */
|
|
@@ -138,14 +139,21 @@ static void it_flush_text(struct leptris_iterparse* it) {
|
|
|
138
139
|
/* Text must be created in its PARENT's pool. At depth 1 the
|
|
139
140
|
* parent is the document root (root_doc, full mode only); in v1
|
|
140
141
|
* mode there is no materialized parent — the text is dropped,
|
|
141
|
-
* exactly as before.
|
|
142
|
+
* exactly as before. FULL mode materializes ONE tree entirely
|
|
143
|
+
* inside root_doc — it->doc is never created there, so text at
|
|
144
|
+
* any depth must pool into root_doc as well (deeper text used
|
|
145
|
+
* to create against a NULL doc and silently vanish: nested
|
|
146
|
+
* <deep>x</deep> yielded <deep/>). */
|
|
147
|
+
int full_mode = (it->mode == LEPTRIS_ITERPARSE_FULL_DOCUMENT);
|
|
142
148
|
int root_level = (it->depth == 1);
|
|
143
149
|
if (it->text_len == 0 || it->depth < 1 ||
|
|
144
|
-
(
|
|
150
|
+
(full_mode && !it->root_doc) ||
|
|
151
|
+
(!full_mode && root_level && !it->root_doc)) {
|
|
145
152
|
it->text_len = 0;
|
|
146
153
|
return;
|
|
147
154
|
}
|
|
148
|
-
LeptrisDocument pool_doc =
|
|
155
|
+
LeptrisDocument pool_doc =
|
|
156
|
+
(full_mode || root_level) ? it->root_doc : it->doc;
|
|
149
157
|
it->text_buf[it->text_len] = '\0';
|
|
150
158
|
LeptrisNodeRef t = leptris_text_node_create(pool_doc, it->text_buf);
|
|
151
159
|
if (t) {
|
|
@@ -170,13 +178,16 @@ static void it_append_text(struct leptris_iterparse* it,
|
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
static LeptrisIterparse it_new_common(LeptrisPullParser pull,
|
|
173
|
-
LeptrisIterparseMode mode
|
|
181
|
+
LeptrisIterparseMode mode,
|
|
182
|
+
unsigned flags) {
|
|
174
183
|
if (!pull) return NULL;
|
|
175
184
|
struct leptris_iterparse* it =
|
|
176
185
|
(struct leptris_iterparse*)calloc(1, sizeof(*it));
|
|
177
186
|
if (!it) { leptris_pull_free(pull); return NULL; }
|
|
178
187
|
it->pull = pull;
|
|
179
188
|
it->mode = mode;
|
|
189
|
+
it->keep_first_dup =
|
|
190
|
+
(flags & LEPTRIS_PARSE_SKIP_DUP_DETECTION) ? 1 : 0;
|
|
180
191
|
it->sub_arena = leptris_arena_create(IT_SUBTREE_ARENA_BYTES);
|
|
181
192
|
return it;
|
|
182
193
|
}
|
|
@@ -188,8 +199,15 @@ LEPTRIS_API LeptrisIterparse leptris_iterparse_new(const char* xml,
|
|
|
188
199
|
|
|
189
200
|
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_ex(
|
|
190
201
|
const char* xml, size_t len, LeptrisIterparseMode mode) {
|
|
202
|
+
return leptris_iterparse_new_ex_flags(xml, len, mode, 0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_ex_flags(
|
|
206
|
+
const char* xml, size_t len, LeptrisIterparseMode mode,
|
|
207
|
+
unsigned flags) {
|
|
191
208
|
if (!xml || len == 0) return NULL;
|
|
192
|
-
return it_new_common(
|
|
209
|
+
return it_new_common(leptris_pull_new_flags(xml, len, flags),
|
|
210
|
+
mode, flags);
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file(const char* path) {
|
|
@@ -198,8 +216,14 @@ LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file(const char* path) {
|
|
|
198
216
|
|
|
199
217
|
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file_ex(
|
|
200
218
|
const char* path, LeptrisIterparseMode mode) {
|
|
219
|
+
return leptris_iterparse_new_file_ex_flags(path, mode, 0);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
LEPTRIS_API LeptrisIterparse leptris_iterparse_new_file_ex_flags(
|
|
223
|
+
const char* path, LeptrisIterparseMode mode, unsigned flags) {
|
|
201
224
|
if (!path || !*path) return NULL;
|
|
202
|
-
return it_new_common(
|
|
225
|
+
return it_new_common(leptris_pull_new_file_flags(path, flags),
|
|
226
|
+
mode, flags);
|
|
203
227
|
}
|
|
204
228
|
|
|
205
229
|
/* Advance past the last yield. When a completed TOP-LEVEL child was
|
|
@@ -278,8 +302,13 @@ static LeptrisElement handle_start(struct leptris_iterparse* it,
|
|
|
278
302
|
/* depth==1: a new top-level child — a fresh document over the
|
|
279
303
|
* REUSED arena (bump-reset at release; grown when a child nearly
|
|
280
304
|
* fills the span). Arena creation failure falls back to the
|
|
281
|
-
* plain 32 KB-page document.
|
|
282
|
-
|
|
305
|
+
* plain 32 KB-page document. TOP_LEVEL only: full-document mode
|
|
306
|
+
* materializes ONE tree in root_doc — depth-1 children used to
|
|
307
|
+
* take this path too and became roots of throwaway subtree
|
|
308
|
+
* documents, shattering the tree at every root child
|
|
309
|
+
* (yields looked right; the parent ended up empty). */
|
|
310
|
+
if (it->mode == LEPTRIS_ITERPARSE_TOP_LEVEL &&
|
|
311
|
+
it->depth == 1 && !it->doc) {
|
|
283
312
|
it->doc = it->sub_arena
|
|
284
313
|
? leptris_document_create_on_arena(it->sub_arena)
|
|
285
314
|
: leptris_document_create();
|
|
@@ -300,24 +329,35 @@ static LeptrisElement handle_start(struct leptris_iterparse* it,
|
|
|
300
329
|
prefix_buf[plen] = '\0';
|
|
301
330
|
prefix = prefix_buf;
|
|
302
331
|
}
|
|
303
|
-
LeptrisDocument target
|
|
304
|
-
|
|
332
|
+
LeptrisDocument target;
|
|
333
|
+
if (it->mode == LEPTRIS_ITERPARSE_FULL_DOCUMENT)
|
|
334
|
+
target = it->root_doc; /* one tree, one pool (#592 contract) */
|
|
335
|
+
else
|
|
336
|
+
target = (it->depth == 0) ? it->root_doc : it->doc;
|
|
305
337
|
LeptrisElement e = leptris_element_create(target, local);
|
|
306
338
|
if (!e) { it->exhausted = 1; return NULL; }
|
|
307
339
|
if (prefix) leptris_element_set_prefix(e, prefix);
|
|
308
340
|
attach_namespaces(it, e, prefix);
|
|
309
341
|
size_t na = leptris_pull_attr_count(it->pull);
|
|
310
342
|
for (size_t i = 0; i < na; i++) {
|
|
311
|
-
|
|
343
|
+
const char* an = leptris_pull_attr_name(it->pull, i);
|
|
344
|
+
/* SKIP_DUP_DETECTION admits duplicates with DOM parity:
|
|
345
|
+
* keep the first (direct_parse's dup-skip contract); the
|
|
346
|
+
* lookup runs only when the opt-out is armed. */
|
|
347
|
+
if (it->keep_first_dup && leptris_element_attribute(e, an))
|
|
348
|
+
continue;
|
|
349
|
+
leptris_element_set_attribute(e, an,
|
|
312
350
|
leptris_pull_attr_value(it->pull, i));
|
|
313
351
|
}
|
|
314
352
|
if (it->depth == 0) {
|
|
315
353
|
leptris_document_set_root(it->root_doc, e);
|
|
316
|
-
} else if (it->depth == 1) {
|
|
317
|
-
/*
|
|
318
|
-
* never attached to the document root
|
|
319
|
-
* pools; freeing the subtree would
|
|
320
|
-
* chain dangling — the ASAN UAF in
|
|
354
|
+
} else if (it->mode == LEPTRIS_ITERPARSE_TOP_LEVEL && it->depth == 1) {
|
|
355
|
+
/* TOP_LEVEL: a top-level child is the ROOT of its own
|
|
356
|
+
* subtree document — never attached to the document root
|
|
357
|
+
* (that would link two pools; freeing the subtree would
|
|
358
|
+
* leave the root's child chain dangling — the ASAN UAF in
|
|
359
|
+
* PR #590). FULL_DOCUMENT appends below: the whole tree
|
|
360
|
+
* lives in root_doc and must stay INTACT. */
|
|
321
361
|
leptris_document_set_root(it->doc, e);
|
|
322
362
|
} else {
|
|
323
363
|
leptris_element_append_child(it->stack[it->depth - 1], e);
|
|
@@ -62,6 +62,7 @@ LEPTRIS_API LeptrisSAXParser* leptris_sax_parser_create(LeptrisSAXHandler* handl
|
|
|
62
62
|
parser->line = 1;
|
|
63
63
|
parser->column = 1;
|
|
64
64
|
parser->has_error = 0;
|
|
65
|
+
parser->skip_flags = 0;
|
|
65
66
|
parser->error_message[0] = '\0';
|
|
66
67
|
parser->scratch = NULL;
|
|
67
68
|
parser->scratch_tail = NULL;
|