moxml 0.5.136 → 0.5.137

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: 7ae551e19c600eba6d19a7a2a1c764275673d4ad7deba04d3778d407464ec8b9
4
- data.tar.gz: e546f3c3290bfde650efb0d42625146abaf87f20741be65aee93ab8dad0a1c84
3
+ metadata.gz: 43beff2cbabe6c8800099bc8f56a76b4c2510c6158cb632984cd31c8c3893a24
4
+ data.tar.gz: e7d0540ce919c428a26b55805f0c78fc25de976c08f2639a83161eab6ffc3b4d
5
5
  SHA512:
6
- metadata.gz: 30ba4c3744a7742dcde2b457fcfa99a0a32f7ddac8ebd721ca79eff029b0faeb4ab91b8577d4270975973933193594b3ff7e05926e7e84500dee25d20c90ce3b
7
- data.tar.gz: 12791df8b1c31cc43204904abbe4f7b5e3f3d1e3e4be248637efbb67d734fe6be36f9384a6345be95ba3993efcec43c0f387086d63cafc623d0f63a66c9c69fa
6
+ metadata.gz: 8e33f465faabc1da5cc0c7233f70e60592e0080cb4897b81d98eb1b073fedeea21160d2252170e322c8a66b33f4899e1752f96b30555ba0959f4b6f2e46ecd8b
7
+ data.tar.gz: 29dde54bd4af8259207a708175309fdf9ad7fd3c4702eacf1e77a92d5fbfbd03b0993d2730c70ddb0c5b30edc41bbc4871e73d10334814fd51b813f2e6905629
@@ -12,7 +12,6 @@ module Moxml
12
12
  autoload :Doctype, "moxml/adapter/customized_leptris/doctype"
13
13
  autoload :EntityReference, "moxml/adapter/customized_leptris/entity_reference"
14
14
  autoload :TextSegment, "moxml/adapter/customized_leptris/text_segment"
15
- autoload :DocumentPI, "moxml/adapter/customized_leptris/document_pi"
16
15
  end
17
16
  end
18
17
  end
@@ -83,8 +83,6 @@ module Moxml
83
83
  doc.root = child
84
84
  when ::Leptris::XML::ProcessingInstruction
85
85
  doc.add_pi(child.target, child.content.to_s)
86
- when CustomizedLeptris::DocumentPI
87
- doc.add_pi(child.target, child.data)
88
86
  when ::Leptris::XML::Text
89
87
  texts = attachments.get(doc, :document_text) || []
90
88
  texts << child
@@ -20,8 +20,7 @@ module Moxml
20
20
 
21
21
  case native
22
22
  when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
23
- CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
24
- CustomizedLeptris::DocumentPI
23
+ CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment
25
24
  true
26
25
  else
27
26
  # Parentless elements come only from Iterparse —
@@ -86,7 +86,7 @@ module Moxml
86
86
  # binding, so a Text branch first would swallow CDATA nodes.
87
87
  case node
88
88
  when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
89
- CustomizedLeptris::EntityReference, CustomizedLeptris::DocumentPI
89
+ CustomizedLeptris::EntityReference
90
90
  return node.to_xml
91
91
  when ::Leptris::XML::CDATA
92
92
  return XmlEmitter.cdata(node.content)
@@ -1639,7 +1639,7 @@ module Moxml
1639
1639
  when ::Leptris::XML::Text, CustomizedLeptris::TextSegment then :text
1640
1640
  when ::Leptris::XML::Attr, ::Leptris::XML::ResultAttr then :attribute
1641
1641
  when ::Leptris::XML::Comment then :comment
1642
- when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then :processing_instruction
1642
+ when ::Leptris::XML::ProcessingInstruction then :processing_instruction
1643
1643
  when ::Leptris::XML::Document then :document
1644
1644
  when ::Leptris::XML::DocType, CustomizedLeptris::Doctype then :doctype
1645
1645
  when CustomizedLeptris::Declaration then :declaration
@@ -1666,7 +1666,6 @@ module Moxml
1666
1666
 
1667
1667
  def node_name(node)
1668
1668
  return node.root_name if node.is_a?(::Leptris::XML::DocType)
1669
- return node.target if node.is_a?(CustomizedLeptris::DocumentPI)
1670
1669
 
1671
1670
  if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
1672
1671
  # PIs expose no name through the native layer.
@@ -1684,7 +1683,7 @@ module Moxml
1684
1683
  def set_node_name(node, name)
1685
1684
  node = to_binding(node) if NATIVE_READ_LAYER
1686
1685
  case node
1687
- when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then node.target = name
1686
+ when ::Leptris::XML::ProcessingInstruction then node.target = name
1688
1687
  else node.name = name
1689
1688
  end
1690
1689
  end
@@ -1698,8 +1697,6 @@ module Moxml
1698
1697
  CustomizedLeptris::Doctype.new(node.name, node.external_id, node.system_id)
1699
1698
  when CustomizedLeptris::EntityReference
1700
1699
  CustomizedLeptris::EntityReference.new(node.name)
1701
- when CustomizedLeptris::DocumentPI
1702
- CustomizedLeptris::DocumentPI.new(node.target, node.data, node.parent_doc)
1703
1700
  else
1704
1701
  node.dup
1705
1702
  end
@@ -1796,7 +1793,6 @@ module Moxml
1796
1793
  assemble_document_children(node)
1797
1794
  when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
1798
1795
  CustomizedLeptris::EntityReference, CustomizedLeptris::TextSegment,
1799
- CustomizedLeptris::DocumentPI,
1800
1796
  # Terminal node kinds pay an FFI round trip for an empty
1801
1797
  # list; unfiltered recursions visit every text node.
1802
1798
  ::Leptris::XML::Text, ::Leptris::XML::Comment,
@@ -1832,8 +1828,7 @@ module Moxml
1832
1828
  case node
1833
1829
  when ::Leptris::XML::Element then root_parent(node)
1834
1830
  when ::Leptris::XML::Document then nil
1835
- when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype,
1836
- CustomizedLeptris::DocumentPI then node.parent_doc
1831
+ when CustomizedLeptris::Declaration, CustomizedLeptris::Doctype then node.parent_doc
1837
1832
  when CustomizedLeptris::TextSegment, CustomizedLeptris::EntityReference then node.parent
1838
1833
  # Same body as the Element arm by design (frequency
1839
1834
  # ordering); the generic kinds are cold.
@@ -2535,23 +2530,6 @@ module Moxml
2535
2530
  end
2536
2531
  when CustomizedLeptris::EntityReference
2537
2532
  marker_text_for(node.parent, node.name)&.unlink
2538
- when CustomizedLeptris::DocumentPI
2539
- if NATIVE_DOC_PARTS && node.parent_doc
2540
- pis = node.parent_doc.processing_instructions
2541
- index = pis.index do |target, data|
2542
- target == node.target && data == node.data
2543
- end
2544
- if index
2545
- node.parent_doc.remove_pi(index)
2546
- node.parent_doc = nil
2547
- end
2548
- else
2549
- raise Moxml::NotImplementedError.new(
2550
- "libleptris has no document-level PI removal",
2551
- adapter: :leptris,
2552
- feature: :remove,
2553
- )
2554
- end
2555
2533
  else
2556
2534
  begin
2557
2535
  node.unlink
@@ -142,6 +142,16 @@ module Moxml
142
142
  # Remove the attribute named by XML-correct resolution (see
143
143
  # #assign for the name semantics).
144
144
  #
145
+ # #378: a bare name that matches no no-namespace attribute falls
146
+ # back to the SINGLE namespaced attribute carrying that local
147
+ # name — callers spelling the local part (the name
148
+ # attribute_nodes-style listings expose) expect removal, and the
149
+ # silent no-op broke remediation flows. Bare-name resolution
150
+ # keeps Namespaces 1.0 §5.2 semantics for reads; removal widens
151
+ # only when unambiguous. Several prefixed spellings sharing the
152
+ # local name remove nothing — guessing a prefix would corrupt
153
+ # documents.
154
+ #
145
155
  # @return [Moxml::Attribute, nil] the removed attribute
146
156
  def remove(element, name)
147
157
  name = name.to_s
@@ -153,6 +163,8 @@ module Moxml
153
163
  end
154
164
 
155
165
  attr = resolve(element, name)
166
+ attr ||= resolve_unique_local(element, name) unless name.include?(":")
167
+
156
168
  return nil unless attr
157
169
 
158
170
  adapter.remove_attribute_native(attr.native)
@@ -160,6 +172,17 @@ module Moxml
160
172
  attr
161
173
  end
162
174
 
175
+ # The single namespaced attribute whose local name matches, or
176
+ # nil when none or several do (see #remove).
177
+ def resolve_unique_local(element, local)
178
+ matches = element.attributes.select do |attr|
179
+ attr_name = attr.name
180
+ local_name(attr_name) == local &&
181
+ !attribute_uri(element, attr, attr_name).nil?
182
+ end
183
+ matches.one? ? matches.first : nil
184
+ end
185
+
163
186
  # XPath 1.0 attribute node test, sharing the resolver's
164
187
  # expanded-name semantics:
165
188
  #
data/lib/moxml/element.rb CHANGED
@@ -233,11 +233,15 @@ module Moxml
233
233
  adapter.attachments.set(@native, :children_source_docs, merged)
234
234
  end
235
235
 
236
+ # Removes the attribute named by XML-correct resolution — a
237
+ # qualified name ("p:x"), or a bare name (the no-namespace
238
+ # attribute, falling back to the single namespaced attribute
239
+ # carrying that local name when no bare one exists, #378).
240
+ #
241
+ # @return [Moxml::Attribute, nil] the removed attribute, nil
242
+ # when nothing matched (nokogiri-compatible)
236
243
  def remove_attribute(name)
237
- # Both remove paths end in invalidate_attribute_cache!, which
238
- # bumps the generation.
239
244
  Moxml::AttributeResolver.remove(self, name)
240
- self
241
245
  end
242
246
 
243
247
  # Append a raw XML fragment's top-level nodes as children.
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.136"
4
+ VERSION = "0.5.137"
5
5
  end
@@ -126,6 +126,41 @@ RSpec.shared_examples "Moxml::Attribute" do
126
126
  end
127
127
  end
128
128
 
129
+ describe "removing by local name (#378)" do
130
+ before do
131
+ element.add_namespace("q", "http://x.org")
132
+ element["q:type"] = "namespaced"
133
+ element["type"] = "bare"
134
+ end
135
+
136
+ it "removes the single namespaced attribute carrying that local name" do
137
+ element.remove_attribute("type")
138
+ element.remove_attribute("type")
139
+
140
+ expect(element["q:type"]).to be_nil
141
+ expect(element["type"]).to be_nil
142
+ expect(element.attributes).to be_empty
143
+ end
144
+
145
+ it "returns the removed attribute, nil when nothing matched" do
146
+ removed = element.remove_attribute("type")
147
+
148
+ expect(removed).to be_a(described_class)
149
+ expect(element["type"]).to be_nil
150
+ expect(element.remove_attribute("nonexistent")).to be_nil
151
+ end
152
+
153
+ it "removes nothing when several prefixes share the local name" do
154
+ element.remove_attribute("type")
155
+ element.add_namespace("other", "http://other.org")
156
+ element["other:type"] = "second"
157
+
158
+ expect(element.remove_attribute("type")).to be_nil
159
+ expect(element["q:type"]).to eq("namespaced")
160
+ expect(element["other:type"]).to eq("second")
161
+ end
162
+ end
163
+
129
164
  describe "manipulation" do
130
165
  before do
131
166
  element["test"] = "value"
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.136
4
+ version: 0.5.137
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-10-14 00:00:00.000000000 Z
11
+ date: 2026-10-15 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
@@ -134,7 +134,6 @@ files:
134
134
  - lib/moxml/adapter/customized_leptris.rb
135
135
  - lib/moxml/adapter/customized_leptris/declaration.rb
136
136
  - lib/moxml/adapter/customized_leptris/doctype.rb
137
- - lib/moxml/adapter/customized_leptris/document_pi.rb
138
137
  - lib/moxml/adapter/customized_leptris/entity_reference.rb
139
138
  - lib/moxml/adapter/customized_leptris/text_segment.rb
140
139
  - lib/moxml/adapter/customized_libxml.rb
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Moxml
4
- module Adapter
5
- module CustomizedLeptris
6
- # Document-level processing instruction. libleptris stores these
7
- # outside the element tree (a flat, document-ordered list with no
8
- # prolog/epilog anchoring — its serializer emits them all before
9
- # the root), reachable through Document#processing_instructions
10
- # as [target, data] pairs. This pseudo-native gives the pairs a
11
- # node identity so the moxml contract can list them as document
12
- # children (and answer mutations: creation routes native via
13
- # add_pi; removal has no engine entry).
14
- class DocumentPI
15
- attr_accessor :target, :data, :parent_doc
16
- alias content data
17
- alias content= data=
18
-
19
- def initialize(target, data = "", parent_doc = nil)
20
- @target = target
21
- @data = data.to_s
22
- @parent_doc = parent_doc
23
- end
24
-
25
- def to_xml
26
- data.empty? ? "<?#{target}?>" : "<?#{target} #{data}?>"
27
- end
28
-
29
- def ==(other)
30
- other.is_a?(self.class) && target == other.target && data == other.data
31
- end
32
- end
33
- end
34
- end
35
- end