moxml 0.5.136 → 0.5.138

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: 20467d4026cf631dd85798aaaa47a53526e1a6c8e26b1583ac896e56bb0fa4b4
4
+ data.tar.gz: 3d3829939332dfd24a1d552308d10fd7c0dbbe6eed998a5292ec5a5d82d53def
5
5
  SHA512:
6
- metadata.gz: 30ba4c3744a7742dcde2b457fcfa99a0a32f7ddac8ebd721ca79eff029b0faeb4ab91b8577d4270975973933193594b3ff7e05926e7e84500dee25d20c90ce3b
7
- data.tar.gz: 12791df8b1c31cc43204904abbe4f7b5e3f3d1e3e4be248637efbb67d734fe6be36f9384a6345be95ba3993efcec43c0f387086d63cafc623d0f63a66c9c69fa
6
+ metadata.gz: 353a615d10c7755a3d03a5dd13988eb57b1f30b2bc68f1506e68bc48d053deca42fd968cbba67f5df10cd14dff7da757bfd15f89a734cd1e607e34d3b319eb0a
7
+ data.tar.gz: bfa917028338657c3c09d91f32a964a653f4e50bc8d5abd1d0c7cf404d284d2efb216b0e62df1f17f1d731ef636c6d60bbe27a5f587ccca2c45bb65aa93c8e39
@@ -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
@@ -36,7 +36,13 @@ module Moxml
36
36
  # Nokogiri-shaped contract, epilog anchoring included (issue
37
37
  # #130). Built documents reflect their parts immediately
38
38
  # since leptris-ruby 1.9.32 (leptris-ruby#91).
39
- children.concat(doc.children.to_a)
39
+ # leptris-ruby#459 lists the declaration among the binding
40
+ # children; the Nokogiri-shaped facade contract never lists
41
+ # it (the declaration is a preamble part, not a child node)
42
+ # — filtered at the listing seam.
43
+ children.concat(
44
+ doc.children.to_a.grep_v(::Leptris::XML::Declaration),
45
+ )
40
46
 
41
47
  texts = attachments.get(doc, :document_text)
42
48
  children.concat(texts) if texts
@@ -83,8 +89,6 @@ module Moxml
83
89
  doc.root = child
84
90
  when ::Leptris::XML::ProcessingInstruction
85
91
  doc.add_pi(child.target, child.content.to_s)
86
- when CustomizedLeptris::DocumentPI
87
- doc.add_pi(child.target, child.data)
88
92
  when ::Leptris::XML::Text
89
93
  texts = attachments.get(doc, :document_text) || []
90
94
  texts << child
@@ -158,6 +162,13 @@ module Moxml
158
162
  next
159
163
  end
160
164
 
165
+ # leptris-ruby#459 exposes the declaration as a
166
+ # Document#children entry; the preamble above owns the
167
+ # emission (a second line here duplicates it — and the
168
+ # 1.9.360 0-arity Declaration#to_xml raises under the
169
+ # options-passing call).
170
+ next if child.is_a?(::Leptris::XML::Declaration)
171
+
161
172
  parts << raw_serialize(child, options) << "\n"
162
173
  end
163
174
 
@@ -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
@@ -244,6 +244,9 @@ module Moxml
244
244
  return NATIVE_NODE_TYPE_MAP[node.node_type] || :unknown
245
245
  end
246
246
  return :document if node.is_a?(::LibXML::XML::Document)
247
+ # Attr is a separate class (not ::Node) — attribute-terminal
248
+ # XPath results mint as plain Node without this.
249
+ return :attribute if node.is_a?(::LibXML::XML::Attr)
247
250
 
248
251
  wrapper_type = WRAPPER_NODE_TYPE_MAP[node.class]
249
252
  return wrapper_type if wrapper_type
@@ -949,6 +952,16 @@ module Moxml
949
952
  native_node = unpatch_node(node)
950
953
  return [] unless native_node
951
954
 
955
+ # libxml-ruby's Document#find returns EMPTY for relative
956
+ # paths (nokogiri/libxml2 evaluate them fine): a relative
957
+ # expression from the document node is equivalent to the
958
+ # absolute form — the document's only name-testable child
959
+ # is the root — so rewrite instead of losing the match.
960
+ if native_node.is_a?(::LibXML::XML::Document) &&
961
+ expression.match?(/\A[A-Za-z_*@]/)
962
+ expression = "/#{expression}"
963
+ end
964
+
952
965
  # Build namespace context for LibXML
953
966
  # LibXML requires ALL prefixes in the XPath to be registered
954
967
  ns_context = build_xpath_namespaces(native_node, namespaces)
@@ -626,7 +626,11 @@ module Moxml
626
626
  end
627
627
 
628
628
  def xpath(node, expression, namespaces = nil)
629
- result = node.xpath(expression, namespaces)
629
+ # An explicit bindings hash — even EMPTY — makes nokogiri
630
+ # register a prefix context, which disables libxml2's
631
+ # literal xmlns: default-namespace handling. Only pass the
632
+ # hash when the caller actually bound something.
633
+ result = namespaces.nil? || namespaces.empty? ? node.xpath(expression) : node.xpath(expression, namespaces)
630
634
  # Adapter contract: Array<native> | LazyNodeSet | scalar
631
635
  # (count(), string-length(), boolean functions return
632
636
  # scalars). The native node-set passes through lazily —
@@ -604,10 +604,13 @@ module Moxml
604
604
  end
605
605
 
606
606
  def xpath(node, expression, namespaces = {})
607
+ # XPath.match for every expression: get_elements only
608
+ # handles simple child-name paths — attribute terminals,
609
+ # absolute paths and predicates silently degrade there.
607
610
  if namespaces && !namespaces.empty?
608
- ::REXML::XPath.match(node, expression, namespaces)
611
+ ::REXML::XPath.match(node, expression, namespaces).to_a
609
612
  else
610
- node.get_elements(expression).to_a
613
+ ::REXML::XPath.match(node, expression).to_a
611
614
  end
612
615
  rescue ::REXML::ParseException => e
613
616
  raise Moxml::XPathError.new(
@@ -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.138"
5
5
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Cross-adapter XPath idioms that nokogiri consumers rely on:
6
+ #
7
+ # - The literal xmlns: prefix resolves against the default namespace
8
+ # (the libxml2 quirk that is nokogiri's documented way to match
9
+ # default-namespaced elements; leptris carries it since engine
10
+ # 1.9.359 / leptris-ruby#469; moxml's own engine always has).
11
+ # - Attribute-terminal steps work in every path position
12
+ # (leptris-ruby#458's table: //a/@id, //@id, r/a/@id,
13
+ # //*[f()]/@id — fixed by the engine's VM path work).
14
+ RSpec.describe "XPath idioms across adapters" do
15
+ let(:default_ns_xml) do
16
+ '<svgmap xmlns="http://www.example.org/doc"><target href="a"/></svgmap>'
17
+ end
18
+ let(:plain_xml) { '<r><a id="x"><b/></a></r>' }
19
+
20
+ Moxml::Adapter::AVAILABLE_ADAPTERS.each do |adapter_name|
21
+ next if adapter_name == :headed_ox # same engine as :ox
22
+
23
+ context "with #{adapter_name}" do
24
+ let(:ctx) { Moxml.new(adapter_name) }
25
+
26
+ before do
27
+ if adapter_name == :leptris &&
28
+ Gem::Version.new(Leptris::VERSION) < Gem::Version.new("1.9.359.0")
29
+ skip "binding #{Leptris::VERSION} predates the xmlns: fix (1.9.359)"
30
+ end
31
+ if adapter_name == :oga
32
+ # oga's own Ruby XPath engine has no xmlns: default-ns
33
+ # support (third-party; explicit bindings work).
34
+ skip "oga's XPath engine does not implement the xmlns: idiom"
35
+ end
36
+ end
37
+
38
+ it "matches default-namespaced elements via the xmlns: prefix" do
39
+ doc = ctx.parse(default_ns_xml)
40
+ targets = doc.root.xpath("./xmlns:target")
41
+ expect(targets.length).to eq(1)
42
+ expect(targets.first.name).to eq("target")
43
+ end
44
+
45
+ it "keeps explicit namespace bindings winning over the idiom" do
46
+ doc = ctx.parse(default_ns_xml)
47
+ targets = doc.root.xpath("./xmlns:target", "xmlns" => "urn:bogus")
48
+ expect(targets).to be_empty
49
+ end
50
+
51
+ it "resolves attribute-terminal steps in every path position" do
52
+ doc = ctx.parse(plain_xml)
53
+ expect(doc.xpath("//a/@id").first&.value).to eq("x")
54
+ expect(doc.xpath("//@id").first&.value).to eq("x")
55
+ expect(doc.xpath("r/a/@id").first&.value).to eq("x")
56
+ expect(
57
+ doc.xpath("//*[local-name()='a']/@id").first&.value,
58
+ ).to eq("x")
59
+ end
60
+ end
61
+ end
62
+ 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"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # leptris-ruby#459 exposes the declaration as a Document#children
6
+ # entry (binding 1.9.360). The serialize child walk must skip it —
7
+ # the composed preamble owns the emission — or declaration-bearing
8
+ # round trips duplicate the line (1.9.359) or raise (1.9.360's
9
+ # 0-arity Declaration#to_xml under the options-passing call).
10
+ RSpec.describe "leptris document declaration child" do
11
+ let(:ctx) { Moxml.new(:leptris) }
12
+ let(:xml) { %(<?xml version="1.0" encoding="UTF-8"?>\n<documents></documents>) }
13
+
14
+ before do
15
+ unless Gem::Version.new(Leptris::VERSION) >= Gem::Version.new("1.9.360.0")
16
+ skip "binding #{Leptris::VERSION} predates the declaration child (1.9.360)"
17
+ end
18
+ end
19
+
20
+ it "round trips byte-identical with exactly one declaration" do
21
+ doc = ctx.parse(xml)
22
+ out = doc.to_xml
23
+
24
+ expect(out).to eq("#{xml}\n")
25
+ expect(out.scan("<?xml").length).to eq(1)
26
+ end
27
+
28
+ it "never lists the declaration among document children" do
29
+ doc = ctx.parse(xml)
30
+ first = doc.children.to_a
31
+ second = doc.children.to_a
32
+
33
+ expect(first.map(&:node_type)).to eq(%i[element])
34
+ expect(second.map(&:node_type)).to eq(first.map(&:node_type))
35
+ expect(doc.to_xml).to eq("#{xml}\n")
36
+ end
37
+
38
+ it "keeps declaration-less documents unchanged" do
39
+ doc = ctx.parse("<r/>")
40
+ expect(doc.to_xml).to eq("<r></r>\n")
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.136
4
+ version: 0.5.138
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
@@ -314,6 +313,7 @@ files:
314
313
  - spec/consistency/eol_normalization_spec.rb
315
314
  - spec/consistency/html_doctype_spec.rb
316
315
  - spec/consistency/round_trip_spec.rb
316
+ - spec/consistency/xpath_idioms_spec.rb
317
317
  - spec/consistency/xslt_spec.rb
318
318
  - spec/examples/README.md
319
319
  - spec/examples/attribute_examples_spec.rb
@@ -453,6 +453,7 @@ files:
453
453
  - spec/moxml/adapter/leptris_attribute_snapshot_spec.rb
454
454
  - spec/moxml/adapter/leptris_binding_cap_spec.rb
455
455
  - spec/moxml/adapter/leptris_doc_parts_spec.rb
456
+ - spec/moxml/adapter/leptris_document_declaration_spec.rb
456
457
  - spec/moxml/adapter/leptris_parse_skips_spec.rb
457
458
  - spec/moxml/adapter/leptris_record_driver_spec.rb
458
459
  - spec/moxml/adapter/leptris_sax_dup_attr_spec.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