moxml 0.5.78 → 0.5.80
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/README.adoc +40 -3
- data/lib/moxml/adapter/base.rb +10 -0
- data/lib/moxml/adapter/leptris.rb +27 -4
- data/lib/moxml/adapter/nokogiri.rb +12 -0
- data/lib/moxml/element.rb +23 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/document_parse_diagnostics_spec.rb +12 -0
- data/spec/moxml/element_add_element_spec.rb +50 -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: 656e065f07ecc3aea2624682345254907cab3bee557d14a74e7a9a1e6d3e53f2
|
|
4
|
+
data.tar.gz: d3606b240493ed82eb131bb8dd37e7d011bcd4d7d12d8907407c5408fcecb970
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d956c3ff27cae1eedce7a4cc9cb53f57a7d12a58999b74ddebb7bc4a3c99661e2cea010a5a2cc9682315ed5d4bfe06b4122453127d6682ecb96fcfd4401202e2
|
|
7
|
+
data.tar.gz: 478679f5fc817ba910aa0adc927ca0e22009ec1cc787b1388cef9ed3ff570da51c83eaa5e5a99f9b4b9c21e2d6d0d8dbed280925035bc00b580ba242776e7766
|
data/README.adoc
CHANGED
|
@@ -103,9 +103,10 @@ entity references (`entity_mode: :keep`), 1.9.193.4+ native plan rows
|
|
|
103
103
|
executor (`Moxml::StructPlan`; the floor is the varargs-mint fix),
|
|
104
104
|
1.9.204+ native document-level parts (created DOCTYPEs as native
|
|
105
105
|
nodes, document-PI removal, declaration clear). Current
|
|
106
|
-
recommendation: 1.9.
|
|
107
|
-
|
|
108
|
-
borrowed-text-run
|
|
106
|
+
recommendation: 1.9.228+ (the #318 text-set fix train; carries
|
|
107
|
+
the plan-ABI surface — bound plan rows, typed scalars, fused
|
|
108
|
+
Descriptor#materialize — plus the 1.9.217+ borrowed-text-run and
|
|
109
|
+
1.9.221+ memory wins).
|
|
109
110
|
|===
|
|
110
111
|
|
|
111
112
|
NOTE: moxml's dev Gemfile pins `leptris ~> 1.9.174`, `nokogiri ~> 1.18`,
|
|
@@ -644,6 +645,42 @@ root.add_child(title)
|
|
|
644
645
|
puts doc.to_xml(indent: 2)
|
|
645
646
|
----
|
|
646
647
|
|
|
648
|
+
=== Fast paths and diagnostics
|
|
649
|
+
|
|
650
|
+
Read-only attribute listing without Attribute node objects — the
|
|
651
|
+
walk-hot shape when only names and values are needed (leptris
|
|
652
|
+
answers in one C crossing; document order, duplicates included):
|
|
653
|
+
|
|
654
|
+
[source,ruby]
|
|
655
|
+
----
|
|
656
|
+
element.attribute_pairs.each do |name, value|
|
|
657
|
+
# names are shared frozen strings; dup before mutating
|
|
658
|
+
end
|
|
659
|
+
----
|
|
660
|
+
|
|
661
|
+
Typed struct members on `Moxml::StructPlan` — the executor casts
|
|
662
|
+
in C with no intermediate String (unparseable input degrades to
|
|
663
|
+
the raw String):
|
|
664
|
+
|
|
665
|
+
[source,ruby]
|
|
666
|
+
----
|
|
667
|
+
plan = Moxml::StructPlan.new do
|
|
668
|
+
element "item", Item,
|
|
669
|
+
attrs: { "price" => [:price, :float], "qty" => [:qty, :integer] },
|
|
670
|
+
text: [:label, :integer]
|
|
671
|
+
end
|
|
672
|
+
----
|
|
673
|
+
|
|
674
|
+
Recover-class parse diagnostics (duplicate-attribute events and
|
|
675
|
+
siblings) on adapters with the surface; `[]` elsewhere. Read
|
|
676
|
+
while the document is alive:
|
|
677
|
+
|
|
678
|
+
[source,ruby]
|
|
679
|
+
----
|
|
680
|
+
doc.parse_diagnostics
|
|
681
|
+
# => [{ kind: :recover, message: "Attribute a redefined" }]
|
|
682
|
+
----
|
|
683
|
+
|
|
647
684
|
== Real-world examples
|
|
648
685
|
|
|
649
686
|
Practical, runnable examples demonstrating Moxml usage in common scenarios are
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -358,6 +358,16 @@ namespace_validation_mode: :strict)
|
|
|
358
358
|
nil
|
|
359
359
|
end
|
|
360
360
|
|
|
361
|
+
# Single-crossing construction contract
|
|
362
|
+
# (Moxml::Element#add_element): create + attach under parent
|
|
363
|
+
# + all attributes in one call, returning the new native
|
|
364
|
+
# element. This default has no bulk face — nil sends the
|
|
365
|
+
# caller down the portable create_element + add_child path;
|
|
366
|
+
# adapters with the face override (leptris — #312/#1344).
|
|
367
|
+
def create_element_with_attrs(_parent, _name, _attrs)
|
|
368
|
+
nil
|
|
369
|
+
end
|
|
370
|
+
|
|
361
371
|
# Read-only attribute listing as [name, value] pairs
|
|
362
372
|
# (Moxml::Element#attribute_pairs) — document order,
|
|
363
373
|
# duplicates included, no Attribute node wrappers. The
|
|
@@ -353,10 +353,8 @@ module Moxml
|
|
|
353
353
|
}.freeze
|
|
354
354
|
|
|
355
355
|
def wrap_binding_node(node, context)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return cached if cached
|
|
359
|
-
end
|
|
356
|
+
cached = node.instance_variable_get(:@moxml_wrapper)
|
|
357
|
+
return cached if cached
|
|
360
358
|
|
|
361
359
|
entry = BINDING_FAST_WRAP[node.class]
|
|
362
360
|
return nil unless entry
|
|
@@ -1490,6 +1488,31 @@ module Moxml
|
|
|
1490
1488
|
line.zero? ? nil : line
|
|
1491
1489
|
end
|
|
1492
1490
|
|
|
1491
|
+
# Single-crossing construction (#312/#1344 consumer face):
|
|
1492
|
+
# create + attach under parent + all attributes in ONE
|
|
1493
|
+
# Ruby->C call (create_child is create+attach; the attr
|
|
1494
|
+
# loop is C->C dlsym). Requires parent binding + the face;
|
|
1495
|
+
# callers fall back to create_element + set_attribute +
|
|
1496
|
+
# add_child when absent.
|
|
1497
|
+
def create_element_with_attrs(parent, name, attrs)
|
|
1498
|
+
binding_parent = to_binding(parent)
|
|
1499
|
+
doc = binding_parent.document or return nil
|
|
1500
|
+
return nil unless ::Leptris::XML::Native.respond_to?(:create_element_with_attrs)
|
|
1501
|
+
|
|
1502
|
+
flat = attrs.flatten
|
|
1503
|
+
addr = ::Leptris::XML::Native.create_element_with_attrs(
|
|
1504
|
+
doc.c_address, binding_parent.c_address, name.to_s, flat)
|
|
1505
|
+
return nil if addr.nil? || addr.zero?
|
|
1506
|
+
|
|
1507
|
+
# The binding's own wrap produces the proper binding
|
|
1508
|
+
# Element (kind class, @c_address, @document); canonical
|
|
1509
|
+
# convergence (#219) hands every accessor — children
|
|
1510
|
+
# included — the SAME native, so the wrapper identity
|
|
1511
|
+
# holds across later traversals.
|
|
1512
|
+
canonical_native(doc,
|
|
1513
|
+
::Leptris::XML::Node.wrap(::FFI::Pointer.new(addr), doc))
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1493
1516
|
def attributes(element)
|
|
1494
1517
|
element = to_binding(element)
|
|
1495
1518
|
element.attribute_nodes
|
|
@@ -294,6 +294,18 @@ module Moxml
|
|
|
294
294
|
element[name.to_s] = value.to_s
|
|
295
295
|
end
|
|
296
296
|
|
|
297
|
+
# Cross-adapter parity for Document#parse_diagnostics
|
|
298
|
+
# (#271): Nokogiri's doc.errors already carries recover-
|
|
299
|
+
# mode entries; mapped into the same { kind:, message: }
|
|
300
|
+
# shape. Kind is :error — Nokogiri does not classify.
|
|
301
|
+
def parse_diagnostics(native_doc)
|
|
302
|
+
return [] unless native_doc.respond_to?(:errors)
|
|
303
|
+
|
|
304
|
+
native_doc.errors.map do |e|
|
|
305
|
+
{ kind: :error, message: e.message.to_s }
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
297
309
|
def attributes(element)
|
|
298
310
|
# attribute_nodes, not attributes.values: the attributes Hash
|
|
299
311
|
# is keyed by local name, so xmi:type and type collide and
|
data/lib/moxml/element.rb
CHANGED
|
@@ -126,6 +126,29 @@ module Moxml
|
|
|
126
126
|
adapter.attribute_pairs(@native)
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
# Create an element with attributes and attach it under this
|
|
130
|
+
# element — ONE construction on adapters with the bulk face
|
|
131
|
+
# (leptris, #312/#1344 consumer face): create + attach + all
|
|
132
|
+
# attributes in a single Ruby->C crossing. Returns the child
|
|
133
|
+
# wrapper. Equivalent to create_element + attribute writes +
|
|
134
|
+
# add_child, minus two crossings and the intermediate churn.
|
|
135
|
+
def add_element(name, attrs = {})
|
|
136
|
+
native_child = adapter.create_element_with_attrs(
|
|
137
|
+
@native, name, attrs
|
|
138
|
+
)
|
|
139
|
+
if native_child
|
|
140
|
+
adapter.node_type(native_child)
|
|
141
|
+
Moxml::Node.wrap_with(native_child, context, adapter)
|
|
142
|
+
else
|
|
143
|
+
fallback = Moxml::Node.wrap_with(
|
|
144
|
+
adapter.create_element(name), context, adapter
|
|
145
|
+
)
|
|
146
|
+
attrs.each { |k, v| fallback[k] = v.to_s }
|
|
147
|
+
add_child(fallback)
|
|
148
|
+
fallback
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
129
152
|
def attributes
|
|
130
153
|
# Primed like Node.wrap: the adapter and type are known at
|
|
131
154
|
# mint, so the first name/value/attribute? access skips the
|
data/lib/moxml/version.rb
CHANGED
|
@@ -40,4 +40,16 @@ RSpec.describe Moxml::Document do
|
|
|
40
40
|
rescue StandardError, LoadError
|
|
41
41
|
skip "rexml adapter unavailable"
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
it "maps nokogiri doc.errors into the same shape" do
|
|
45
|
+
ctx = Moxml.new(:nokogiri)
|
|
46
|
+
doc = ctx.parse(%(<r a="1" a="2"/>), recover: true) ||
|
|
47
|
+
ctx.parse(%(<r a="1" a="2"/>))
|
|
48
|
+
diags = doc.parse_diagnostics
|
|
49
|
+
expect(diags).not_to be_empty
|
|
50
|
+
expect(diags.first[:kind]).to eq(:error)
|
|
51
|
+
expect(diags.first[:message]).to be_a(String)
|
|
52
|
+
rescue StandardError, LoadError
|
|
53
|
+
skip "nokogiri adapter unavailable"
|
|
54
|
+
end
|
|
43
55
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Element#add_element adapter-contract conformance (#312): the
|
|
6
|
+
# child must land attached with ALL attributes in one call —
|
|
7
|
+
# through the bulk face (leptris) or the portable create_element +
|
|
8
|
+
# add_child fallback (every other adapter). Guards both halves of
|
|
9
|
+
# the contract: a missing base default NoMethodErrors on non-face
|
|
10
|
+
# adapters, a broken fallback loses attributes or the attach.
|
|
11
|
+
RSpec.describe Moxml::Element do
|
|
12
|
+
ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
|
13
|
+
|
|
14
|
+
def with_adapter(name)
|
|
15
|
+
ctx = Moxml.new(name)
|
|
16
|
+
yield ctx
|
|
17
|
+
rescue StandardError, LoadError
|
|
18
|
+
skip "adapter #{name} unavailable"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "attaches a child with all attributes on every available adapter" do
|
|
22
|
+
ADAPTERS.each do |name|
|
|
23
|
+
with_adapter(name) do |ctx|
|
|
24
|
+
doc = ctx.parse("<catalog/>")
|
|
25
|
+
root = doc.root
|
|
26
|
+
child = root.add_element("record", "id" => "r1", "kind" => "k1")
|
|
27
|
+
|
|
28
|
+
expect(child.name).to eq("record"), "#{name}: name"
|
|
29
|
+
expect(child["id"]).to eq("r1"), "#{name}: id attr"
|
|
30
|
+
expect(child["kind"]).to eq("k1"), "#{name}: kind attr"
|
|
31
|
+
expect(child.parent.name).to eq("catalog"), "#{name}: attach"
|
|
32
|
+
expect(root.children.map(&:name)).to include("record"), "#{name}: visible in children"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "nests under a non-root element with empty attrs" do
|
|
38
|
+
ADAPTERS.each do |name|
|
|
39
|
+
with_adapter(name) do |ctx|
|
|
40
|
+
doc = ctx.parse("<catalog><record/></catalog>")
|
|
41
|
+
record = doc.root.children.first
|
|
42
|
+
field = record.add_element("field")
|
|
43
|
+
|
|
44
|
+
expect(field.name).to eq("field"), "#{name}: name"
|
|
45
|
+
expect(field.attributes).to be_empty, "#{name}: no attrs"
|
|
46
|
+
expect(field.parent.name).to eq("record"), "#{name}: attach"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
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.80
|
|
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-24 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
|
|
@@ -468,6 +468,7 @@ files:
|
|
|
468
468
|
- spec/moxml/document_free_spec.rb
|
|
469
469
|
- spec/moxml/document_parse_diagnostics_spec.rb
|
|
470
470
|
- spec/moxml/document_spec.rb
|
|
471
|
+
- spec/moxml/element_add_element_spec.rb
|
|
471
472
|
- spec/moxml/element_attribute_pairs_spec.rb
|
|
472
473
|
- spec/moxml/element_spec.rb
|
|
473
474
|
- spec/moxml/entity_preservation_spec.rb
|