moxml 0.5.56 → 0.5.57

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: d4db82d82671fd5cc6189294198abf6d019cd9a719dd25eff901227ebd6c411e
4
+ data.tar.gz: 34611277972cb9687a3b1350ee4a9d5d3036c432fb2ed89409492a2bae812821
5
5
  SHA512:
6
- metadata.gz: 580fe3a426ebf5d37017fa727527518a76e2221c34543e58c22a08c41d92300e23dbc8bfc391d7f39b09ffd9b78921b1434dc2a51154af4e507e2be409935e08
7
- data.tar.gz: 15cbb5eb0ca6795715aea82f2b05e017c6d5726c0b6e814713955aa4da0c7e045b21afd9304b7fd1bfdc1cc685f9b45ee743d80f5e06025770218ae383d76060
6
+ metadata.gz: 22ee802575e1c85d3f2a03981ed0fce5ed24e23576d005bbf08861e06051026b23276977a8cdedc3340709ea617fcd8a01cade368f74083d4a05728b6dd5f9f0
7
+ data.tar.gz: 43b6109e181270728559005b64e7e8b728a38403b2230f2c868b125def432ceddc5e9e1e468ad6afebcb2a073c9d5e9f69da48acae2499218dbe7c12a81a61aa
@@ -598,7 +598,21 @@ module Moxml
598
598
  # entity-marker splitting applies.
599
599
  def parse_fragment(xml, _context = nil)
600
600
  doc = parse("<m>#{xml}</m>")
601
- children(doc.native.root)
601
+ # Lifetime seam (#245): the fragment's natives live in the
602
+ # parse document's C tree, and the binding Document is the
603
+ # sole owner with a GC finalizer. The parent_node chain
604
+ # (fragment -> synthetic <m> -> Document wrapper) keeps the
605
+ # owning wrappers strongly reachable from every returned
606
+ # node; the wrappers are minted HERE and returned as-is —
607
+ # re-wrapping at the caller would race the context map's
608
+ # weak values and silently drop the chain.
609
+ root_wrapper = doc.root
610
+ root_wrapper.parent_node = doc
611
+ children(root_wrapper.native).map do |child|
612
+ wrapped = Node.wrap(child, doc.context)
613
+ wrapped.parent_node = root_wrapper
614
+ wrapped
615
+ end
602
616
  end
603
617
 
604
618
  def iterparse(xml, mode = :top_level, _context = nil, &block)
@@ -794,6 +808,10 @@ module Moxml
794
808
 
795
809
  element = node
796
810
  if namespace.nil?
811
+ # Supported un-prefix (#245, relaton migration): rename
812
+ # the node to its local part (name = local) — the
813
+ # serializer re-attaches the old prefix after namespace=
814
+ # nil on prefixed elements until leptris-ruby#244 lands.
797
815
  # Nil-clear contract (issue #164): undeclare the default
798
816
  # namespace (xmlns="") and drop any name prefix — the
799
817
  # element then reports no namespace, matching the other
@@ -1359,7 +1377,17 @@ module Moxml
1359
1377
  node.document.add_pi(new_node.target, new_node.content.to_s)
1360
1378
  return new_node
1361
1379
  end
1362
- node.add_previous_sibling(new_node)
1380
+ return node.add_previous_sibling(new_node) if node.is_a?(::Leptris::XML::Element)
1381
+
1382
+ # Non-element receivers: raw-FFI anchor (issue #245, same
1383
+ # shape as add_next_sibling).
1384
+ adopt_for_sibling_insert(new_node, node)
1385
+ ::Leptris::XML::FFI.check_status(
1386
+ ::Leptris::XML::FFI.leptris_element_insert_before(
1387
+ node.c_ptr, new_node.c_ptr
1388
+ ),
1389
+ )
1390
+ new_node
1363
1391
  end
1364
1392
 
1365
1393
  def add_next_sibling(node, new_node)
@@ -1367,7 +1395,31 @@ module Moxml
1367
1395
  node = to_binding(node)
1368
1396
  new_node = to_binding(new_node)
1369
1397
  end
1370
- node.add_next_sibling(new_node)
1398
+ return node.add_next_sibling(new_node) if node.is_a?(::Leptris::XML::Element)
1399
+
1400
+ # Non-element receivers (Text/Comment/CDATA): the binding's
1401
+ # method is Element-only, but the C insert anchors on any
1402
+ # node — route through the FFI entry directly, keeping the
1403
+ # binding's namespace-lift adoption for element inserts
1404
+ # (issue #245).
1405
+ adopt_for_sibling_insert(new_node, node)
1406
+ ::Leptris::XML::FFI.check_status(
1407
+ ::Leptris::XML::FFI.leptris_element_insert_after(
1408
+ node.c_ptr, new_node.c_ptr
1409
+ ),
1410
+ )
1411
+ new_node
1412
+ end
1413
+
1414
+ # The binding's add_*_ sibling methods lift an adopted
1415
+ # element's namespace declarations into the target scope;
1416
+ # the raw-FFI path above must preserve that.
1417
+ def adopt_for_sibling_insert(new_node, anchor)
1418
+ return unless new_node.is_a?(::Leptris::XML::Element)
1419
+ return if ::Leptris::XML::Element.skip_adoption_lift?(new_node)
1420
+
1421
+ scope = anchor.parent.is_a?(::Leptris::XML::Element) ? anchor.parent.namespaces : {}
1422
+ ::Leptris::XML::Element.lift_namespaces_for_adoption(new_node, scope)
1371
1423
  end
1372
1424
 
1373
1425
  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/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.57"
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)
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.57
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