moxml 0.5.56 → 0.5.58

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: 30ad2e1e2f91d4d31eb467d5a2beee85bc3112585d6383bf37b0a66e6d1bca40
4
- data.tar.gz: ff7ef79362aa97205ce0b16c7161a9c5d7f25720a3794a967cfb9dbf4afa3a5e
3
+ metadata.gz: 03fd3940f1334b3f417e4847f35e87497581404372209de4426819a8acc6d363
4
+ data.tar.gz: 805f6966495456ff6e36c4f1be3df9fcbbf1c89cfcf2ddc6a4153c37e1bcec71
5
5
  SHA512:
6
- metadata.gz: 580fe3a426ebf5d37017fa727527518a76e2221c34543e58c22a08c41d92300e23dbc8bfc391d7f39b09ffd9b78921b1434dc2a51154af4e507e2be409935e08
7
- data.tar.gz: 15cbb5eb0ca6795715aea82f2b05e017c6d5726c0b6e814713955aa4da0c7e045b21afd9304b7fd1bfdc1cc685f9b45ee743d80f5e06025770218ae383d76060
6
+ metadata.gz: 872ba59e6f8b72779d2ea43c8af201052a69be9d75ad1add3fa2618bc58e81cee0503ce352d06c8b46c64cc6340859006b36afeadef4302f29c81e2760285baa
7
+ data.tar.gz: 01eed98fc6ac76efb35d2456c9af57487bbdddf13092a09b0667d11e6e2d48a1905865046aab6a59ca8a89523fc4337a705c1b1cf95d98342d56cb4494c0d91e
@@ -246,6 +246,31 @@ module Moxml
246
246
  NATIVE_MUTATIONS_COHERENT &&
247
247
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.4")
248
248
 
249
+ # leptris-ruby#246 klass-propagating reads: the read
250
+ # family (children/element_children/next_sibling/parent)
251
+ # mints the mapped subclass per kind, so children arrive
252
+ # already carrying the contract — wrap_native's is_a?
253
+ # guard short-circuits and the per-visit from() mint
254
+ # (the second allocation of the pair) drops out. Kind
255
+ # order is the C layer's node-type ints: element, text,
256
+ # comment, cdata, pi.
257
+ NATIVE_KLASS_CHILDREN =
258
+ NATIVE_IDENTITY &&
259
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.2")
260
+
261
+ if NATIVE_KLASS_CHILDREN
262
+ kind_classes = [
263
+ Identity::ELEMENT,
264
+ Identity::TEXT,
265
+ Identity::COMMENT,
266
+ Identity::CDATA,
267
+ Identity::PROCESSING_INSTRUCTION,
268
+ ]
269
+ kind_classes.each do |klass|
270
+ klass.install_child_klasses(kind_classes)
271
+ end
272
+ end
273
+
249
274
  def wrap_native(node, type, _context)
250
275
  return nil unless NATIVE_IDENTITY &&
251
276
  node.is_a?(::Leptris::XML::NativeNode)
@@ -598,7 +623,21 @@ module Moxml
598
623
  # entity-marker splitting applies.
599
624
  def parse_fragment(xml, _context = nil)
600
625
  doc = parse("<m>#{xml}</m>")
601
- children(doc.native.root)
626
+ # Lifetime seam (#245): the fragment's natives live in the
627
+ # parse document's C tree, and the binding Document is the
628
+ # sole owner with a GC finalizer. The parent_node chain
629
+ # (fragment -> synthetic <m> -> Document wrapper) keeps the
630
+ # owning wrappers strongly reachable from every returned
631
+ # node; the wrappers are minted HERE and returned as-is —
632
+ # re-wrapping at the caller would race the context map's
633
+ # weak values and silently drop the chain.
634
+ root_wrapper = doc.root
635
+ root_wrapper.parent_node = doc
636
+ children(root_wrapper.native).map do |child|
637
+ wrapped = Node.wrap(child, doc.context)
638
+ wrapped.parent_node = root_wrapper
639
+ wrapped
640
+ end
602
641
  end
603
642
 
604
643
  def iterparse(xml, mode = :top_level, _context = nil, &block)
@@ -794,6 +833,10 @@ module Moxml
794
833
 
795
834
  element = node
796
835
  if namespace.nil?
836
+ # Supported un-prefix (#245, relaton migration): rename
837
+ # the node to its local part (name = local) — the
838
+ # serializer re-attaches the old prefix after namespace=
839
+ # nil on prefixed elements until leptris-ruby#244 lands.
797
840
  # Nil-clear contract (issue #164): undeclare the default
798
841
  # namespace (xmlns="") and drop any name prefix — the
799
842
  # element then reports no namespace, matching the other
@@ -1359,7 +1402,17 @@ module Moxml
1359
1402
  node.document.add_pi(new_node.target, new_node.content.to_s)
1360
1403
  return new_node
1361
1404
  end
1362
- node.add_previous_sibling(new_node)
1405
+ return node.add_previous_sibling(new_node) if node.is_a?(::Leptris::XML::Element)
1406
+
1407
+ # Non-element receivers: raw-FFI anchor (issue #245, same
1408
+ # shape as add_next_sibling).
1409
+ adopt_for_sibling_insert(new_node, node)
1410
+ ::Leptris::XML::FFI.check_status(
1411
+ ::Leptris::XML::FFI.leptris_element_insert_before(
1412
+ node.c_ptr, new_node.c_ptr
1413
+ ),
1414
+ )
1415
+ new_node
1363
1416
  end
1364
1417
 
1365
1418
  def add_next_sibling(node, new_node)
@@ -1367,7 +1420,31 @@ module Moxml
1367
1420
  node = to_binding(node)
1368
1421
  new_node = to_binding(new_node)
1369
1422
  end
1370
- node.add_next_sibling(new_node)
1423
+ return node.add_next_sibling(new_node) if node.is_a?(::Leptris::XML::Element)
1424
+
1425
+ # Non-element receivers (Text/Comment/CDATA): the binding's
1426
+ # method is Element-only, but the C insert anchors on any
1427
+ # node — route through the FFI entry directly, keeping the
1428
+ # binding's namespace-lift adoption for element inserts
1429
+ # (issue #245).
1430
+ adopt_for_sibling_insert(new_node, node)
1431
+ ::Leptris::XML::FFI.check_status(
1432
+ ::Leptris::XML::FFI.leptris_element_insert_after(
1433
+ node.c_ptr, new_node.c_ptr
1434
+ ),
1435
+ )
1436
+ new_node
1437
+ end
1438
+
1439
+ # The binding's add_*_ sibling methods lift an adopted
1440
+ # element's namespace declarations into the target scope;
1441
+ # the raw-FFI path above must preserve that.
1442
+ def adopt_for_sibling_insert(new_node, anchor)
1443
+ return unless new_node.is_a?(::Leptris::XML::Element)
1444
+ return if ::Leptris::XML::Element.skip_adoption_lift?(new_node)
1445
+
1446
+ scope = anchor.parent.is_a?(::Leptris::XML::Element) ? anchor.parent.namespaces : {}
1447
+ ::Leptris::XML::Element.lift_namespaces_for_adoption(new_node, scope)
1371
1448
  end
1372
1449
 
1373
1450
  def remove(node)
data/lib/moxml/context.rb CHANGED
@@ -124,8 +124,11 @@ module Moxml
124
124
  # are the result either way.
125
125
  def parse_fragment(xml)
126
126
  adapter = config.adapter
127
- adapter.parse_fragment(xml, self).map do |native|
128
- Moxml::Node.wrap(native, self)
127
+ adapter.parse_fragment(xml, self).map do |node|
128
+ # Adapters that tie a parent_node lifetime chain (#245)
129
+ # return wrapped nodes — pass them through untouched;
130
+ # re-wrapping would race the weak wrapper map.
131
+ node.is_a?(Moxml::Node) ? node : Moxml::Node.wrap(node, self)
129
132
  end
130
133
  end
131
134
 
data/lib/moxml/node.rb CHANGED
@@ -593,10 +593,16 @@ module Moxml
593
593
 
594
594
  # Extend-in-place (issue #230): adapters whose natives can
595
595
  # carry the contract modules directly (leptris TypedData) mint
596
- # the native itself as the wrapper — @native is self.
596
+ # the native itself as the wrapper — @native is self. The
597
+ # minted wrapper primes with ITSELF (not the input native):
598
+ # adapter calls then receive the contract-bearing receiver,
599
+ # which klass-propagating reads (#246) dispatch on — and the
600
+ # input native keeps its registration so later wraps of the
601
+ # same base object hit the cache instead of re-minting.
597
602
  if (extended = adapter.wrap_native(node, type, context))
598
- extended.prime_contract(node, context, adapter, type)
603
+ extended.prime_contract(extended, context, adapter, type)
599
604
  context.register_wrapper(node, extended)
605
+ context.register_wrapper(extended, extended) unless extended.equal?(node)
600
606
  return extended
601
607
  end
602
608
 
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.56"
4
+ VERSION = "0.5.58"
5
5
  end
@@ -672,6 +672,39 @@ RSpec.describe Moxml::Adapter::Leptris do
672
672
  end
673
673
  end
674
674
 
675
+ describe "adapter seams from the relaton migration (issue #245)" do
676
+ let(:ctx) { Moxml.new(:leptris) }
677
+
678
+ it "keeps parse_fragment nodes' owning document alive" do
679
+ nodes = ctx.parse_fragment("<em>x</em>")
680
+ element = nodes.first
681
+ expect(element.parent_node).not_to be_nil
682
+ GC.start
683
+ GC.start
684
+ expect(element.name).to eq("em")
685
+ expect(element.text).to eq("x")
686
+ document = element.document
687
+ expect(document.children.first.name).to eq("m")
688
+ expect(document.children.first.children.first.name).to eq("em")
689
+ end
690
+
691
+ it "inserts siblings after a text node" do
692
+ doc = ctx.parse("<root><p>a<!--c1-->b</p></root>")
693
+ paragraph = doc.root.children.first
694
+ cursor = paragraph.children.first
695
+ cursor.add_next_sibling(doc.create_text("Z"))
696
+ expect(paragraph.to_xml).to eq("<p>aZ<!--c1-->b</p>")
697
+ end
698
+
699
+ it "inserts siblings before a text node" do
700
+ doc = ctx.parse("<root><p><!--c1-->ab</p></root>")
701
+ paragraph = doc.root.children.first
702
+ cursor = paragraph.children.last
703
+ cursor.add_previous_sibling(doc.create_text("Y"))
704
+ expect(paragraph.to_xml).to eq("<p><!--c1-->Yab</p>")
705
+ end
706
+ end
707
+
675
708
  describe "to_binding is defined regardless of the native layer (issue #217)" do
676
709
  it "is the identity for binding nodes" do
677
710
  ctx = Moxml.new(:leptris)
@@ -765,4 +798,32 @@ RSpec.describe Moxml::Adapter::Leptris do
765
798
  expect(pi[2]).to be_a(Integer)
766
799
  end
767
800
  end
801
+
802
+ describe "klass-propagating children" do
803
+ let(:ctx) { Moxml.new(:leptris) }
804
+
805
+ it "mints contract-bearing children in C" do
806
+ skip "requires klass-propagating reads (leptris 1.9.193.2+)" unless described_class.singleton_class.const_get(:NATIVE_KLASS_CHILDREN)
807
+
808
+ doc = ctx.parse(%(<r><e a="1"/>t</r>))
809
+ kids = doc.root.children
810
+ expect(kids.first).to be_a(Moxml::Element)
811
+ expect(kids.last).to be_a(Moxml::Text)
812
+ expect(kids.first["a"]).to eq("1")
813
+ expect(kids.last.text).to eq("t")
814
+ # The C mint IS the wrapper — @native is self, no re-mint.
815
+ expect(kids.first.native).to equal(kids.first)
816
+ end
817
+
818
+ it "keeps identity across access paths" do
819
+ skip "requires klass-propagating reads (leptris 1.9.193.2+)" unless described_class.singleton_class.const_get(:NATIVE_KLASS_CHILDREN)
820
+
821
+ doc = ctx.parse(%(<r><a/>t</r>))
822
+ first = doc.root.children.first
823
+ expect(doc.root.element_children.first).to equal(first)
824
+ expect(first.parent.children.first).to equal(first)
825
+ expect(first.next_sibling.text).to eq("t")
826
+ expect(first.next_sibling.parent).to eq(doc.root)
827
+ end
828
+ end
768
829
  end
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.56
4
+ version: 0.5.58
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-09-16 00:00:00.000000000 Z
11
+ date: 2026-09-17 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