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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22a6c5c9f6c1936f8326f382bb7af70943d45573f5dd5f2dcf8f01715dda219f
4
- data.tar.gz: 3027ff550a0d310c5030705447d1d6765571ee23f9a9b480cfa6e726461e629c
3
+ metadata.gz: 56235008b014f2ccf89a5713d7e95f6cf358a828be7aefb5923a651065980d18
4
+ data.tar.gz: 3d0d3c43d5f2a05fc3c21c0efbeadd33e308d4deaa330fe9b2e8530672944d40
5
5
  SHA512:
6
- metadata.gz: 83515167b1d41a688a9923a2442325087b28f0aad3aa82d767e0ce100c83fbe9c6a76c59a3bc71244830b3a8247fe251dcd5169b7250954cfa8e6711caed79df
7
- data.tar.gz: deacd256531c9509174349a61bf5a272061549967a94cb7cc3eaead403923f5c5e1ec4804a05082dfa1c0619bf99f0e709a5c96c819b6a2ade9016309297e21f
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 / parse_file
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
- status_message)
47
+ serialize_into_string, scratch buffers)
48
48
  lib/leptris/xml/document.rb — the only C-memory owner (finalizer);
49
- factories, PI accessors, exslt, last_error
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/mutation surface
54
- lib/leptris/xml/node_set.rb lazy XPath results; batch fetch via
55
- get_nodes_ex
56
- lib/leptris/xml/searchable.rb xpath/css/at_*; namespace-bound path
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/sax/ callback SAX
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` inside `lib/`.
85
- - No `instance_variable_set`/`_get` across objects; no `respond_to?`
86
- type checks; specs use real documents, never doubles.
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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.92.1"
4
+ VERSION = "1.9.92.2"
5
5
  end
@@ -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.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
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
@@ -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 C copy, attached as the
255
- # fresh document's root (#696 1.9.39, #721 1.9.47, #812 1.9.76 —
256
- # every child kind and namespace survives, pool-threaded).
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
- new_doc = Leptris::XML::Document.create
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.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
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
@@ -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 C copy, attached as the
378
- # fresh document's root. Both copy gaps are closed for good:
379
- # comment/PI children in 1.9.39 (#696), namespaces in 1.9.47
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
- new_doc = Leptris::XML::Document.create
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 ->(x) { x.respond_to?(:read) } then parse_io(input)
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
- # C-copies the subtree into a fresh document (every child kind
119
- # and namespace survive since 1.9.76 #696/#721/#812) and
120
- # serializes that document without a declaration identical
121
- # output to the element serializer for the standard layout, with
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.create
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
@@ -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 = resolve_context(doc_or_element)
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, context_ptr(context), set)
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, context_ptr(context))
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
@@ -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
- case doc_or_element
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.1
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