moxml 0.5.79 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9314d43b66d5890286b147d882d0200e33adee9e61209e8088674be8205e11ea
4
- data.tar.gz: 3ec14b4892a6ae2f0dd2a8d2bded06b86a0c86ce742b20f6763478df4a851963
3
+ metadata.gz: 656e065f07ecc3aea2624682345254907cab3bee557d14a74e7a9a1e6d3e53f2
4
+ data.tar.gz: d3606b240493ed82eb131bb8dd37e7d011bcd4d7d12d8907407c5408fcecb970
5
5
  SHA512:
6
- metadata.gz: bb2f60f496656598a90121c56039f620e4a46518753c9062262d4aa21a7c56db6b3d8b1fa5d3d87f4f45773a1de03d63e731e10ee95fc9fbe1c6d8bab5c3122d
7
- data.tar.gz: 59d27148762e9b3837feb218bd42820a3a17291b8ac266b8868b730b19dc0d4d84abaa238f60e32a279c31673593dfc70f809d4cefd8b60c6deae173e8f63745
6
+ metadata.gz: d956c3ff27cae1eedce7a4cc9cb53f57a7d12a58999b74ddebb7bc4a3c99661e2cea010a5a2cc9682315ed5d4bfe06b4122453127d6682ecb96fcfd4401202e2
7
+ data.tar.gz: 478679f5fc817ba910aa0adc927ca0e22009ec1cc787b1388cef9ed3ff570da51c83eaa5e5a99f9b4b9c21e2d6d0d8dbed280925035bc00b580ba242776e7766
@@ -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
@@ -1488,6 +1488,31 @@ module Moxml
1488
1488
  line.zero? ? nil : line
1489
1489
  end
1490
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
+
1491
1516
  def attributes(element)
1492
1517
  element = to_binding(element)
1493
1518
  element.attribute_nodes
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.79"
4
+ VERSION = "0.5.80"
5
5
  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.79
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-23 00:00:00.000000000 Z
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