moxml 0.5.48 → 0.5.50

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: fbe45e0b5017c94650ed10dde4e726843ad5a820005ac4a1f653d5747ef04f71
4
- data.tar.gz: 14f003f4f15ff1b9a40429654a7435485cb20ca62e88f6b9dc21e1120e9c85dd
3
+ metadata.gz: '0188508b255f1adb8c622a2965a91352c7ffc63afd79d53796ad48c708d479eb'
4
+ data.tar.gz: 16f4ce7dfbd8a335dfd535bca051288f8a5645a273d7aea9be8fa2d245073482
5
5
  SHA512:
6
- metadata.gz: b6bcf2d9d9b6a34bbd56afc5076421c22e107ac33e9a8adc06ebe0329b7cab59e038ace45e0cb2d14debe8ba16c8cd606af49d8994bf59137e51d2679487295d
7
- data.tar.gz: 22ea33b5bc99974709cc8cbcf670dc39fdce033fcce310353cda9aa15568129a283055a8e8975f211d67a8167fa6f87597db0d81e4935f77864a6d58ccc1a9dd
6
+ metadata.gz: 13d1a513fecc2d4e23ee833929b05b2c93d77b4567623246f22f4ddb8f3461a2661f69a1cc73b091b08ab6474b40f929ecf4272ba54112f313ba936598761b81
7
+ data.tar.gz: 76ef5828a376dd566c1945a8655e71fe5d8a68f7a8a007a1260855a05ebcec442034712370e3e9decc5d07490ea2f9ce534b49c5c036df41e6b291868698927f
@@ -141,6 +141,14 @@ module Moxml
141
141
  nil
142
142
  end
143
143
 
144
+ # Subtree walk capability: an adapter with a C-side pre-order
145
+ # traversal (leptris >= 1.9.174.6 visit) walks descendants in
146
+ # one dispatch; nil keeps the recursive children walk. Self
147
+ # is not yielded (each_node semantics).
148
+ def walk_descendants(_native, _context)
149
+ nil
150
+ end
151
+
144
152
  def same_node?(one, other)
145
153
  one == other
146
154
  end
@@ -159,21 +159,116 @@ module Moxml
159
159
  # 3-4 Ruby frames collapse to the module call over the C
160
160
  # method. @native is self; every adapter method that accepts
161
161
  # a native already handles NativeNode receivers.
162
- # Extend-in-place mint (#230 stage 2): disabled by
163
- # measurement. Extending TypedData natives with the
164
- # contract modules works correctly (full battery + lm
165
- # 5422/0 under identity mode) but each extend allocates a
166
- # singleton plus per-module iclasses — the consumer
167
- # pipeline regressed 4017us -> 16350us with 6x the
168
- # allocations on a 900-node walk. The per-read frame
169
- # savings (~50ns) cannot repay that mint cost. Revisit
170
- # when the binding grows klass-injection
171
- # (NativeNode.from(doc, ptr, klass:) minting the
172
- # contract-carrying subclass in C) — asked upstream in the
173
- # #230 thread. Wrappers mint through the Wrappers:: shells
174
- # meanwhile.
175
- def wrap_native(_node, _type, _context)
176
- nil
162
+ # Identity mode v2 (#230): wrapper classes SUBCLASS the
163
+ # TypedData native and include the contract modules, and
164
+ # NativeNode.from honors its receiver (klass-injection,
165
+ # 1.9.174.4+): the C wrap mints our class directly — no
166
+ # Ruby-level extend (the v1 extend mint regressed the
167
+ # pipeline 4x; this is the same TypedData wrap the base
168
+ # mint pays). @native is self; the adapter's C-surface
169
+ # bind_calls accept these receivers (they are NativeNode
170
+ # instances). The hot bare reads super into the C methods
171
+ # (exact-name = the bare-name contract).
172
+ module Identity
173
+ ELEMENT = Class.new(::Leptris::XML::NativeNode) do
174
+ include ::Moxml::Node
175
+ include ::Moxml::Element
176
+ end
177
+ TEXT = Class.new(::Leptris::XML::NativeNode) do
178
+ include ::Moxml::Node
179
+ include ::Moxml::Text
180
+ end
181
+ CDATA = Class.new(::Leptris::XML::NativeNode) do
182
+ include ::Moxml::Node
183
+ include ::Moxml::Cdata
184
+ end
185
+ COMMENT = Class.new(::Leptris::XML::NativeNode) do
186
+ include ::Moxml::Node
187
+ include ::Moxml::Comment
188
+ end
189
+ PROCESSING_INSTRUCTION = Class.new(::Leptris::XML::NativeNode) do
190
+ include ::Moxml::Node
191
+ include ::Moxml::ProcessingInstruction
192
+ end
193
+
194
+ TYPES = {
195
+ element: ELEMENT,
196
+ text: TEXT,
197
+ cdata: CDATA,
198
+ comment: COMMENT,
199
+ processing_instruction: PROCESSING_INSTRUCTION,
200
+ }.freeze
201
+
202
+ module Reads
203
+ # The C methods sit BELOW the contract modules in the
204
+ # ancestry (NativeNode is the superclass), so super
205
+ # from here would run the contract implementation and
206
+ # ADD frames. bind_call reaches the C surface
207
+ # directly; prefixed names take the contract path.
208
+ ELEMENT_CONTRACT_READ =
209
+ ::Moxml::ElementBehavior.instance_method(:[])
210
+
211
+ def [](key)
212
+ if key.is_a?(String) && !key.include?(":")
213
+ value = NN_ATTRIBUTE.bind_call(self, key)
214
+ return value unless value.is_a?(String) && entity_bearing?
215
+
216
+ return ::Moxml::Adapter::Leptris.restore_entities(value)
217
+ end
218
+
219
+ ELEMENT_CONTRACT_READ.bind_call(self, key)
220
+ end
221
+
222
+ def text
223
+ value = NN_CONTENT.bind_call(self)
224
+ value.is_a?(String) && entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
225
+ end
226
+ end
227
+ ELEMENT.include(Reads)
228
+ TEXT.include(Reads)
229
+ end
230
+
231
+ NATIVE_IDENTITY =
232
+ NATIVE_MUTATIONS_COHERENT &&
233
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.4")
234
+
235
+ def wrap_native(node, type, _context)
236
+ return nil unless NATIVE_IDENTITY &&
237
+ node.is_a?(::Leptris::XML::NativeNode)
238
+
239
+ # from() registers in the shared address-keyed native
240
+ # cache, so later canonical/bulk mints hand OUR instance
241
+ # back — a native already carrying the contract IS the
242
+ # wrapper; minting again would loop per access.
243
+ return node if node.is_a?(::Moxml::Node)
244
+
245
+ klass = Identity::TYPES[type]
246
+ return nil unless klass
247
+
248
+ doc = doc_for(node)
249
+ return nil unless doc
250
+
251
+ klass.from(doc, node)
252
+ end
253
+
254
+ NATIVE_C_WALK =
255
+ defined?(::Leptris::XML::NativeNode) &&
256
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.6")
257
+
258
+ # One C visit walk (TODO.perf/27: post-order/abort state in
259
+ # the C callback, rb_yield direct — no FFI closure per
260
+ # node) instead of per-level children walks. Pre-order via
261
+ # entering; depth 0 is the receiver and stays unyielded.
262
+ def walk_descendants(native, context)
263
+ return nil unless NATIVE_C_WALK
264
+
265
+ root = to_binding(native)
266
+ return nil unless root.is_a?(::Leptris::XML::Element) ||
267
+ root.is_a?(::Leptris::XML::Document)
268
+
269
+ root.visit do |node, entering, depth|
270
+ yield Moxml::Node.wrap(node, context) if entering && depth.positive?
271
+ end
177
272
  end
178
273
 
179
274
  def record_native_doc(root_native, doc)
data/lib/moxml/node.rb CHANGED
@@ -278,6 +278,19 @@ module Moxml
278
278
  # Recursively yield all descendant nodes
279
279
  # Used by XPath descendant-or-self and descendant axes
280
280
  def each_node(&block)
281
+ unless block
282
+ # Eager materialization: the leptris C-side walk rb_yields
283
+ # from the C callback and segfaults across fiber-suspended
284
+ # enumerator frames, so the no-block form never enters it.
285
+ nodes = []
286
+ each_node { |node| nodes << node }
287
+ return nodes.each
288
+ end
289
+
290
+ # Adapters with a C-side subtree walk take it in one
291
+ # dispatch; the recursive children walk stays the fallback.
292
+ return if adapter.walk_descendants(@native, context, &block)
293
+
281
294
  children.each do |child|
282
295
  yield child
283
296
  child.each_node(&block)
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.48"
4
+ VERSION = "0.5.50"
5
5
  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.48
4
+ version: 0.5.50
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-15 00:00:00.000000000 Z
11
+ date: 2026-09-16 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