moxml 0.5.55 → 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: ded5e928e12262125965a33ed378e35d8ecc85c3e65de4d514a1fefe0ddd9458
4
- data.tar.gz: b86296c3c0402915fc455696b9bd76b7892a08f58ec8fa9d8ac043fcd36784f4
3
+ metadata.gz: d4db82d82671fd5cc6189294198abf6d019cd9a719dd25eff901227ebd6c411e
4
+ data.tar.gz: 34611277972cb9687a3b1350ee4a9d5d3036c432fb2ed89409492a2bae812821
5
5
  SHA512:
6
- metadata.gz: 8a29c038b4e5290a47acab859cf99796e9b653eb991f12e34539871e3dc8f02a6d3e2c66b312db64d87488a308bbb93f365dd5e4294e75fa329a5bfcb77bb174
7
- data.tar.gz: ddfecce2232d70546ed2ac42c9ba5eb000e1d98cee79a00047f0a2927fcb47e40b27b4b35fb282f8086b805428062e664a3652673195e67a6fa7596907579063
6
+ metadata.gz: 22ee802575e1c85d3f2a03981ed0fce5ed24e23576d005bbf08861e06051026b23276977a8cdedc3340709ea617fcd8a01cade368f74083d4a05728b6dd5f9f0
7
+ data.tar.gz: 43b6109e181270728559005b64e7e8b728a38403b2230f2c868b125def432ceddc5e9e1e468ad6afebcb2a073c9d5e9f69da48acae2499218dbe7c12a81a61aa
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gemspec
9
9
  gem "benchmark"
10
10
  gem "benchmark-ips"
11
11
  gem "get_process_mem"
12
- gem "leptris", "~> 1.9.174.1"
12
+ gem "leptris", "~> 1.9.174"
13
13
  gem "libxml-ruby", ">= 5.0"
14
14
  gem "nokogiri", "~> 1.18"
15
15
  gem "openssl", "~> 3.0"
@@ -164,6 +164,14 @@ module Moxml
164
164
  nil
165
165
  end
166
166
 
167
+ # Source position {line, col_start, col_end} for a node where
168
+ # the engine exposes it (leptris 1.9.181+ source_position);
169
+ # nil elsewhere and for nodes without a position (created
170
+ # nodes answer zeros upstream — pass those through as-is).
171
+ def source_position(_native)
172
+ nil
173
+ end
174
+
167
175
  # Subtree walk capability: an adapter with a C-side pre-order
168
176
  # traversal (leptris >= 1.9.174.6 visit) walks descendants in
169
177
  # one dispatch; nil keeps the recursive children walk. Self
@@ -103,6 +103,12 @@ module Moxml
103
103
  NN_ADD_CHILD = NN.instance_method(:add_child)
104
104
  end
105
105
 
106
+ # 1.9.181 (lockstep): Node#source_position — {line, col_start,
107
+ # col_end} from the engine's source-position API (upstream
108
+ # #1124); created nodes report zeros.
109
+ NATIVE_SOURCE_POSITION =
110
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.181")
111
+
106
112
  # Entity-reference preservation (leptris-ruby#212 / upstream
107
113
  # #1094): 1.9.177 ships ParseOptions.keep_entity_refs plus
108
114
  # first-class EntityReference nodes — the marker machinery
@@ -592,7 +598,21 @@ module Moxml
592
598
  # entity-marker splitting applies.
593
599
  def parse_fragment(xml, _context = nil)
594
600
  doc = parse("<m>#{xml}</m>")
595
- 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
596
616
  end
597
617
 
598
618
  def iterparse(xml, mode = :top_level, _context = nil, &block)
@@ -788,6 +808,10 @@ module Moxml
788
808
 
789
809
  element = node
790
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.
791
815
  # Nil-clear contract (issue #164): undeclare the default
792
816
  # namespace (xmlns="") and drop any name prefix — the
793
817
  # element then reports no namespace, matching the other
@@ -1238,6 +1262,12 @@ module Moxml
1238
1262
  r
1239
1263
  end
1240
1264
 
1265
+ def source_position(node)
1266
+ return nil unless NATIVE_SOURCE_POSITION
1267
+
1268
+ to_binding(node).source_position
1269
+ end
1270
+
1241
1271
  def line_number(node)
1242
1272
  if NATIVE_MUTATIONS_COHERENT &&
1243
1273
  node.is_a?(::Leptris::XML::NativeNode)
@@ -1347,7 +1377,17 @@ module Moxml
1347
1377
  node.document.add_pi(new_node.target, new_node.content.to_s)
1348
1378
  return new_node
1349
1379
  end
1350
- 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
1351
1391
  end
1352
1392
 
1353
1393
  def add_next_sibling(node, new_node)
@@ -1355,7 +1395,31 @@ module Moxml
1355
1395
  node = to_binding(node)
1356
1396
  new_node = to_binding(new_node)
1357
1397
  end
1358
- 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)
1359
1423
  end
1360
1424
 
1361
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/node.rb CHANGED
@@ -402,6 +402,14 @@ module Moxml
402
402
  adapter.line_number(@native)
403
403
  end
404
404
 
405
+ # {line, col_start, col_end} where the engine exposes source
406
+ # positions (leptris 1.9.181+); nil elsewhere. Created nodes
407
+ # answer zeros upstream — distinguishable from nil by callers
408
+ # that care.
409
+ def source_position
410
+ adapter.source_position(@native)
411
+ end
412
+
405
413
  # Content-defined Merkle digest of this subtree (issue #173,
406
414
  # companion to leptris#869): a u64 Integer where the backend
407
415
  # computes one, nil everywhere else. Consumers gate on nil and
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.55"
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)
@@ -694,6 +727,25 @@ RSpec.describe Moxml::Adapter::Leptris do
694
727
  end
695
728
  end
696
729
 
730
+ describe "source_position (leptris 1.9.181+, upstream #1124)" do
731
+ let(:ctx) { Moxml.new(:leptris) }
732
+
733
+ it "answers engine source positions for parsed nodes" do
734
+ skip "requires leptris 1.9.181" unless described_class::NATIVE_SOURCE_POSITION
735
+
736
+ doc = ctx.parse("<root>\n <child>text</child>\n</root>")
737
+ expect(doc.root.source_position).to include(line: 1)
738
+ expect(doc.root.source_position).to be_a(Hash)
739
+ end
740
+
741
+ it "answers nil below the gate and on other adapters" do
742
+ expect(Moxml::Adapter::Base.source_position(nil)).to be_nil
743
+ expect(Moxml::Adapter::Nokogiri.source_position(nil)).to be_nil
744
+ noko = Moxml.new(:nokogiri).parse("<root><a/></root>")
745
+ expect(noko.root.children.first.source_position).to be_nil
746
+ end
747
+ end
748
+
697
749
  describe "subtree digest (issue #173, leptris#869)" do
698
750
  let(:ctx) { Moxml.new(:leptris) }
699
751
 
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.55
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