moxml 0.5.147 → 0.5.148

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: 2e5797a825682b992d84b5ab9155d74f81fdeca24293b40ad7b9e58860c407f0
4
- data.tar.gz: 11accb2da9da7cf656d28ab7e128da698f1702120332d94d06b4a20ea39ad112
3
+ metadata.gz: 1b51347e90c3dae3f4b0b41268b4ee8cd92fa91a4210a17b1ea75da85db0f82a
4
+ data.tar.gz: d251705fd512baed6295dad6a56c12e7db531dfa4845b2f77983c9c305bfaffd
5
5
  SHA512:
6
- metadata.gz: 2b8980eb872815020ed2b6d582172ffd62c08a426c2fa2104bd891cd5e2f1e69392fd82d9573215383399087cb686852c47fb10e1d93e812c1641f8eb5e4eeef
7
- data.tar.gz: 924b76844411af051e0a9b02ea55788dbc4f5d0e6ec0be31ade4ded2c17c6c6e58356f687ef42e2d7934a4a8f684720f77b0d7688e108437aa64c68a41b68d06
6
+ metadata.gz: 8ffcc8a80a304b656005d171627c40a982ba1b1594bbd2cb9090f390150f799b69041e338a54d2e79245a3fdf526639406938f4fdd82b290f55092c43ac20f34
7
+ data.tar.gz: 764dd4cd2fa5346a701224b7841ff0fb7cfced542b68ad914f4200acda168c5060813cb737429a5bcfd67eca12e74870b8bc4449bc83214678f5c86beaf3aa8c
@@ -105,6 +105,9 @@ module Moxml
105
105
  ::Leptris::XML::Document.method(:parse)
106
106
  .parameters.any? { |_, key| key == :strict }
107
107
 
108
+ ADD_CHILD_NATIVE =
109
+ NATIVE_READ_LAYER && NN.method_defined?(:add_child_native)
110
+
108
111
  if NATIVE_READ_LAYER
109
112
  NN_ATTRIBUTE = NN.instance_method(:attribute)
110
113
  NN_NAME = NN.instance_method(:name)
@@ -624,6 +627,31 @@ module Moxml
624
627
  end
625
628
  TEXT.include(TextContractReads)
626
629
  end
630
+
631
+ # leptris-ruby#507: children_read joins the alias set. The
632
+ # generation-checked memo STAYS — re-walks of the same
633
+ # document are the common harness/consumer shape and the
634
+ # memo serves them allocation-free (dropping it regressed
635
+ # the walk 2x in GC). Only the MISS gets cheaper: the
636
+ # klass-children face plain-dispatches and returns the
637
+ # already-minted wrappers, so the adapter crossing
638
+ # (bind_call + to_a + keep-refs probe) drops out, and
639
+ # entity-marker documents take super for the split.
640
+ if NN.method_defined?(:children_read)
641
+ module ChildReads
642
+ def children
643
+ @children = nil if @children_gen != context.children_generation
644
+ @children_gen = context.children_generation
645
+ @children ||= if doc_entity_bearing?
646
+ super
647
+ else
648
+ NodeSet.new(children_read, context, self)
649
+ end
650
+ end
651
+ end
652
+ ELEMENT.include(ChildReads)
653
+ TEXT.include(ChildReads)
654
+ end
627
655
  end
628
656
 
629
657
  # leptris-ruby#246 klass-propagating reads: the read
@@ -1464,6 +1492,14 @@ module Moxml
1464
1492
  count = ffi.leptris_document_parse_diag_count(doc)
1465
1493
  return [] if count.zero?
1466
1494
 
1495
+ # leptris#1771: one face carries the diag's machine-readable
1496
+ # code plus line/column with the entry (nil when the engine
1497
+ # has no token code — pre-#1771 records). The face probe
1498
+ # keeps older bindings on kind+message (issue #408).
1499
+ lint_code = if ffi.respond_to?(:leptris_document_parse_diag_code)
1500
+ ffi.method(:leptris_document_parse_diag_code)
1501
+ end
1502
+
1467
1503
  kind_ptr = ::FFI::MemoryPointer.new(:int)
1468
1504
  diagnostics = []
1469
1505
  count.times do |i|
@@ -1473,7 +1509,19 @@ module Moxml
1473
1509
  ) == 1
1474
1510
 
1475
1511
  kind = DIAG_KINDS[kind_ptr.read_int] || :unknown
1476
- diagnostics << { kind: kind, message: buffer.read_string }
1512
+ diag = { kind: kind, message: buffer.read_string }
1513
+ if lint_code
1514
+ code_buf = ::FFI::MemoryPointer.new(:char, 128)
1515
+ line_ptr = ::FFI::MemoryPointer.new(:int)
1516
+ column_ptr = ::FFI::MemoryPointer.new(:int)
1517
+ lint_code.call(doc, i, code_buf, 128, line_ptr, column_ptr)
1518
+ code = code_buf.read_string
1519
+ diag[:code] = code.empty? ? nil : code
1520
+ diag[:line] = line_ptr.read_int.zero? ? nil : line_ptr.read_int
1521
+ diag[:column] =
1522
+ column_ptr.read_int.zero? ? nil : column_ptr.read_int
1523
+ end
1524
+ diagnostics << diag
1477
1525
  end
1478
1526
  diagnostics
1479
1527
  end
@@ -2496,6 +2544,27 @@ module Moxml
2496
2544
  end
2497
2545
 
2498
2546
  def add_child(parent, child)
2547
+ # Same-document Identity append — the builder hot path. One
2548
+ # document pair decides the verdict: the pin, the absorb
2549
+ # checks and the binding bridges only exist for
2550
+ # cross-document work, and the plain C append (version bump
2551
+ # inside) is the whole job. bind_call, not dispatch — the
2552
+ # contract modules shadow add_child on Identity receivers
2553
+ # (moxml#2560 note below).
2554
+ if NATIVE_MUTATIONS_COHERENT && parent.is_a?(NN) && child.is_a?(NN) &&
2555
+ !child.is_a?(CustomizedLeptris::EntityReference)
2556
+ parent_doc = NN_DOCUMENT.bind_call(parent)
2557
+ if parent_doc
2558
+ child_doc = NN_DOCUMENT.bind_call(child)
2559
+ if child_doc && parent_doc.equal?(child_doc)
2560
+ # add_child_native (leptris-ruby#509) plain-dispatches
2561
+ # on Identity receivers — one frame, no bind_call.
2562
+ return parent.add_child_native(child) if ADD_CHILD_NATIVE
2563
+
2564
+ return NN_ADD_CHILD.bind_call(parent, child)
2565
+ end
2566
+ end
2567
+ end
2499
2568
  # 1.9.311: binding-family children (created PIs, texts...) —
2500
2569
  # a DIRECT absorb makes the binding add below move the node
2501
2570
  # by reference. A source already owned elsewhere would need
@@ -163,11 +163,21 @@ module Moxml
163
163
  end
164
164
 
165
165
  def create_element(name)
166
- Wrappers::Element.new(adapter.create_element(name, owner_doc: @native), context)
166
+ native = adapter.create_element(name, owner_doc: @native)
167
+ # leptris-ruby#508 mints registered klasses at create: the node
168
+ # IS a contract-carrying identity (@native is self) — prime it
169
+ # in place instead of shelling a second wrapper (a raw mint
170
+ # reaching ==/adapter dies on the nil @context).
171
+ return native.prime_contract(native, context, adapter) if native.is_a?(Moxml::Node)
172
+
173
+ Wrappers::Element.new(native, context)
167
174
  end
168
175
 
169
176
  def create_text(content)
170
- Wrappers::Text.new(adapter.create_text(content, owner_doc: @native), context)
177
+ native = adapter.create_text(content, owner_doc: @native)
178
+ return native.prime_contract(native, context, adapter) if native.is_a?(Moxml::Node)
179
+
180
+ Wrappers::Text.new(native, context)
171
181
  end
172
182
 
173
183
  def create_cdata(content)
@@ -227,9 +237,13 @@ module Moxml
227
237
  raise Error, "Document already has a root element"
228
238
  else
229
239
  adapter.add_child(@native, node.native)
230
- # Refresh native for adapters where identity changes (e.g., LibXML doc.root=)
240
+ # Refresh native for adapters where identity changes (e.g., LibXML doc.root=).
241
+ # Native equality via the adapter, not wrapper !=: re-mints can
242
+ # arrive without context primed (1.9.373 actual_native), and
243
+ # wrapper == dispatches through the adapter (issue #409-class).
231
244
  refreshed = adapter.actual_native(node.native, @native)
232
- node.refresh_native!(refreshed) if refreshed && refreshed != node.native
245
+ node.refresh_native!(refreshed) if refreshed &&
246
+ !adapter.same_node?(refreshed, node.native)
233
247
  end
234
248
  node.parent_node = self
235
249
  invalidate_children_cache!
data/lib/moxml/node.rb CHANGED
@@ -847,6 +847,18 @@ module Moxml
847
847
  def self.wrap_with(node, context, adapter)
848
848
  return nil if node.nil?
849
849
 
850
+ # Identity natives ARE the canonical wrapper (leptris C-mint):
851
+ # @native is self, so canonicalization, the cache probes and
852
+ # the re-mint below all answer the receiver. One native ivar
853
+ # read decides; shells (@native != self) take the full path.
854
+ # The C mint leaves the node un-primed (@context unset) — prime
855
+ # on first wrap. Conditionally: an already-primed identity (a
856
+ # walked node) must not lose @parent_node to a re-prime.
857
+ if node.is_a?(Node) && node.instance_variable_get(:@native).equal?(node)
858
+ node.prime_contract(node, context, adapter) unless node.instance_variable_defined?(:@context)
859
+ return node
860
+ end
861
+
850
862
  # One C node may have several Ruby representations (leptris
851
863
  # native/binding duality, moxml#311); every wrap resolves to
852
864
  # the canonical one so memo holders share one invalidation
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.147"
4
+ VERSION = "0.5.148"
5
5
  end
@@ -130,4 +130,33 @@ RSpec.describe "leptris Identity contract reads" do
130
130
  expect(el.text).to eq("v")
131
131
  end
132
132
  end
133
+
134
+ describe "children" do
135
+ it "answers without the adapter children crossing (children_read)" do
136
+ unless Leptris::XML::NativeNode.method_defined?(:children_read)
137
+ skip "binding #{Leptris::VERSION} has no children_read face"
138
+ end
139
+ doc = ctx.parse("<root><item>v</item></root>")
140
+ el = doc.root.children.first
141
+
142
+ singleton = Moxml::Adapter::Leptris.singleton_class
143
+ singleton.send(:alias_method, :children_without_walk, :children)
144
+ singleton.send(:define_method, :children) { |*| raise "adapter children crossing" }
145
+ begin
146
+ kids = el.children
147
+ expect(kids.to_a.map(&:name)).to eq(["text"])
148
+ ensure
149
+ singleton.send(:alias_method, :children, :children_without_walk)
150
+ singleton.send(:remove_method, :children_without_walk)
151
+ end
152
+ end
153
+
154
+ it "returns a NodeSet carrying the contract wrappers" do
155
+ doc = ctx.parse("<root><item>v</item></root>")
156
+ kids = doc.root.children.first.children
157
+ expect(kids).to be_a(Moxml::NodeSet)
158
+ expect(kids.to_a.first.name).to eq("text")
159
+ expect(kids.to_a.first.content).to eq("v")
160
+ end
161
+ end
133
162
  end
@@ -10,13 +10,20 @@ require "spec_helper"
10
10
  RSpec.describe "detached text content= names the cause" do
11
11
  let(:ctx) { Moxml.new(:leptris) }
12
12
 
13
- it "raises a named Moxml error for a removed text node" do
13
+ it "answers removed text nodes: mutable on fixed engines (1.9.374+), named error before" do
14
14
  doc = ctx.parse("<r><a>x</a><b>y</b></r>")
15
15
  text = doc.root.children[0].children[0]
16
16
  text.remove
17
17
 
18
- expect { text.content = "z" }
19
- .to raise_error(Moxml::Error, /detached \(parentless\).*issue #401/)
18
+ if Gem::Version.new(Leptris::VERSION) >= Gem::Version.new("1.9.374")
19
+ # leptris/leptris#1785 fixed the engine root cause: detached
20
+ # text resolves its owning document, so the mutation succeeds.
21
+ text.content = "z"
22
+ expect(text.content).to eq("z")
23
+ else
24
+ expect { text.content = "z" }
25
+ .to raise_error(Moxml::Error, /detached \(parentless\).*issue #401/)
26
+ end
20
27
  end
21
28
 
22
29
  it "keeps created-unattached nodes mutable" do
@@ -23,6 +23,18 @@ RSpec.describe "parse_html collect_errors forwarding" do
23
23
  expect(diags.first).to have_key(:message)
24
24
  end
25
25
 
26
+ # Issue #408: the #1771 reader fields ride the diag hash — the
27
+ # token code and position canon's HtmlValidator maps onto
28
+ # validation errors (nil where the engine has no token code).
29
+ it "carries the token code and position (issue #408)" do
30
+ doc = ctx.parse_html(src, collect_errors: true)
31
+ eof = doc.parse_diagnostics.find { |d| d[:message].include?("EOF") }
32
+ expect(eof).not_to be_nil
33
+ expect(eof[:code]).to eq("eof-in-tag")
34
+ expect(eof[:line]).to eq(1)
35
+ expect(eof[:column]).to eq(1)
36
+ end
37
+
26
38
  it "keeps the default lane silent (no option, no diagnostics)" do
27
39
  doc = ctx.parse_html(src)
28
40
  expect(doc.parse_diagnostics).to be_empty
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.147
4
+ version: 0.5.148
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-19 00:00:00.000000000 Z
11
+ date: 2026-10-20 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