moxml 0.5.153 → 0.5.155

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: 7f599e1c38d471ff887c23b2cccfe367a280dbbe3d70213a74dcb0ec1debe9da
4
- data.tar.gz: c6d8c03cac7308e5be8e6068e5ac57da8e65c76d367b5c46711c263a5500bbc8
3
+ metadata.gz: c6118a2cde5308a843923649c4d115a89c39f99a74e5cf5b0804c7c69ffe17ce
4
+ data.tar.gz: 97fd393417a588ed4ffea5c885982ead30553b76679c38edea3a466f8c093e23
5
5
  SHA512:
6
- metadata.gz: 4849176647a258995d7d2a2dfe28908dd0b9efb3fde41a08de624a55d7bc1b06849f26d6fba0ed75797c5111f4ea8c3c42def0c1d7834a6ddec8054ad0bd46f4
7
- data.tar.gz: c18c0615e863e24584066ab8f6b80f81630c574c9c17d30bca561f47211cb10dda22bb4e438f66a92e5ecafbd656621aad0df801ac1ba259913597062910ffb8
6
+ metadata.gz: 4471e50a04a8b3432a56555eb5f45edaecfd20b704dc890d6f89f5c076752f8350fe7c95b8e8c58b9569add68900619561ef685706161b0dee2c4275f2a54c12
7
+ data.tar.gz: 04f39ca1432d7e910485b9305e9fe4aa042890f6dd49b80981bc262846152d1ab8fa5d6ac5a584c000612a491bc3625849ee3ed5c65694d892f462787d007115
@@ -78,11 +78,15 @@ jobs:
78
78
  env:
79
79
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80
80
  run: |
81
- # The released version is the gemspec's at the pinned tree —
82
- # NOT "latest": back-to-back releases would otherwise aim a
83
- # back-aimed rerun at the newer notes.
84
- VERSION=$(ruby -e 'print Gem::Specification.load("moxml.gemspec").version')
85
- TAG="v${VERSION}"
81
+ # The notes target is the release created by the triggering
82
+ # run — NOT "latest" (a rerun of an old run would mis-aim)
83
+ # and NOT the gemspec at the pinned tree (the checkout is
84
+ # the pre-bump tree; the bump lands on main during the
85
+ # release run itself).
86
+ TRIGGERED_AT="${{ github.event.workflow_run.created_at }}"
87
+ TAG=$(gh api "repos/${GITHUB_REPOSITORY}/releases" \
88
+ --jq "map(select(.created_at >= \"${TRIGGERED_AT}\")) | sort_by(.created_at) | last | .tag_name")
89
+ TAG=${TAG:-$(gh release view --json tagName --jq .tagName)}
86
90
  echo "Reporting against $TAG"
87
91
  gh release view "$TAG" --json body --jq .body > /tmp/body.md
88
92
  ruby -e '
@@ -137,3 +137,12 @@ parse (the engine's instruction content is trimmed); leading
137
137
  separator runs collapse per the model. Pinned in the taxonomy
138
138
  differential (`pi trailing whitespace`, face `pi_content`).
139
139
  |===
140
+
141
+ | D-21
142
+ | leptris (bindings before 1.9.360)
143
+ | The binding exposes no `Leptris::XML::Declaration` class, so the
144
+ document-children declaration filter resolves conditionally and is a
145
+ no-op there (declaration nodes cannot occur). On such bindings the
146
+ class is also absent for consumers. Pinned in
147
+ `spec/moxml/adapter/leptris_document_children_older_binding_spec.rb`.
148
+ |===
@@ -14,6 +14,18 @@ module Moxml
14
14
  had_source_declaration document_text
15
15
  ].freeze
16
16
 
17
+ # The XML declaration kind is not stable across leptris
18
+ # releases: 1.9.345-era builds lack Leptris::XML::Declaration
19
+ # entirely (the pure-Ruby CustomizedLeptris twin carries the
20
+ # declaration then), later builds expose it again (and list it
21
+ # among the document children, leptris-ruby#459). Resolve the
22
+ # kind set once, by feature detection, and filter both kinds —
23
+ # the shim twin is never a listing node either way.
24
+ DECLARATION_KINDS = [
25
+ ::Moxml::Adapter::CustomizedLeptris::Declaration,
26
+ (::Leptris::XML::Declaration if ::Leptris::XML.const_defined?(:Declaration)),
27
+ ].compact.freeze
28
+
17
29
  def free_document(native)
18
30
  DOCUMENT_ATTACHMENT_KEYS.each { |key| attachments.delete(native, key) }
19
31
  native.free
@@ -41,7 +53,9 @@ module Moxml
41
53
  # it (the declaration is a preamble part, not a child node)
42
54
  # — filtered at the listing seam.
43
55
  children.concat(
44
- doc.children.to_a.grep_v(::Leptris::XML::Declaration),
56
+ doc.children.to_a.reject do |child|
57
+ DECLARATION_KINDS.any? { |kind| child.is_a?(kind) }
58
+ end,
45
59
  )
46
60
 
47
61
  texts = attachments.get(doc, :document_text)
@@ -167,7 +181,7 @@ module Moxml
167
181
  # emission (a second line here duplicates it — and the
168
182
  # 1.9.360 0-arity Declaration#to_xml raises under the
169
183
  # options-passing call).
170
- next if child.is_a?(::Leptris::XML::Declaration)
184
+ next if DECLARATION_KINDS.any? { |kind| child.is_a?(kind) }
171
185
 
172
186
  parts << raw_serialize(child, options) << "\n"
173
187
  end
@@ -749,7 +749,16 @@ module Moxml
749
749
  @children = nil
750
750
  @children_gen = generation
751
751
  end
752
- @children ||= if doc_entity_bearing?
752
+ @children ||= if doc_entity_bearing? ||
753
+ !::Moxml::Adapter::Leptris
754
+ .children_read_splice_safe?
755
+ # Splice-unsafe builds (1.9.381.0,
756
+ # leptris-ruby#545): the cached face's
757
+ # child list no longer reflects
758
+ # splices — the adapter crossing is
759
+ # the correctness path, and the
760
+ # no-crossing contract is probed off
761
+ # for those builds.
753
762
  super
754
763
  else
755
764
  NodeSet.new(children_read, ctx, self,
@@ -1276,6 +1285,29 @@ module Moxml
1276
1285
  end
1277
1286
  end
1278
1287
 
1288
+ # 1.9.381.0: the children_read face stopped reflecting
1289
+ # post-splice state — Element#add_next_sibling lands the node
1290
+ # in the tree (native bulk reads and walk_columnar see it) but
1291
+ # leaves the face's child list stale. Probe the exact repro
1292
+ # once: prime the face, splice, and require children_read to
1293
+ # see the inserted node. Unsafe builds take the adapter
1294
+ # children path; fixed builds restore the fast path
1295
+ # automatically.
1296
+ def children_read_splice_safe?
1297
+ return @children_read_splice_safe unless @children_read_splice_safe.nil?
1298
+
1299
+ @children_read_splice_safe = begin
1300
+ native_doc = ::Leptris::XML::Document.parse("<r><a/>\n<b/></r>")
1301
+ root = native_doc.root
1302
+ root.children_read.to_a # prime the face's child list
1303
+ a = root.children.to_a.first
1304
+ a.add_next_sibling(native_doc.create_element("x"))
1305
+ root.children_read.to_a.map { |n| n.name.to_s } == %w[a x b]
1306
+ rescue StandardError
1307
+ false
1308
+ end
1309
+ end
1310
+
1279
1311
  def xpath_index_document(node)
1280
1312
  return node if node.is_a?(::Leptris::XML::Document)
1281
1313
 
@@ -2719,6 +2751,7 @@ module Moxml
2719
2751
  doc = NN_DOCUMENT.bind_call(parent_nn)
2720
2752
  src = to_binding(child_nn)
2721
2753
  installed = copy_binding_subtree(doc, src)
2754
+ advance_mutation_version(parent_nn)
2722
2755
  ::Leptris::XML::FFI.check_status(
2723
2756
  ::Leptris::XML::FFI.leptris_element_append_child(
2724
2757
  ::FFI::Pointer.new(parent_nn.address), installed.c_ptr
@@ -2767,6 +2800,7 @@ module Moxml
2767
2800
  end
2768
2801
  # The raw engine add rejects binding-family children —
2769
2802
  # append through the FFI face like structural_adopt.
2803
+ advance_mutation_version(parent_nn)
2770
2804
  ::Leptris::XML::FFI.check_status(
2771
2805
  ::Leptris::XML::FFI.leptris_element_append_child(
2772
2806
  ::FFI::Pointer.new(parent_nn.address), rebuilt.c_ptr
@@ -3090,7 +3124,13 @@ module Moxml
3090
3124
 
3091
3125
  # Non-element receivers: raw-FFI anchor (issue #245, same
3092
3126
  # shape as add_next_sibling).
3127
+ # leptris-ruby#543: the raw-FFI splice is a wrapper-visible
3128
+ # mutation — advance the binding document's version or the
3129
+ # walk-read memos warmed before it serve stale children.
3130
+ node.document&.advance_version if
3131
+ node.document.respond_to?(:advance_version)
3093
3132
  adopt_for_sibling_insert(new_node, node)
3133
+ advance_mutation_version(node)
3094
3134
  ::Leptris::XML::FFI.check_status(
3095
3135
  ::Leptris::XML::FFI.leptris_element_insert_before(
3096
3136
  node.c_ptr, new_node.c_ptr
@@ -3134,7 +3174,13 @@ module Moxml
3134
3174
  # node — route through the FFI entry directly, keeping the
3135
3175
  # binding's namespace-lift adoption for element inserts
3136
3176
  # (issue #245).
3177
+ # leptris-ruby#543: the raw-FFI splice is a wrapper-visible
3178
+ # mutation — advance the binding document's version or the
3179
+ # walk-read memos warmed before it serve stale children.
3180
+ node.document&.advance_version if
3181
+ node.document.respond_to?(:advance_version)
3137
3182
  adopt_for_sibling_insert(new_node, node)
3183
+ advance_mutation_version(node)
3138
3184
  ::Leptris::XML::FFI.check_status(
3139
3185
  ::Leptris::XML::FFI.leptris_element_insert_after(
3140
3186
  node.c_ptr, new_node.c_ptr
@@ -3154,6 +3200,23 @@ module Moxml
3154
3200
  ::Leptris::XML::Element.lift_namespaces_for_adoption(new_node, scope)
3155
3201
  end
3156
3202
 
3203
+ # The binding's version-stamped read memos (the #530
3204
+ # walk-reads, the C content/attribute memos) key their
3205
+ # validity on the owning Document's mutation version. The
3206
+ # gated Ruby faces advance it (Node#ensure_writable!) and
3207
+ # the C-bound faces bump in C — a RAW FFI write bypasses
3208
+ # both, so every raw write in this adapter bumps here,
3209
+ # before the call, mirroring ensure_writable!'s
3210
+ # conservative discipline (a failed write merely discards
3211
+ # memos). Without it, a children memo warmed before the
3212
+ # insert serves the pre-insert tree forever.
3213
+ def advance_mutation_version(node)
3214
+ doc = node.respond_to?(:document) ? node.document : nil
3215
+ doc.advance_version if doc.respond_to?(:advance_version)
3216
+ rescue ::Leptris::XML::Error
3217
+ nil
3218
+ end
3219
+
3157
3220
  def remove(node)
3158
3221
  drop_columnar_children(node)
3159
3222
  node = to_binding(node) if NATIVE_READ_LAYER
@@ -3225,12 +3288,14 @@ module Moxml
3225
3288
  prev = node.previous_sibling
3226
3289
  node.unlink
3227
3290
  if prev.is_a?(::Leptris::XML::Element)
3291
+ advance_mutation_version(prev)
3228
3292
  ::Leptris::XML::FFI.check_status(
3229
3293
  ::Leptris::XML::FFI.leptris_element_insert_after(
3230
3294
  prev.c_ptr, new_node.c_ptr
3231
3295
  ),
3232
3296
  )
3233
3297
  else
3298
+ advance_mutation_version(parent)
3234
3299
  ::Leptris::XML::FFI.check_status(
3235
3300
  ::Leptris::XML::FFI.leptris_element_append_child(
3236
3301
  parent.c_ptr, new_node.c_ptr
@@ -3645,8 +3710,8 @@ module Moxml
3645
3710
  # nil and leaves the pointer — fall through to the scalar
3646
3711
  # wrap, whose free owns it.
3647
3712
  if BULK_XPATH_ROWS &&
3648
- ::Leptris::XML::FFI.leptris_xpath_result_type(result_ptr) ==
3649
- ::Leptris::XML::FFI::XPATH_NODESET
3713
+ ::Leptris::XML::FFI.leptris_xpath_result_type(result_ptr) ==
3714
+ ::Leptris::XML::FFI::XPATH_NODESET
3650
3715
  # The one-crossing mint is deferred: a caller that only
3651
3716
  # asks #size never pays for the rows (#527). The holder
3652
3717
  # runs Document#xpath_result_rows_bulk on first read.
@@ -37,10 +37,10 @@ module Moxml
37
37
  materialize[index]
38
38
  end
39
39
 
40
- def each
41
- return enum_for(:each) unless block_given?
40
+ def each(&block)
41
+ return enum_for(:each) unless block
42
42
 
43
- materialize.each { |row| yield row }
43
+ materialize.each(&block)
44
44
  self
45
45
  end
46
46
 
@@ -51,7 +51,7 @@ module Moxml
51
51
  def self.finalizer(address)
52
52
  proc do
53
53
  ::Leptris::XML::FFI.leptris_xpath_result_free(
54
- ::FFI::Pointer.new(address)
54
+ ::FFI::Pointer.new(address),
55
55
  )
56
56
  end
57
57
  end
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.153"
4
+ VERSION = "0.5.155"
5
5
  end
@@ -136,6 +136,9 @@ RSpec.describe "leptris Identity contract reads" do
136
136
  unless Leptris::XML::NativeNode.method_defined?(:children_read)
137
137
  skip "binding #{Leptris::VERSION} has no children_read face"
138
138
  end
139
+ unless Moxml::Adapter::Leptris.children_read_splice_safe?
140
+ skip "binding #{Leptris::VERSION} lacks the splice-safe children_read face (leptris-ruby#545) — the crossing is the correctness path there"
141
+ end
139
142
  doc = ctx.parse("<root><item>v</item></root>")
140
143
  el = doc.root.children.first
141
144
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # The document-children listing filters the declaration node, but on
6
+ # leptris releases that predate the Declaration class entirely
7
+ # (< 1.9.360) referencing the constant raised NameError on every
8
+ # document-children enumeration — the first `doc.children` on any
9
+ # document. The filter must resolve the constant conditionally.
10
+ RSpec.describe "leptris document children on older leptris" do
11
+ let(:ctx) { Moxml.new(:leptris) }
12
+
13
+ it "lists document children when the release has no Declaration class" do
14
+ leptris_xml = Leptris::XML
15
+ had_declaration = leptris_xml.const_defined?(:Declaration, false)
16
+ declaration = had_declaration ? leptris_xml.const_get(:Declaration) : nil
17
+ # stub_const cannot express a MISSING constant; simulating the
18
+ # older binding requires the constant to be absent
19
+ if had_declaration
20
+ leptris_xml.send(:remove_const, :Declaration) # rubocop:disable RSpec/RemoveConst
21
+ end
22
+
23
+ begin
24
+ doc = ctx.create_document
25
+ doc.add_child(doc.create_element("root"))
26
+ expect(doc.children.map(&:name)).to eq(%w[root])
27
+ ensure
28
+ leptris_xml.const_set(:Declaration, declaration) if had_declaration
29
+ end
30
+ end
31
+
32
+ it "keeps filtering the declaration on releases that have the class" do
33
+ skip "binding #{Leptris::VERSION} has no Declaration class (D-21)" unless
34
+ Leptris::XML.const_defined?(:Declaration, false)
35
+
36
+ doc = ctx.parse(%(<?xml version="1.0" encoding="UTF-8"?>\n<documents></documents>))
37
+ expect(doc.children.map(&:node_type)).to eq(%i[element])
38
+ end
39
+ end
@@ -490,6 +490,12 @@ RSpec.describe Moxml::Adapter::Leptris do
490
490
  # The identity map must hold wrappers weakly: parse-and-drop
491
491
  # workloads otherwise pin wrappers, natives, and (via the
492
492
  # binding finalizer never running) the C subtrees.
493
+ # Splice-unsafe builds (leptris-ruby#545) walk through the
494
+ # adapter crossing, whose wrappers ride the strong registry —
495
+ # the weak-release contract is probed off there.
496
+ unless described_class.children_read_splice_safe?
497
+ skip "binding #{Leptris::VERSION} requires a splice-safe children_read face (leptris-ruby#545)"
498
+ end
493
499
  xml = %(<r>#{Array.new(50) { |i| "<e id=\"i#{i}\">x</e>" }.join}</r>)
494
500
  ctx = Moxml.new(:leptris)
495
501
  count = -> {
@@ -72,12 +72,12 @@ RSpec.describe "leptris xpath bulk rows routing" do
72
72
  # leptris-ruby#527: the count row is one evaluation plus a size
73
73
  # read — it must not pay the per-row mint.
74
74
  describe "row materialization is deferred" do
75
- def bulk_calls
75
+ def bulk_calls(&block)
76
76
  calls = 0
77
77
  tp = TracePoint.new(:c_call) do |t|
78
78
  calls += 1 if t.method_id == :xpath_result_rows_bulk
79
79
  end
80
- tp.enable { yield }
80
+ tp.enable(&block)
81
81
  calls
82
82
  end
83
83
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.153
4
+ version: 0.5.155
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-22 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Moxml is a unified XML manipulation library that provides a common API
@@ -486,6 +486,7 @@ files:
486
486
  - spec/moxml/adapter/leptris_contract_reads_spec.rb
487
487
  - spec/moxml/adapter/leptris_detached_text_spec.rb
488
488
  - spec/moxml/adapter/leptris_doc_parts_spec.rb
489
+ - spec/moxml/adapter/leptris_document_children_older_binding_spec.rb
489
490
  - spec/moxml/adapter/leptris_document_declaration_spec.rb
490
491
  - spec/moxml/adapter/leptris_inbody_comment_spec.rb
491
492
  - spec/moxml/adapter/leptris_parse_lint_spec.rb