moxml 0.5.41 → 0.5.43

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: d96e7e77957533fdb65727e5d244d038a6947b30a46563463b995114e4594faf
4
- data.tar.gz: 11940d59f145433f37e0a5ea286055cf6d30167614291d007ff2d41dc68606a5
3
+ metadata.gz: 0c8fd5df5889694c9d7a3ee99c4caad866ddc40edd13ba46945f55e4e9f77a9b
4
+ data.tar.gz: ce14f6e9199e22576c0c5149e716a672efb9e44ef3a9b68d1b78abad02754a92
5
5
  SHA512:
6
- metadata.gz: 25d5f4a19555bd8802f978a3586d2d4c7fc9770d918fbfee1777203d548c6ed8d0d67fa42fd4dcddc047675f2034482f1323542379fe61193358209a4f8670d0
7
- data.tar.gz: be203488271f744cb405f70a48af237408870c433a7b5922c8cafb1f303b2a4a451d0b0f6c3c80ce249a9fe75ec98904c330ff3a0cda212587fb231aac255f53
6
+ metadata.gz: 93ac1cd541ed454ac89c10179e5e56f919a4cd309ae8f16abe02950ea5a6fedfdb03c53a7a8c23520616cd202a2b8f5384616ac8cd7b9e884d9f127c1d38bd98
7
+ data.tar.gz: ed75757fec960bd181921f9562650f4f3d44f2261fa0eb41a09437906d26581ad07c8b5446d0719a1d26b5308de2e1e25c60d2f0013da6861d144692d53eada4
@@ -293,12 +293,19 @@ scratch there. 1.9.163.2 moved the bulk into C and fixed the
293
293
  truncation — newer bindings use their C path (version-memoized,
294
294
  cheaper than a per-call count+copy).
295
295
 
296
- The native *builder* factories (native_create_element/text) are
297
- deliberately not adopted: native mutations do not advance the
298
- binding's document version, so the binding's memoized reads over
299
- the same tree go stale, and attaching the created natives pays a
300
- per-node `to_binding` bridge that costs more than the factory
301
- saves (create+attach measured 5816 -> 7697ns vs nokogiri 1750).
296
+ The native builder factories (native_create_element/text) are
297
+ adopted from 1.9.163.5, where native mutations became
298
+ version-coherent with the binding (leptris-ruby#204/#208 both
299
+ surfaces' memos drop on mutation). The earlier rejection stale
300
+ binding memos plus a per-attach `to_binding` bridge costing more
301
+ than the factory saved (5816 -> 7697ns vs nokogiri 1750) — no
302
+ longer applies: `add_child` carries a both-native fast path with
303
+ no bridge, and `doc_for` reads the 1.9.163.5 `NativeNode#document`
304
+ accessor instead of the parent climb. create ×2 + attach:
305
+ **4846 -> 2333ns (0.30x -> 0.78x of raw nokogiri)**. Attribute
306
+ writes on native nodes still bridge (the native layer has no
307
+ `[]=`), and the native `attributes` hash (name → String) does not
308
+ fit the attribute-node contract — enumeration stays bridged.
302
309
 
303
310
  Serialize (document, 30KB): passing `encoding: "UTF-8"` when it
304
311
  equals the document's own ran a conversion pass for byte-identical
@@ -65,6 +65,15 @@ module Moxml
65
65
  defined?(::Leptris::XML::NativeNode) &&
66
66
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.2")
67
67
 
68
+ # 1.9.163.5 (leptris-ruby#204/#208): native mutations are
69
+ # version-coherent with the binding (both surfaces' memos
70
+ # drop on mutation) — the builder factories and native
71
+ # add_child are adoptable end-to-end; NativeNode grew a
72
+ # document accessor and line numbers.
73
+ NATIVE_MUTATIONS_COHERENT =
74
+ defined?(::Leptris::XML::NativeNode) &&
75
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.5")
76
+
68
77
  # 1.9.163.2 moved the native bulk children into C
69
78
  # (Native.bulk_children) and fixed the 512 truncation
70
79
  # (leptris-ruby#202). The 162.6-163.1 window carries the
@@ -80,7 +89,17 @@ module Moxml
80
89
  # sites must never depend on the layer having loaded
81
90
  # (issue #217 — binding-only installs crashed on the
82
91
  # unguarded bridges).
92
+ # Class-body level: a bare @ivar inside `class << self` lands
93
+ # on the singleton's singleton, invisible to the methods at
94
+ # call time (same shape as @native_doc_roots below).
95
+ @binding_of = ObjectSpace::WeakMap.new
96
+
83
97
  class << self
98
+ # native -> bridged binding node. The bridge object is
99
+ # identity-stable for the native's lifetime (the binding's
100
+ # per-document wrap cache), so repeat bridges — the write
101
+ # loop on a factory-created element, re-accessed attribute
102
+ # lists — skip the document resolution and pointer wrap.
84
103
  # Binding node for any native: identity for binding nodes
85
104
  # (and on installs without the native layer — the constant
86
105
  # check short-circuits), a Node.wrap over the shared C
@@ -89,11 +108,14 @@ module Moxml
89
108
  return node unless NATIVE_READ_LAYER &&
90
109
  node.is_a?(::Leptris::XML::NativeNode)
91
110
 
111
+ bridged = @binding_of[node]
112
+ return bridged if bridged
113
+
92
114
  doc = doc_for(node)
93
115
  ptr = ::FFI::Pointer.new(node.address)
94
- return ::Leptris::XML::Node.wrap(ptr, doc) if doc
95
-
96
- ::Leptris::XML::Node.wrap(ptr, nil)
116
+ bridged = ::Leptris::XML::Node.wrap(ptr, doc)
117
+ @binding_of[node] = bridged unless bridged.nil?
118
+ bridged
97
119
  end
98
120
  end
99
121
 
@@ -130,6 +152,10 @@ module Moxml
130
152
  # to the root (C-bound reads) and look the doc up in the
131
153
  # root registry. Detached subtrees answer nil.
132
154
  def doc_for(node)
155
+ # The 1.9.163.5 native layer exposes the owning document
156
+ # directly; the root climb serves older bindings.
157
+ return node.document if NATIVE_MUTATIONS_COHERENT
158
+
133
159
  current = node
134
160
  current = current.parent while current&.parent
135
161
  @native_doc_roots[current]
@@ -268,7 +294,7 @@ module Moxml
268
294
  end
269
295
 
270
296
  def set_root(doc, element)
271
- doc.root = element
297
+ to_binding(doc).root = to_binding(element)
272
298
  end
273
299
 
274
300
  def bare_set_qname_safe?
@@ -477,18 +503,24 @@ module Moxml
477
503
  ::Leptris::XML::Document.create
478
504
  end
479
505
 
480
- # The native builder factories (native_create_element/text)
481
- # are deliberately NOT adopted: native mutations do not
482
- # advance the binding's document version, so the binding's
483
- # memoized reads over the same tree go stale; attaching the
484
- # created natives also pays a per-node to_binding bridge
485
- # that costs more than the factory saves (create+attach
486
- # measured 5816 -> 7697ns vs nokogiri 1750).
506
+ # Native builder factories: one C call and a TypedData wrap.
507
+ # Adopted from 1.9.163.5, where native mutations became
508
+ # version-coherent (leptris-ruby#204/#208) the earlier
509
+ # rejection (stale binding memos + per-attach bridge cost)
510
+ # no longer applies: add_child carries a native fast path.
487
511
  def create_native_element(name, owner_doc = nil)
512
+ if NATIVE_MUTATIONS_COHERENT && owner_doc
513
+ return owner_doc.native_create_element(name.to_s)
514
+ end
515
+
488
516
  (owner_doc || create_document).create_element(name.to_s)
489
517
  end
490
518
 
491
519
  def create_native_text(content, owner_doc = nil)
520
+ if NATIVE_MUTATIONS_COHERENT && owner_doc
521
+ return owner_doc.native_create_text(content)
522
+ end
523
+
492
524
  (owner_doc || create_document).create_text_node(content)
493
525
  end
494
526
 
@@ -986,6 +1018,11 @@ module Moxml
986
1018
  end
987
1019
 
988
1020
  def line_number(node)
1021
+ if NATIVE_MUTATIONS_COHERENT &&
1022
+ node.is_a?(::Leptris::XML::NativeNode)
1023
+ line = node.line
1024
+ return line.nil? || line.zero? ? nil : line
1025
+ end
989
1026
  return nil unless node.is_a?(::Leptris::XML::Node)
990
1027
 
991
1028
  line = node.line
@@ -1050,7 +1087,9 @@ module Moxml
1050
1087
  end
1051
1088
 
1052
1089
  def add_child(parent, child)
1053
- if NATIVE_READ_LAYER
1090
+ if NATIVE_READ_LAYER && !(NATIVE_MUTATIONS_COHERENT &&
1091
+ parent.is_a?(::Leptris::XML::NativeNode) &&
1092
+ child.is_a?(::Leptris::XML::NativeNode))
1054
1093
  parent = to_binding(parent)
1055
1094
  child = to_binding(child)
1056
1095
  end
@@ -1070,7 +1109,10 @@ module Moxml
1070
1109
  end
1071
1110
 
1072
1111
  def add_previous_sibling(node, new_node)
1073
- node = to_binding(node) if NATIVE_READ_LAYER
1112
+ if NATIVE_READ_LAYER
1113
+ node = to_binding(node)
1114
+ new_node = to_binding(new_node)
1115
+ end
1074
1116
  # A PI inserted before the root lives at document level in
1075
1117
  # libleptris's model, not in the element tree.
1076
1118
  if new_node.is_a?(::Leptris::XML::ProcessingInstruction) &&
@@ -1082,7 +1124,10 @@ module Moxml
1082
1124
  end
1083
1125
 
1084
1126
  def add_next_sibling(node, new_node)
1085
- node = to_binding(node) if NATIVE_READ_LAYER
1127
+ if NATIVE_READ_LAYER
1128
+ node = to_binding(node)
1129
+ new_node = to_binding(new_node)
1130
+ end
1086
1131
  node.add_next_sibling(new_node)
1087
1132
  end
1088
1133
 
@@ -140,6 +140,7 @@ module Moxml
140
140
  end
141
141
 
142
142
  def set_root(doc, element)
143
+ doc.delete_element(doc.root) if doc.root
143
144
  doc.add_element(element)
144
145
  end
145
146
 
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.41"
4
+ VERSION = "0.5.43"
5
5
  end
@@ -143,4 +143,38 @@ RSpec.shared_examples "Moxml Integration" do
143
143
  )
144
144
  end
145
145
  end
146
+
147
+ describe "document root assignment" do
148
+ it "attaches a factory-created element as the root and serializes" do
149
+ doc = context.create_document
150
+ root = doc.create_element("root")
151
+ root["id"] = "1"
152
+ child = doc.create_element("child")
153
+ child.text = "data"
154
+ root.add_child(child)
155
+
156
+ doc.root = root
157
+
158
+ expect(doc.root.name).to eq("root")
159
+ expect(doc.root["id"]).to eq("1")
160
+ expect(doc.root.parent).to eq(doc)
161
+ expect(doc.to_xml).to include("<root id=\"1\">", "<child>data</child>")
162
+
163
+ reparsed = context.parse(doc.to_xml)
164
+ expect(reparsed.root["id"]).to eq("1")
165
+ expect(reparsed.at_xpath("//child").text).to eq("data")
166
+ end
167
+
168
+ it "replaces an existing root" do
169
+ doc = context.parse("<old><nested/></old>")
170
+ replacement = doc.create_element("new")
171
+ replacement.add_child("text")
172
+
173
+ doc.root = replacement
174
+
175
+ expect(doc.root.name).to eq("new")
176
+ expect(doc.to_xml).to include("<new>text</new>")
177
+ expect(doc.to_xml).not_to include("old")
178
+ end
179
+ end
146
180
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.41
4
+ version: 0.5.43
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.