moxml 0.5.70 → 0.5.71
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 +4 -4
- data/Gemfile +1 -1
- data/README.adoc +3 -1
- data/lib/moxml/adapter/base.rb +14 -0
- data/lib/moxml/adapter/leptris.rb +53 -92
- data/lib/moxml/element.rb +11 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +2 -4
- data/spec/moxml/adapter/platform_spec.rb +2 -2
- data/spec/moxml/element_attribute_pairs_spec.rb +42 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20275fa1b50e6428f448b919513cea0b71c6737e002254bd34802f2491f4a6df
|
|
4
|
+
data.tar.gz: '09f7c98af52d5efab3a8ca2efbee63a50f3eae2c26cb1a8f1d4795711462c6b6'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4474310fe16f301dacbef2e6eac14f1b7cbb6c3be75fd547ed6ea85e054e0ad8c6fd60d3d11448e17ee2d556408670a1999ed8cb1d4dec83392480d6b6283e63
|
|
7
|
+
data.tar.gz: 7167a5ef4c392461ecb813ca330e1800f2433ed9c3254925943d921223210e84d480826ee8787728a14cb29c2f3b0f3f4f2c3141b3b87a4bc1d3df3e204798c5
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
|
@@ -91,8 +91,10 @@ moxml's customized generator and decoder overrides.
|
|
|
91
91
|
6.0 itself requires Ruby >= 3.2); Ruby 3.1 resolves 5.x.
|
|
92
92
|
|
|
93
93
|
|Leptris (leptris-ruby)
|
|
94
|
-
|>= 1.9.
|
|
94
|
+
|>= 1.9.194.1
|
|
95
95
|
|Hard floor — `MINIMUM_BINDING_VERSION`; older bindings do not load.
|
|
96
|
+
Raised from 1.9.32 (2026-09-20): every known consumer resolves a
|
|
97
|
+
modern binding, and the sub-1.9.194 accommodation paths are deleted.
|
|
96
98
|
Newer bindings unlock more native surface automatically:
|
|
97
99
|
1.9.163.7+ single-result XPath seams, 1.9.174.4+ C-minted identity
|
|
98
100
|
wrappers, 1.9.174.6+ one-dispatch subtree walks, 1.9.177+ first-class
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -358,6 +358,20 @@ namespace_validation_mode: :strict)
|
|
|
358
358
|
nil
|
|
359
359
|
end
|
|
360
360
|
|
|
361
|
+
# Read-only attribute listing as [name, value] pairs
|
|
362
|
+
# (Moxml::Element#attribute_pairs) — document order,
|
|
363
|
+
# duplicates included, no Attribute node wrappers. The
|
|
364
|
+
# walk-hot shape for consumers that only read name/value;
|
|
365
|
+
# mutation and per-attribute namespace resolution stay on
|
|
366
|
+
# #attributes. This default derives from #attributes;
|
|
367
|
+
# adapters with a bulk face override (leptris answers in
|
|
368
|
+
# one C crossing — leptris-ruby#278).
|
|
369
|
+
def attribute_pairs(element)
|
|
370
|
+
attributes(element).map do |attr|
|
|
371
|
+
[attribute_name(attr), attr.value.to_s]
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
361
375
|
# Plan row stream (Moxml::Plan) — ADAPTER CONTRACT. Yields
|
|
362
376
|
# |name, attrs_pairs (flat [k, v, ...]), first-text, depth|
|
|
363
377
|
# per element in document order (pre-order); first-text is
|
|
@@ -26,18 +26,17 @@ module Moxml
|
|
|
26
26
|
# (1.7.0) surfaces predate it. Older bindings are not eligible
|
|
27
27
|
# for the default (see Config.leptris_preferred_available?) and
|
|
28
28
|
# the adapter no longer carries accommodation paths for them.
|
|
29
|
-
|
|
29
|
+
# Raised 1.9.32 -> 1.9.194.1 (2026-09-20): every known consumer
|
|
30
|
+
# resolves a modern binding (canon itself pins ~> 1.9.193), and
|
|
31
|
+
# the sub-1.9.194 accommodation paths are deleted.
|
|
32
|
+
MINIMUM_BINDING_VERSION = "1.9.194.1"
|
|
30
33
|
|
|
31
34
|
# leptris_parse_html_string shipped in bindings 1.9.80
|
|
32
35
|
# (libleptris 1.9.75, engine #659) as Leptris::XML.parse_html.
|
|
33
|
-
HTML_PARSE_SUPPORTED =
|
|
34
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.80")
|
|
35
|
-
|
|
36
36
|
# Attribute-node xpath results carry proper wrappers since
|
|
37
37
|
# 1.9.105 (leptris-ruby#153: ResultAttr with name/value);
|
|
38
38
|
# before that the native gate routed them to the Ruby engine.
|
|
39
|
-
ATTR_RESULT_NATIVE =
|
|
40
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.105")
|
|
39
|
+
ATTR_RESULT_NATIVE = true
|
|
41
40
|
|
|
42
41
|
# leptris_node_digest shipped in bindings 1.9.99 (libleptris
|
|
43
42
|
# 1.9.99, engine #869): on-demand Merkle subtree hash, zero
|
|
@@ -223,77 +222,46 @@ module Moxml
|
|
|
223
222
|
ELEMENT_CONTRACT_READ =
|
|
224
223
|
::Moxml::ElementBehavior.instance_method(:[])
|
|
225
224
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
# Doc-level entity-marker memo
|
|
230
|
-
#
|
|
225
|
+
# Bindings >= 1.9.194.1 (the floor) carry unshadowed
|
|
226
|
+
# aliases; the UnboundMethod#bind_call face for older
|
|
227
|
+
# shapes is deleted.
|
|
228
|
+
# Doc-level entity-marker memo (one WeakMap,
|
|
229
|
+
# generation-stamped Integer values).
|
|
231
230
|
ENTITY_DOC_MEMO = ::ObjectSpace::WeakMap.new
|
|
232
231
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def [](key)
|
|
254
|
-
if key.is_a?(String) && !key.include?(":")
|
|
255
|
-
value = attr_read(key)
|
|
256
|
-
return value unless value.is_a?(String) && doc_entity_bearing?
|
|
232
|
+
module Reads
|
|
233
|
+
# Marker presence is a DOCUMENT fact, but the wrapper
|
|
234
|
+
# memo re-derives it per node (the per-read machinery
|
|
235
|
+
# was ~a quarter of the consumer walk). Doc-level
|
|
236
|
+
# WeakMap, generation-stamped into one Integer
|
|
237
|
+
# (immediates make safe weak values): one C document
|
|
238
|
+
# read plus one map hit per bare read. Entries die
|
|
239
|
+
# with their documents; a stale generation re-derives.
|
|
240
|
+
def doc_entity_bearing?
|
|
241
|
+
doc = NN_DOCUMENT.bind_call(self)
|
|
242
|
+
memo = ENTITY_DOC_MEMO[doc]
|
|
243
|
+
gen = ::Moxml::Adapter::Leptris.serialize_generation
|
|
244
|
+
return memo.allbits?(1) if memo && (memo >> 1) == gen
|
|
245
|
+
|
|
246
|
+
bearing = ::Moxml::Adapter::Leptris.entity_bearing?(self)
|
|
247
|
+
ENTITY_DOC_MEMO[doc] = (gen << 1) | (bearing ? 1 : 0)
|
|
248
|
+
bearing
|
|
249
|
+
end
|
|
257
250
|
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
def [](key)
|
|
252
|
+
if key.is_a?(String) && !key.include?(":")
|
|
253
|
+
value = attr_read(key)
|
|
254
|
+
return value unless value.is_a?(String) && doc_entity_bearing?
|
|
260
255
|
|
|
261
|
-
|
|
256
|
+
return ::Moxml::Adapter::Leptris.restore_entities(value)
|
|
262
257
|
end
|
|
263
258
|
|
|
264
|
-
|
|
265
|
-
value = text_read
|
|
266
|
-
value.is_a?(String) && doc_entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
|
|
267
|
-
end
|
|
259
|
+
ELEMENT_CONTRACT_READ.bind_call(self, key)
|
|
268
260
|
end
|
|
269
|
-
else
|
|
270
|
-
module Reads
|
|
271
|
-
def doc_entity_bearing?
|
|
272
|
-
doc = NN_DOCUMENT.bind_call(self)
|
|
273
|
-
memo = ENTITY_DOC_MEMO[doc]
|
|
274
|
-
gen = ::Moxml::Adapter::Leptris.serialize_generation
|
|
275
|
-
return memo.allbits?(1) if memo && (memo >> 1) == gen
|
|
276
|
-
|
|
277
|
-
bearing = ::Moxml::Adapter::Leptris.entity_bearing?(self)
|
|
278
|
-
ENTITY_DOC_MEMO[doc] = (gen << 1) | (bearing ? 1 : 0)
|
|
279
|
-
bearing
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def [](key)
|
|
283
|
-
if key.is_a?(String) && !key.include?(":")
|
|
284
|
-
value = NN_ATTRIBUTE.bind_call(self, key)
|
|
285
|
-
return value unless value.is_a?(String) && doc_entity_bearing?
|
|
286
|
-
|
|
287
|
-
return ::Moxml::Adapter::Leptris.restore_entities(value)
|
|
288
|
-
end
|
|
289
261
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def text
|
|
294
|
-
value = NN_CONTENT.bind_call(self)
|
|
295
|
-
value.is_a?(String) && doc_entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
|
|
296
|
-
end
|
|
262
|
+
def text
|
|
263
|
+
value = text_read
|
|
264
|
+
value.is_a?(String) && doc_entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
|
|
297
265
|
end
|
|
298
266
|
end
|
|
299
267
|
ELEMENT.include(Reads)
|
|
@@ -536,9 +504,7 @@ module Moxml
|
|
|
536
504
|
# exactly the plan's shape, with no wrapper minting at all.
|
|
537
505
|
# Marker-bearing documents stay on the generic path (the bulk
|
|
538
506
|
# stream has no marker split).
|
|
539
|
-
NATIVE_PLAN_ROWS =
|
|
540
|
-
NATIVE_READ_LAYER &&
|
|
541
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.4")
|
|
507
|
+
NATIVE_PLAN_ROWS = true
|
|
542
508
|
|
|
543
509
|
# leptris-ruby#272 (binding 1.9.201.1): Native.plan_structs —
|
|
544
510
|
# the C struct executor behind Moxml::StructPlan. The floor
|
|
@@ -602,20 +568,10 @@ module Moxml
|
|
|
602
568
|
end
|
|
603
569
|
return nil unless root_binding
|
|
604
570
|
|
|
605
|
-
rows =
|
|
606
|
-
|
|
607
|
-
root_binding.c_address)
|
|
608
|
-
else
|
|
609
|
-
doc.snapshot(root_binding)
|
|
610
|
-
end
|
|
571
|
+
rows = ::Leptris::XML::Native.snapshot_rows(doc,
|
|
572
|
+
root_binding.c_address)
|
|
611
573
|
rows.each do |row|
|
|
612
|
-
|
|
613
|
-
yield(row[0], row[1], row[2], row[3])
|
|
614
|
-
else
|
|
615
|
-
next unless row[:kind] == "element"
|
|
616
|
-
|
|
617
|
-
yield(row[:name], row[:attrs].flatten, row[:text], row[:depth])
|
|
618
|
-
end
|
|
574
|
+
yield(row[0], row[1], row[2], row[3])
|
|
619
575
|
end
|
|
620
576
|
true
|
|
621
577
|
end
|
|
@@ -806,13 +762,6 @@ module Moxml
|
|
|
806
762
|
end
|
|
807
763
|
|
|
808
764
|
def parse_html(html, _options = {}, _context = nil)
|
|
809
|
-
unless HTML_PARSE_SUPPORTED
|
|
810
|
-
raise Moxml::AdapterError.new(
|
|
811
|
-
"HTML parsing requires leptris >= 1.9.80 (have #{::Leptris::VERSION})",
|
|
812
|
-
adapter: name, operation: "parse_html",
|
|
813
|
-
)
|
|
814
|
-
end
|
|
815
|
-
|
|
816
765
|
html_string = html.is_a?(IO) || html.is_a?(StringIO) ? html.read : html.to_s
|
|
817
766
|
native_doc = begin
|
|
818
767
|
::Leptris::XML.parse_html(html_string)
|
|
@@ -1460,6 +1409,18 @@ module Moxml
|
|
|
1460
1409
|
element.attribute_nodes
|
|
1461
1410
|
end
|
|
1462
1411
|
|
|
1412
|
+
# One C crossing per element on bindings with the bulk face
|
|
1413
|
+
# (leptris-ruby#278): interned names, fresh values, zero
|
|
1414
|
+
# Attr objects. Face-probed, not version-gated — the floor
|
|
1415
|
+
# (1.9.194.1) admits bindings without it, which take the
|
|
1416
|
+
# base default over #attributes.
|
|
1417
|
+
def attribute_pairs(element)
|
|
1418
|
+
binding_element = to_binding(element)
|
|
1419
|
+
return super unless binding_element.respond_to?(:attribute_pairs)
|
|
1420
|
+
|
|
1421
|
+
binding_element.attribute_pairs
|
|
1422
|
+
end
|
|
1423
|
+
|
|
1463
1424
|
def attribute_element(attr)
|
|
1464
1425
|
attr.element
|
|
1465
1426
|
end
|
data/lib/moxml/element.rb
CHANGED
|
@@ -115,6 +115,17 @@ module Moxml
|
|
|
115
115
|
self[attr_name]
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
# Read-only [name, value] pairs in document order, duplicates
|
|
119
|
+
# included — no Attribute node wrappers. The walk-hot shape for
|
|
120
|
+
# consumers that only read name/value (leptris-ruby#278): on
|
|
121
|
+
# leptris >= 1.9.208.1 this answers in one C crossing with zero
|
|
122
|
+
# per-attribute objects. Names may be shared frozen strings —
|
|
123
|
+
# dup before mutating. Mutation and per-attribute namespace
|
|
124
|
+
# resolution stay on #attributes.
|
|
125
|
+
def attribute_pairs
|
|
126
|
+
adapter.attribute_pairs(@native)
|
|
127
|
+
end
|
|
128
|
+
|
|
118
129
|
def attributes
|
|
119
130
|
# Primed like Node.wrap: the adapter and type are known at
|
|
120
131
|
# mint, so the first name/value/attribute? access skips the
|
data/lib/moxml/version.rb
CHANGED
|
@@ -324,10 +324,8 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
324
324
|
end
|
|
325
325
|
|
|
326
326
|
describe "HTML parsing (leptris/leptris#659)" do
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
end
|
|
330
|
-
|
|
327
|
+
# leptris >= 1.9.80 is below the binding floor (1.9.194.1) —
|
|
328
|
+
# the HTML engine mode is always present.
|
|
331
329
|
let(:ctx) { Moxml.new(:leptris) }
|
|
332
330
|
|
|
333
331
|
it "synthesizes the html/body structure with lowercased names" do
|
|
@@ -93,11 +93,11 @@ RSpec.describe Moxml::Adapter do
|
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
it "pins the leptris binding floor at 1.9.
|
|
96
|
+
it "pins the leptris binding floor at 1.9.194.1 (issue #149; raised 2026-09-20)" do
|
|
97
97
|
skip "leptris not installed" unless described_class.available?(:leptris)
|
|
98
98
|
|
|
99
99
|
adapter = described_class.load(:leptris)
|
|
100
|
-
expect(adapter::MINIMUM_BINDING_VERSION).to eq("1.9.
|
|
100
|
+
expect(adapter::MINIMUM_BINDING_VERSION).to eq("1.9.194.1")
|
|
101
101
|
expect(Gem::Version.new(Leptris::VERSION))
|
|
102
102
|
.to be >= Gem::Version.new(adapter::MINIMUM_BINDING_VERSION)
|
|
103
103
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Element#attribute_pairs adapter-contract conformance
|
|
6
|
+
# (leptris-ruby#278): pairs must equal what the node path yields —
|
|
7
|
+
# attributes.map { |a| [a.name, a.value.to_s] } — on every adapter,
|
|
8
|
+
# whether the adapter answers through a bulk C face (leptris >=
|
|
9
|
+
# 1.9.208.1) or derives from #attributes.
|
|
10
|
+
RSpec.describe Moxml::Element do
|
|
11
|
+
let(:xml) do
|
|
12
|
+
'<catalog><record id="r0" kind="k0"><field name="f0" unit="u0">v</field></record></catalog>'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
|
16
|
+
|
|
17
|
+
def with_adapter(name)
|
|
18
|
+
ctx = Moxml.new(name)
|
|
19
|
+
yield ctx
|
|
20
|
+
rescue StandardError, LoadError
|
|
21
|
+
skip "adapter #{name} unavailable"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "matches the node path on every available adapter" do
|
|
25
|
+
results = ADAPTERS.filter_map do |name|
|
|
26
|
+
with_adapter(name) do |ctx|
|
|
27
|
+
root = ctx.parse(xml).root
|
|
28
|
+
[name, root.attribute_pairs]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
expect(results).not_to be_empty
|
|
32
|
+
|
|
33
|
+
results.each do |name, pairs|
|
|
34
|
+
with_adapter(name) do |ctx|
|
|
35
|
+
root = ctx.parse(xml).root
|
|
36
|
+
node_derived = root.attributes.map { |a| [a.name, a.value.to_s] }
|
|
37
|
+
expect(pairs).to eq(node_derived),
|
|
38
|
+
"#{name}: #{pairs.inspect} vs #{node_derived.inspect}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
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.
|
|
4
|
+
version: 0.5.71
|
|
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-
|
|
11
|
+
date: 2026-09-20 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
|
|
@@ -466,6 +466,7 @@ files:
|
|
|
466
466
|
- spec/moxml/document_builder_spec.rb
|
|
467
467
|
- spec/moxml/document_free_spec.rb
|
|
468
468
|
- spec/moxml/document_spec.rb
|
|
469
|
+
- spec/moxml/element_attribute_pairs_spec.rb
|
|
469
470
|
- spec/moxml/element_spec.rb
|
|
470
471
|
- spec/moxml/entity_preservation_spec.rb
|
|
471
472
|
- spec/moxml/entity_reference_spec.rb
|