leptris 1.9.92.1 → 1.9.92.2
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 +35 -0
- data/CLAUDE.md +24 -13
- data/README.adoc +31 -0
- data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
- data/TODO.restructure/02-deep-copy-seam.md +21 -0
- data/TODO.restructure/03-evaluation-context-seam.md +18 -0
- data/TODO.restructure/04-spec-mece-restructure.md +20 -0
- data/TODO.restructure/05-memory-documentation.md +13 -0
- data/TODO.restructure/06-architecture-map-refresh.md +12 -0
- data/TODO.restructure/07-external-gates.md +18 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +13 -1
- data/lib/leptris/xml/element.rb +4 -7
- data/lib/leptris/xml/evaluation_context.rb +32 -0
- data/lib/leptris/xml/iterparse.rb +1 -1
- data/lib/leptris/xml/node.rb +4 -10
- data/lib/leptris/xml/sax/parser.rb +3 -1
- data/lib/leptris/xml/serialization.rb +5 -9
- data/lib/leptris/xml/xpath.rb +3 -19
- data/lib/leptris/xml/xquery.rb +3 -13
- data/lib/leptris/xml.rb +1 -0
- 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: 56235008b014f2ccf89a5713d7e95f6cf358a828be7aefb5923a651065980d18
|
|
4
|
+
data.tar.gz: 3d0d3c43d5f2a05fc3c21c0efbeadd33e308d4deaa330fe9b2e8530672944d40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22c6d7837e4c8db9c4298a5a8b1f944e3c3f2b8ad02126a5e6374fb68b8619fa0456e58f9f1aca6f6384ead6148fc0b83d516d254558d35d5f02e7c9671f762f
|
|
7
|
+
data.tar.gz: b891d175c7e9655a1346bd414191d1bc58465ae22f20949ca91a29ce95497edb943e6c9b49b1ca85f824dcd6160a967c12f2be27c12cf69c85303f7402112e12
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ 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.92.2] - 2026-09-07
|
|
9
|
+
|
|
10
|
+
### Changed — restructure round (TODO.restructure/01-06, all DONE)
|
|
11
|
+
|
|
12
|
+
- **Constraint compliance**: the last `respond_to?` duck checks in
|
|
13
|
+
lib are gone — `Document.parse`/`Iterparse.parse` dispatch on
|
|
14
|
+
`is_a?(String)` (readable objects read; garbage fails honestly),
|
|
15
|
+
`SAX::Parser#parse` dispatches on explicit `IO, StringIO,
|
|
16
|
+
Pathname` types. Audit clean: no `send`, no
|
|
17
|
+
`instance_variable_set/_get`, no `require_relative`; the two
|
|
18
|
+
in-file-documented require exceptions (gem-root version, eager
|
|
19
|
+
FFI bootstrap) recorded in the audit.
|
|
20
|
+
- **Deep-copy seam**: `Document.copy_of` is the single authority
|
|
21
|
+
for "C-copy an element as a new document's root" —
|
|
22
|
+
`Node#dup`/`Element#dup`/the indent-unit path delegate (three
|
|
23
|
+
inline duplicates retired; the drift that twice flipped dup
|
|
24
|
+
across #696/#721/#812 can no longer happen).
|
|
25
|
+
- **Evaluation-context seam**: new `EvaluationContext` value object
|
|
26
|
+
(autoloaded) resolves Document-or-Element receivers for
|
|
27
|
+
`XPath#eval` and `XQuery#eval` — one typed seam, one
|
|
28
|
+
ArgumentError shape.
|
|
29
|
+
- **Spec MECE**: HTML specs live in `spec/xml/html_spec.rb`;
|
|
30
|
+
pure-`#xpath` blocks (value-level surface, 2.0 ledger, type
|
|
31
|
+
operators, xs: constructors, ResultText) live in
|
|
32
|
+
`xpath_spec.rb`; `xquery_spec.rb` carries only the XQuery face.
|
|
33
|
+
Count-neutral: 535 examples before and after.
|
|
34
|
+
- **Docs**: README gains a Memory section (finalizer-drain
|
|
35
|
+
semantics, held-workload profile — #147 option A); CLAUDE.md's
|
|
36
|
+
architecture map and conventions refreshed.
|
|
37
|
+
|
|
38
|
+
No behavior change (same FFI sequences, delegation only); suite
|
|
39
|
+
green throughout. The perf battery window was unavailable (shared
|
|
40
|
+
machine at load 184) — neutrality is structural: identical call
|
|
41
|
+
sequences plus one cold-path allocation per expression evaluation.
|
|
42
|
+
|
|
8
43
|
## [1.9.92.1] - 2026-09-06
|
|
9
44
|
|
|
10
45
|
### Changed — hotfix: replaces libleptris 1.9.93
|
data/CLAUDE.md
CHANGED
|
@@ -39,24 +39,32 @@ The C DOM is the single source of truth; Ruby objects are thin FFI
|
|
|
39
39
|
handles. One Ruby method = one FFI call wherever possible.
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
lib/leptris.rb — Leptris::VERSION
|
|
42
|
+
lib/leptris.rb — Leptris::VERSION + eager FFI bootstrap
|
|
43
43
|
lib/leptris/xml.rb — autoload registry, error classes,
|
|
44
|
-
Leptris::XML.parse /
|
|
44
|
+
Leptris::XML.parse / parse_html
|
|
45
45
|
lib/leptris/xml/ffi.rb — every public C declaration + seam
|
|
46
46
|
helpers (check_status, read_owned_string,
|
|
47
|
-
|
|
47
|
+
serialize_into_string, scratch buffers)
|
|
48
48
|
lib/leptris/xml/document.rb — the only C-memory owner (finalizer);
|
|
49
|
-
factories
|
|
49
|
+
factories; Document.copy_of: the single
|
|
50
|
+
deep-copy seam behind Node#dup/Element#dup
|
|
50
51
|
lib/leptris/xml/node.rb — Node.wrap: the ONLY wrapper constructor
|
|
51
|
-
(identity cache + type dispatch
|
|
52
|
+
(identity cache + type dispatch,
|
|
53
|
+
ResultText for sequence items)
|
|
52
54
|
lib/leptris/xml/element.rb — attributes via the v1.1.0 iteration
|
|
53
|
-
face; namespace/
|
|
54
|
-
lib/leptris/xml/
|
|
55
|
-
|
|
56
|
-
lib/leptris/xml/
|
|
55
|
+
face; namespace= (nil-detach/rebind)
|
|
56
|
+
lib/leptris/xml/result_text.rb — sequence/map/array result items
|
|
57
|
+
(values captured at materialization)
|
|
58
|
+
lib/leptris/xml/node_set.rb — lazy XPath/XQuery results; batch fetch
|
|
59
|
+
via get_nodes_ex; ResultText capture
|
|
60
|
+
lib/leptris/xml/searchable.rb — xpath/css/at_*; result materialization
|
|
61
|
+
lib/leptris/xml/evaluation_context.rb — receiver -> (document,
|
|
62
|
+
context-node ptr) for the expression faces
|
|
57
63
|
lib/leptris/xml/xpath.rb — compiled expressions (parse once,
|
|
58
64
|
eval many)
|
|
59
|
-
lib/leptris/xml/
|
|
65
|
+
lib/leptris/xml/xquery.rb — compiled XQuery 1.0 core face
|
|
66
|
+
lib/leptris/xml/xslt.rb — compiled XSLT 1.0-3.0 face
|
|
67
|
+
lib/leptris/xml/sax/ — callback SAX (streaming + DOM dispatch)
|
|
60
68
|
lib/leptris/xml/pull.rb — StAX-style pull parsing
|
|
61
69
|
lib/leptris/xml/iterparse.rb — bounded-memory element iteration
|
|
62
70
|
lib/leptris/xml/serialization.rb — serialize/c14n (Document + Element
|
|
@@ -81,9 +89,12 @@ lib/leptris/xml/css_to_xpath.rb — minimal CSS translation
|
|
|
81
89
|
|
|
82
90
|
## Conventions
|
|
83
91
|
|
|
84
|
-
- Autoload only — no `require_relative`
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
- Autoload only — no `require_relative` or intra-library `require`
|
|
93
|
+
inside `lib/` (sanctioned exceptions, with reasons in-file:
|
|
94
|
+
`leptris/version` at the gem root and the eager FFI bootstrap).
|
|
95
|
+
- No `instance_variable_set`/`_get` across objects; no `send` to
|
|
96
|
+
private methods; no `respond_to?` type checks (dispatch on types
|
|
97
|
+
with `is_a?`/`case`); specs use real documents, never doubles.
|
|
87
98
|
- New C surface: attach in `ffi.rb`, sugar where it earns its keep,
|
|
88
99
|
specs against a locally built library, CHANGELOG, lockstep release.
|
|
89
100
|
- All changes via PRs; no AI attribution; `git add` explicit paths.
|
data/README.adoc
CHANGED
|
@@ -548,6 +548,37 @@ is valid only while its Document is alive.
|
|
|
548
548
|
* Don't hold a `Node` reference past the lifetime of its `Document`.
|
|
549
549
|
The C memory is gone; using the wrapper is undefined behaviour.
|
|
550
550
|
|
|
551
|
+
== Memory behavior
|
|
552
|
+
|
|
553
|
+
Two facts worth knowing for memory-sensitive workloads:
|
|
554
|
+
|
|
555
|
+
**Finalizers drain asynchronously.** When a Document becomes
|
|
556
|
+
unreachable, its C tree frees when Ruby runs the registered
|
|
557
|
+
finalizer — and MRI executes finalizers on its own scheduling, not
|
|
558
|
+
synchronously inside `GC.start`. A tight `GC.start` loop can leave
|
|
559
|
+
the last document's wrappers (flagged `uncollectible`) alive
|
|
560
|
+
indefinitely; a short wall-clock yield drains the queue:
|
|
561
|
+
|
|
562
|
+
[source,ruby]
|
|
563
|
+
----
|
|
564
|
+
doc = Leptris::XML::Document.parse(xml)
|
|
565
|
+
doc.root.children # ...
|
|
566
|
+
doc = nil
|
|
567
|
+
GC.start
|
|
568
|
+
sleep 0.05 # yield to the finalizer queue
|
|
569
|
+
GC.start # now fully collected, C tree freed
|
|
570
|
+
----
|
|
571
|
+
|
|
572
|
+
**Held documents carry the Ruby wrapper layer.** A fully walked,
|
|
573
|
+
held document costs roughly +552 kB of wrapper objects on top of
|
|
574
|
+
the C tree's +456 kB (about 1.57x Nokogiri for the same held
|
|
575
|
+
shape) — the measured cost of the FFI-only, no-compile-at-install
|
|
576
|
+
architecture: every wrapped node is a small Ruby object holding an
|
|
577
|
+
`FFI::Pointer`. Parse-and-discard workloads are unaffected (the
|
|
578
|
+
tree is the dominant cost and the wrappers die young). See
|
|
579
|
+
https://github.com/leptris/leptris-ruby/issues/147[#147] for the
|
|
580
|
+
analysis and the open TypedData options.
|
|
581
|
+
|
|
551
582
|
== Errors
|
|
552
583
|
|
|
553
584
|
All Leptris errors descend from `Leptris::XML::Error`:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 01 — Constraint compliance audit (autoload / send / ivar / respond_to?)
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
## Findings (2026-09-07 scan)
|
|
6
|
+
|
|
7
|
+
- `.send(` in lib: none.
|
|
8
|
+
- `instance_variable_set`/`_get` across objects: none.
|
|
9
|
+
(`instance_variable_defined?(:@readonly_document)` in node.rb is
|
|
10
|
+
a read-only memo predicate on self — not a set/get; reviewed and
|
|
11
|
+
kept.)
|
|
12
|
+
- `require_relative` in lib/spec: none.
|
|
13
|
+
- `respond_to?` type checks — three real violations in lib + one
|
|
14
|
+
in spec (fixed below).
|
|
15
|
+
|
|
16
|
+
## Sanctioned exceptions (documented, load-bearing)
|
|
17
|
+
|
|
18
|
+
- `leptris.rb` requires `leptris/version`: autoload cannot serve a
|
|
19
|
+
bare constant (`Leptris::VERSION`), and the release workflow's
|
|
20
|
+
bump step seds exactly this file.
|
|
21
|
+
- `leptris.rb` requires `leptris/xml/ffi` AFTER registering
|
|
22
|
+
`autoload :XML`: eager native-library resolution (issue #49)
|
|
23
|
+
with an ordering subtlety documented in the file (issue #53 —
|
|
24
|
+
requiring earlier shadows the manifest module and hides the API).
|
|
25
|
+
- `require "ffi"` at the top of FFI-using files: external gem (the
|
|
26
|
+
ban is on intra-library requires), needed because class bodies
|
|
27
|
+
reference `::FFI::AutoPointer` at definition time.
|
|
28
|
+
|
|
29
|
+
## Fixes
|
|
30
|
+
|
|
31
|
+
- [x] `Document.parse`: `respond_to?(:read)` duck check → `is_a?(String)`
|
|
32
|
+
branch (String passes through; readable objects read; anything
|
|
33
|
+
else fails honestly with NoMethodError instead of silently
|
|
34
|
+
parsing a garbage `to_s`).
|
|
35
|
+
- [x] `Iterparse.parse`: same replacement.
|
|
36
|
+
- [x] `SAX::Parser#parse`: lambda-guard `respond_to?(:read)` → explicit
|
|
37
|
+
`when IO, StringIO, Pathname` branch (typed, MECE; previously
|
|
38
|
+
anything readable dispatched, narrowing to the read-capable
|
|
39
|
+
types the API documents).
|
|
40
|
+
- [x] `features_namespace_spec.rb`: dropped the `respond_to?` null-guard.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 02 — Deep-copy seam: one authority for "copy as a new document's root"
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
The create-document → element_copy → wrap → attach-as-root sequence
|
|
6
|
+
was duplicated in three places (`Node#dup`, `Element#dup`,
|
|
7
|
+
`Serialization.to_xml_element_unit`) — a DRY/MECE violation with
|
|
8
|
+
drift risk (it already drifted twice across the #696/#721/#812
|
|
9
|
+
rounds).
|
|
10
|
+
|
|
11
|
+
## Change
|
|
12
|
+
|
|
13
|
+
- [x] `Leptris::XML::Document.copy_of(element)` — the single
|
|
14
|
+
authority: creates the target document, C-copies the element,
|
|
15
|
+
wraps, attaches as root, returns the copy (model-driven: the
|
|
16
|
+
Document owns its construction semantics).
|
|
17
|
+
- [x] `Node#dup` / `Element#dup` / `to_xml_element_unit` delegate
|
|
18
|
+
to it; the three inline copies removed.
|
|
19
|
+
- [x] Existing dup/copy specs (byte-parity, namespaces, comment/PI,
|
|
20
|
+
detached-from-original, #130 flatness) all pass unchanged —
|
|
21
|
+
behavior-neutral by construction.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 03 — Evaluation-context seam for the expression faces
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
`XPath#eval` and `XQuery#eval` each hand-rolled "resolve a Document
|
|
6
|
+
or Element receiver into (owning document, context-node pointer)"
|
|
7
|
+
with their own argument errors — duplicated logic, drift-prone as
|
|
8
|
+
faces multiply (XPath / XQuery / future).
|
|
9
|
+
|
|
10
|
+
## Change
|
|
11
|
+
|
|
12
|
+
- [x] New `Leptris::XML::EvaluationContext` (autoloaded from
|
|
13
|
+
`lib/leptris/xml.rb`, per the autoload rule): a value object
|
|
14
|
+
with `.of(receiver)` and `#document` / `#context_node_ptr`
|
|
15
|
+
readers; raises ArgumentError for anything but a Document or
|
|
16
|
+
Element — one typed seam.
|
|
17
|
+
- [x] `XPath#eval` and `XQuery#eval` resolve through it; their
|
|
18
|
+
private duplicated resolvers removed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 04 — Spec MECE: one domain per file
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
The lockstep rounds accreted specs into whichever file was open:
|
|
6
|
+
`xquery_spec.rb` carried HTML-parsing and pure-XPath blocks;
|
|
7
|
+
`document_spec.rb` carried the HTML suite. Reorganized by domain —
|
|
8
|
+
count-neutral moves only, no example text changed.
|
|
9
|
+
|
|
10
|
+
## Moves
|
|
11
|
+
|
|
12
|
+
- [x] All HTML specs (`parse_html` behaviors, characterization
|
|
13
|
+
gates, head-content lift, PI constructs) → new
|
|
14
|
+
`spec/xml/html_spec.rb`.
|
|
15
|
+
- [x] Pure-`#xpath` blocks (XPath 3.1 value-level surface, 2.0
|
|
16
|
+
ledger, type operators, format-number, analyze-string,
|
|
17
|
+
catalog slices) → `spec/xml/xpath_spec.rb`.
|
|
18
|
+
- [x] `xquery_spec.rb` now contains only the XQuery face;
|
|
19
|
+
`document_spec.rb` only Document behaviors.
|
|
20
|
+
- [x] Total example count unchanged before/after (audit gate).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 05 — README: Memory section (#147 resolution, option A)
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
- [x] Document the two findings from leptris-ruby#147:
|
|
6
|
+
finalizer semantics (objects stay `uncollectible` until the
|
|
7
|
+
async finalizer queue drains — retention tests must yield
|
|
8
|
+
wall-clock time, not just `GC.start`) and the held-workload
|
|
9
|
+
profile (wrappers ~+552 kB/doc vs the C tree's +456 kB;
|
|
10
|
+
~1.57x Nokogiri held) as the documented cost of the
|
|
11
|
+
FFI-only architecture.
|
|
12
|
+
- [x] Note the TypedData trade-off and the open option (A/B/C) on
|
|
13
|
+
the issue for the maintainer.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 06 — CLAUDE.md architecture map refresh
|
|
2
|
+
|
|
3
|
+
Status: DONE
|
|
4
|
+
|
|
5
|
+
The map predated XQuery, ResultText, HTML parsing, the XSLT/XPath
|
|
6
|
+
compiled faces, and the copy/evaluation-context seams.
|
|
7
|
+
|
|
8
|
+
- [x] File map updated (xquery.rb, result_text.rb, xslt.rb,
|
|
9
|
+
xpath.rb, evaluation_context.rb, Document.copy_of seam).
|
|
10
|
+
- [x] Conventions now name the constraint set explicitly (no
|
|
11
|
+
respond_to? type checks in lib; send/ivar rules were already
|
|
12
|
+
there).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 07 — External gates (blocked outside this repo; tracked, not completable here)
|
|
2
|
+
|
|
3
|
+
Status: TRACKED
|
|
4
|
+
|
|
5
|
+
- [ ] **leptris/leptris#875 re-adoption**: when the fix releases,
|
|
6
|
+
re-pin to `{C}`, un-pend the sentinel spec, re-run the
|
|
7
|
+
96/97/120 boundary + the 120-template perf fixture as the
|
|
8
|
+
release gate, ship `{C}.0`. (1.9.93.0/1.9.93.1 yanked;
|
|
9
|
+
1.9.92.1 is the standing replacement.)
|
|
10
|
+
- [ ] **utf8proc build decision** (owner): `fn:normalize-unicode`
|
|
11
|
+
requires utf8proc, compiled out of the vendored platform
|
|
12
|
+
builds — enabling it changes the vendored binary surface.
|
|
13
|
+
Pending spec documents it.
|
|
14
|
+
- [ ] **#147 option A/B/C** (owner): A (document — DONE via 05),
|
|
15
|
+
B (FFI+native `leptris-typed` variant gem — roadmap), C
|
|
16
|
+
(revisit if ruby-ffi grows TypedData).
|
|
17
|
+
- [ ] **#683 remainder** (upstream): 3.1 string templates, XQuery
|
|
18
|
+
3.1 territory.
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -58,7 +58,7 @@ class Leptris::XML::Document
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
|
|
61
|
-
xml = xml_or_io.
|
|
61
|
+
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
62
62
|
if xml.empty?
|
|
63
63
|
raise Leptris::XML::ParseError, "empty input"
|
|
64
64
|
end
|
|
@@ -119,6 +119,18 @@ class Leptris::XML::Document
|
|
|
119
119
|
wrap(raw)
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
# Deep copy of +element+ as the root of a NEW document — the one
|
|
123
|
+
# authority behind Node#dup / Element#dup and the indent-unit
|
|
124
|
+
# path (the C copy keeps every child kind and namespace since
|
|
125
|
+
# libleptris 1.9.76 — #696/#721/#812). The returned tree is
|
|
126
|
+
# detached from the original and fully navigable.
|
|
127
|
+
def self.copy_of(element)
|
|
128
|
+
new_doc = create
|
|
129
|
+
copy = Leptris::XML::FFI.leptris_element_copy(element.c_ptr, new_doc.c_ptr)
|
|
130
|
+
raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
|
|
131
|
+
new_doc.root = Leptris::XML::Node.wrap(copy, new_doc)
|
|
132
|
+
end
|
|
133
|
+
|
|
122
134
|
# Convert a raw LeptrisDocument pointer into a Ruby Document with safe
|
|
123
135
|
# GC lifetime management. The finalizer captures the raw address
|
|
124
136
|
# integer (not the Document or Pointer object — those would prevent
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -251,15 +251,12 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
251
251
|
self
|
|
252
252
|
end
|
|
253
253
|
|
|
254
|
-
# Deep copy in a NEW document via the
|
|
255
|
-
#
|
|
256
|
-
#
|
|
254
|
+
# Deep copy in a NEW document via Document.copy_of (the single
|
|
255
|
+
# copy seam — every child kind and namespace survives,
|
|
256
|
+
# #696/#721/#812).
|
|
257
257
|
def dup
|
|
258
258
|
ensure_alive!
|
|
259
|
-
|
|
260
|
-
copy = Leptris::XML::FFI.leptris_element_copy(@c_ptr, new_doc.c_ptr)
|
|
261
|
-
raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
|
|
262
|
-
new_doc.root = Leptris::XML::Node.wrap(copy, new_doc)
|
|
259
|
+
Leptris::XML::Document.copy_of(self)
|
|
263
260
|
end
|
|
264
261
|
alias_method :clone, :dup
|
|
265
262
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# Resolves an expression-face receiver into its evaluation context:
|
|
6
|
+
# the OWNING document (lifetime + memoization scope) and the
|
|
7
|
+
# context-node pointer (nil at document level). The single typed
|
|
8
|
+
# seam behind XPath#eval and XQuery#eval — one authority, one
|
|
9
|
+
# ArgumentError shape, as the expression faces multiply.
|
|
10
|
+
#
|
|
11
|
+
# context = Leptris::XML::EvaluationContext.of(node)
|
|
12
|
+
# context.document # => Leptris::XML::Document
|
|
13
|
+
# context.context_node_ptr # => FFI::Pointer or nil
|
|
14
|
+
#
|
|
15
|
+
class Leptris::XML::EvaluationContext
|
|
16
|
+
def self.of(receiver)
|
|
17
|
+
case receiver
|
|
18
|
+
when Leptris::XML::Document then new(receiver, nil)
|
|
19
|
+
when Leptris::XML::Element then new(receiver.document, receiver.c_ptr)
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"expected a Leptris::XML::Document or Element, got #{receiver.class}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(document, context_node_ptr)
|
|
27
|
+
@document = document
|
|
28
|
+
@context_node_ptr = context_node_ptr
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :document, :context_node_ptr
|
|
32
|
+
end
|
|
@@ -41,7 +41,7 @@ class Leptris::XML::Iterparse
|
|
|
41
41
|
# iterator starts — the bounded-memory path is parse_file (C-side
|
|
42
42
|
# file streaming). The C API takes one (xml, len) buffer.
|
|
43
43
|
def self.parse(xml_or_io, mode: :top_level, &block)
|
|
44
|
-
xml = xml_or_io.
|
|
44
|
+
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
45
45
|
iterator = new(Leptris::XML::FFI.leptris_iterparse_new_ex(
|
|
46
46
|
xml, xml.bytesize, mode_code(mode)))
|
|
47
47
|
return iterator unless block
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -374,20 +374,14 @@ class Leptris::XML::Node
|
|
|
374
374
|
result
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
-
# Deep copy in a NEW document via the
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
# (#721) with the 1.9.74 descendant-declaration regression fixed
|
|
381
|
-
# in 1.9.76 (#812). Perf: the detached copier is pool-threaded —
|
|
382
|
-
# upstream measures ~2.3x Nokogiri per subtree.
|
|
377
|
+
# Deep copy in a NEW document via Document.copy_of (the single
|
|
378
|
+
# copy seam — comment/PI children and namespaces both survive,
|
|
379
|
+
# #696/#721/#812; the copier is pool-threaded, ~2.3x Nokogiri).
|
|
383
380
|
def dup
|
|
384
381
|
ensure_alive!
|
|
385
382
|
elem_ptr = Leptris::XML::FFI.leptris_node_as_element(@c_ptr)
|
|
386
383
|
raise Leptris::XML::Error, "dup is only supported for element nodes" if elem_ptr.null?
|
|
387
|
-
|
|
388
|
-
copy = Leptris::XML::FFI.leptris_element_copy(elem_ptr, new_doc.c_ptr)
|
|
389
|
-
raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
|
|
390
|
-
new_doc.root = Leptris::XML::Node.wrap(copy, new_doc)
|
|
384
|
+
Leptris::XML::Document.copy_of(self)
|
|
391
385
|
end
|
|
392
386
|
alias_method :clone, :dup
|
|
393
387
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "ffi"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "stringio"
|
|
4
6
|
|
|
5
7
|
class Leptris::XML::SAX::Parser
|
|
6
8
|
CHUNK_SIZE = 4096
|
|
@@ -42,7 +44,7 @@ class Leptris::XML::SAX::Parser
|
|
|
42
44
|
def parse(input)
|
|
43
45
|
case input
|
|
44
46
|
when String then parse_memory(input)
|
|
45
|
-
when
|
|
47
|
+
when IO, StringIO, Pathname then parse_io(input)
|
|
46
48
|
else
|
|
47
49
|
raise ArgumentError, "SAX parser expects a String or IO, got #{input.class}"
|
|
48
50
|
end
|
|
@@ -115,17 +115,13 @@ module Leptris::XML::Serialization
|
|
|
115
115
|
|
|
116
116
|
# Element-face indent unit (leptris-ruby#109 residual 2): no
|
|
117
117
|
# element-level ext-serialize entry exists yet, so the unit path
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# the unit and every child kind intact.
|
|
118
|
+
# copies the subtree into a fresh document via Document.copy_of
|
|
119
|
+
# (the single copy seam) and serializes that document without a
|
|
120
|
+
# declaration — identical output to the element serializer for
|
|
121
|
+
# the standard layout, with the unit and every child kind intact.
|
|
123
122
|
def self.to_xml_element_unit(element, unit, indent: 0)
|
|
124
|
-
document = Leptris::XML::Document.
|
|
123
|
+
document = Leptris::XML::Document.copy_of(element).document
|
|
125
124
|
begin
|
|
126
|
-
copy = Leptris::XML::FFI.leptris_element_copy(element.c_ptr, document.c_ptr)
|
|
127
|
-
raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
|
|
128
|
-
document.root = Leptris::XML::Node.wrap(copy, document)
|
|
129
125
|
to_xml_indent_unit(document.c_ptr, unit,
|
|
130
126
|
indent: indent, no_decl: true)
|
|
131
127
|
ensure
|
data/lib/leptris/xml/xpath.rb
CHANGED
|
@@ -36,17 +36,17 @@ class Leptris::XML::XPath
|
|
|
36
36
|
# namespace bindings ("prefix" => uri) routes the evaluation through
|
|
37
37
|
# the namespace-bound path, matching Searchable#xpath semantics.
|
|
38
38
|
def eval(doc_or_element, ns = nil)
|
|
39
|
-
context =
|
|
39
|
+
context = Leptris::XML::EvaluationContext.of(doc_or_element)
|
|
40
40
|
document = context.document
|
|
41
41
|
result_ptr =
|
|
42
42
|
if ns && !ns.empty?
|
|
43
43
|
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
44
44
|
Leptris::XML::FFI.leptris_xpath_compiled_eval_ns(
|
|
45
|
-
@handle, document.c_ptr,
|
|
45
|
+
@handle, document.c_ptr, context.context_node_ptr, set)
|
|
46
46
|
end
|
|
47
47
|
else
|
|
48
48
|
Leptris::XML::FFI.leptris_xpath_compiled_eval(
|
|
49
|
-
@handle, document.c_ptr,
|
|
49
|
+
@handle, document.c_ptr, context.context_node_ptr)
|
|
50
50
|
end
|
|
51
51
|
if result_ptr.null?
|
|
52
52
|
raise Leptris::XML::XPathError,
|
|
@@ -54,20 +54,4 @@ class Leptris::XML::XPath
|
|
|
54
54
|
end
|
|
55
55
|
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
56
56
|
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
def resolve_context(doc_or_element)
|
|
61
|
-
case doc_or_element
|
|
62
|
-
when Leptris::XML::Document then doc_or_element
|
|
63
|
-
when Leptris::XML::Element then doc_or_element
|
|
64
|
-
else
|
|
65
|
-
raise ArgumentError,
|
|
66
|
-
"expected a Leptris::XML::Document or Element, got #{doc_or_element.class}"
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def context_ptr(context)
|
|
71
|
-
context.is_a?(Leptris::XML::Document) ? nil : context.c_ptr
|
|
72
|
-
end
|
|
73
57
|
end
|
data/lib/leptris/xml/xquery.rb
CHANGED
|
@@ -47,23 +47,13 @@ class Leptris::XML::XQuery
|
|
|
47
47
|
# an aggregate until the engine materializes readable sequence
|
|
48
48
|
# items (same caveat as the XPath for-return subset).
|
|
49
49
|
def eval(doc_or_element)
|
|
50
|
-
|
|
51
|
-
when Leptris::XML::Document
|
|
52
|
-
document = doc_or_element
|
|
53
|
-
context = nil
|
|
54
|
-
when Leptris::XML::Element
|
|
55
|
-
document = doc_or_element.document
|
|
56
|
-
context = doc_or_element.c_ptr
|
|
57
|
-
else
|
|
58
|
-
raise ArgumentError,
|
|
59
|
-
"expected a Leptris::XML::Document or Element, got #{doc_or_element.class}"
|
|
60
|
-
end
|
|
50
|
+
context = Leptris::XML::EvaluationContext.of(doc_or_element)
|
|
61
51
|
result_ptr = Leptris::XML::FFI.leptris_xquery_eval(
|
|
62
|
-
@handle, document.c_ptr, context)
|
|
52
|
+
@handle, context.document.c_ptr, context.context_node_ptr)
|
|
63
53
|
if result_ptr.null?
|
|
64
54
|
raise Leptris::XML::XPathError,
|
|
65
55
|
Leptris::XML::FFI.leptris_last_error.to_s
|
|
66
56
|
end
|
|
67
|
-
Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
|
|
57
|
+
Leptris::XML::Searchable.wrap_xpath_result(context.document, result_ptr)
|
|
68
58
|
end
|
|
69
59
|
end
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Leptris
|
|
|
22
22
|
autoload :Serialization, "leptris/xml/serialization"
|
|
23
23
|
autoload :SAX, "leptris/xml/sax"
|
|
24
24
|
autoload :XPath, "leptris/xml/xpath"
|
|
25
|
+
autoload :EvaluationContext, "leptris/xml/evaluation_context"
|
|
25
26
|
autoload :ResultText, "leptris/xml/result_text"
|
|
26
27
|
autoload :XSLT, "leptris/xml/xslt"
|
|
27
28
|
autoload :XQuery, "leptris/xml/xquery"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.92.
|
|
4
|
+
version: 1.9.92.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -69,6 +69,13 @@ files:
|
|
|
69
69
|
- LICENSE.md
|
|
70
70
|
- README.adoc
|
|
71
71
|
- Rakefile
|
|
72
|
+
- TODO.restructure/01-constraint-compliance-audit.md
|
|
73
|
+
- TODO.restructure/02-deep-copy-seam.md
|
|
74
|
+
- TODO.restructure/03-evaluation-context-seam.md
|
|
75
|
+
- TODO.restructure/04-spec-mece-restructure.md
|
|
76
|
+
- TODO.restructure/05-memory-documentation.md
|
|
77
|
+
- TODO.restructure/06-architecture-map-refresh.md
|
|
78
|
+
- TODO.restructure/07-external-gates.md
|
|
72
79
|
- docs/adr/0001-lockstep-mirror.md
|
|
73
80
|
- docs/adr/0002-utf8-at-the-seam.md
|
|
74
81
|
- docs/adr/0003-readonly-memoization-pattern.md
|
|
@@ -88,6 +95,7 @@ files:
|
|
|
88
95
|
- lib/leptris/xml/document.rb
|
|
89
96
|
- lib/leptris/xml/document_fragment.rb
|
|
90
97
|
- lib/leptris/xml/element.rb
|
|
98
|
+
- lib/leptris/xml/evaluation_context.rb
|
|
91
99
|
- lib/leptris/xml/ffi.rb
|
|
92
100
|
- lib/leptris/xml/iterparse.rb
|
|
93
101
|
- lib/leptris/xml/namespace.rb
|