moxml 0.5.146 → 0.5.147

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +35 -0
  3. data/README.adoc +19 -1
  4. data/docs/_pages/adapter-compatibility.adoc +7 -0
  5. data/docs/_pages/divergences.adoc +90 -0
  6. data/docs/_references/index.adoc +9 -0
  7. data/docs/_references/processing-model.adoc +211 -0
  8. data/docs/index.adoc +9 -0
  9. data/lib/moxml/adapter/leptris.rb +30 -2
  10. data/lib/moxml/adapter/libxml.rb +5 -0
  11. data/lib/moxml/adapter/oga.rb +10 -0
  12. data/lib/moxml/document.rb +9 -0
  13. data/lib/moxml/version.rb +1 -1
  14. data/spec/consistency/divergence_register_spec.rb +109 -0
  15. data/spec/consistency/document_text_spec.rb +30 -0
  16. data/spec/consistency/eol_normalization_spec.rb +8 -8
  17. data/spec/consistency/loud_degradation_spec.rb +44 -0
  18. data/spec/consistency/model_coverage_audit_spec.rb +113 -0
  19. data/spec/consistency/spec_constant_uniqueness_spec.rb +29 -0
  20. data/spec/consistency/xpath_idioms_spec.rb +1 -1
  21. data/spec/moxml/adapter/leptris_detached_text_spec.rb +34 -0
  22. data/spec/moxml/adapter/leptris_parse_lint_spec.rb +35 -0
  23. data/spec/moxml/adapter/shared_examples/adapter_contract.rb +3 -3
  24. data/spec/moxml/c14n/adapter_delegation_spec.rb +3 -3
  25. data/spec/moxml/c14n/comments_pis_spec.rb +2 -2
  26. data/spec/moxml/c14n/subset_spec.rb +4 -4
  27. data/spec/moxml/c14n/w3c_examples_spec.rb +3 -1
  28. data/spec/moxml/declaration_preservation_spec.rb +2 -2
  29. data/spec/moxml/element_add_element_spec.rb +3 -3
  30. data/spec/moxml/element_attribute_pairs_spec.rb +2 -2
  31. data/spec/moxml/node_spec.rb +1 -1
  32. data/spec/moxml/plan_conformance_spec.rb +3 -3
  33. data/spec/moxml/sax_entity_parity_spec.rb +3 -3
  34. data/spec/moxml/sax_spec.rb +7 -7
  35. data/spec/moxml/struct_plan_spec.rb +2 -2
  36. data/spec/moxml/xpath_capabilities_spec.rb +13 -13
  37. metadata +10 -1
@@ -20,7 +20,7 @@ require "spec_helper"
20
20
  # binding release vending that engine rides
21
21
  # (gate: binding 1.9.240).
22
22
  RSpec.describe "XML line-ending normalization" do
23
- ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
23
+ EOL_NORMALIZATION_ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
24
24
 
25
25
  def with_adapter(name)
26
26
  ctx = Moxml.new(name)
@@ -37,10 +37,10 @@ RSpec.describe "XML line-ending normalization" do
37
37
  end
38
38
 
39
39
  it "normalizes CRLF and lone CR to LF in text content" do
40
- ADAPTERS.each do |name|
40
+ EOL_NORMALIZATION_ADAPTERS.each do |name|
41
41
  next unless leptris_fixed?(name)
42
42
 
43
- skip "oga preserves raw CR (upstream)" if name == :oga
43
+ skip "D-02: oga preserves raw CR (upstream)" if name == :oga
44
44
 
45
45
  with_adapter(name) do |ctx|
46
46
  text = ctx.parse("<r>a\r\nb\rc</r>").root.children.first
@@ -50,10 +50,10 @@ RSpec.describe "XML line-ending normalization" do
50
50
  end
51
51
 
52
52
  it "normalizes EOL inside CDATA content" do
53
- ADAPTERS.each do |name|
53
+ EOL_NORMALIZATION_ADAPTERS.each do |name|
54
54
  next unless leptris_fixed?(name)
55
55
 
56
- skip "oga preserves raw CR (upstream)" if name == :oga
56
+ skip "D-02: oga preserves raw CR (upstream)" if name == :oga
57
57
 
58
58
  with_adapter(name) do |ctx|
59
59
  cdata = ctx.parse("<r><![CDATA[a\r\nb]]></r>").root.children.first
@@ -63,11 +63,11 @@ RSpec.describe "XML line-ending normalization" do
63
63
  end
64
64
 
65
65
  it "preserves CR from character references" do
66
- ADAPTERS.each do |name|
66
+ EOL_NORMALIZATION_ADAPTERS.each do |name|
67
67
  next unless leptris_fixed?(name)
68
68
 
69
- skip "ox collapses &#13; to LF (upstream)" if name == :ox
70
- skip "oga preserves raw CR (upstream)" if name == :oga
69
+ skip "D-03: ox collapses &#13; to LF (upstream)" if name == :ox
70
+ skip "D-02: oga preserves raw CR (upstream)" if name == :oga
71
71
 
72
72
  with_adapter(name) do |ctx|
73
73
  text = ctx.parse("<r>a&#13;b</r>").root.children.first
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Loud degradation (processing model, "Errors and loud degradation"):
6
+ # a capability an engine cannot deliver must answer the contract or
7
+ # raise the named error — never a divergent answer. Probed across all
8
+ # available adapters; the capability lists mirror the adapters'
9
+ # actual engine backing.
10
+ RSpec.describe "loud capability contracts" do
11
+ available = Moxml::Adapter::AVAILABLE_ADAPTERS
12
+ xslt_capable = %i[nokogiri leptris] & available
13
+ html_capable = %i[nokogiri leptris] & available
14
+
15
+ let(:sheet) do
16
+ %(<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">) \
17
+ "<xsl:template match='/'><o/></xsl:template></xsl:stylesheet>"
18
+ end
19
+
20
+ (available - xslt_capable).each do |name|
21
+ it "raises NotImplementedError for XSLT on #{name}" do
22
+ ctx = Moxml.new(name)
23
+ expect { ctx.xslt(sheet) }.to raise_error(Moxml::NotImplementedError)
24
+ end
25
+ end
26
+
27
+ (available - html_capable).each do |name|
28
+ it "raises NotImplementedError for to_xhtml on #{name}" do
29
+ doc = Moxml.new(name).parse("<r><a/></r>")
30
+ expect { doc.to_xhtml }.to raise_error(Moxml::NotImplementedError)
31
+ end
32
+
33
+ it "raises AdapterError for to_html on #{name}" do
34
+ doc = Moxml.new(name).parse("<r><a/></r>")
35
+ expect { doc.to_html }.to raise_error(Moxml::AdapterError)
36
+ end
37
+ end
38
+
39
+ xslt_capable.each do |name|
40
+ it "compiles a stylesheet on #{name}" do
41
+ expect(Moxml.new(name).xslt(sheet)).to be_a(Moxml::XSLT::Stylesheet)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Model coverage matrix (TODO.model/03): every face family of the
6
+ # processing model is governed for every adapter — covered by a pin
7
+ # (the consistency/contract spec that asserts equivalence), exempt via
8
+ # a registered divergence (adapter => D-ID), or loud (adapter =>
9
+ # the named error class). An unpinned family is an ungoverned cell:
10
+ # this audit fails until it has one of the three.
11
+ # See docs/_references/processing-model.adoc for the model itself.
12
+ RSpec.describe "model coverage matrix" do
13
+ MODEL_MATRIX = { # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
14
+ "document order and children listing" => {
15
+ pin: "spec/consistency/adapter_parity_spec.rb",
16
+ },
17
+ "name resolution" => {
18
+ pin: "spec/consistency/xpath_idioms_spec.rb",
19
+ },
20
+ "namespace resolution and bindings normalization" => {
21
+ pin: "spec/consistency/xpath_idioms_spec.rb",
22
+ },
23
+ "attribute access" => {
24
+ pin: "spec/moxml/attribute_resolver_spec.rb",
25
+ },
26
+ "text/content/raw_content" => {
27
+ pin: "spec/moxml/text_spec.rb",
28
+ },
29
+ "parsing modes and diagnostics" => {
30
+ pin: "spec/moxml/document_parse_diagnostics_spec.rb",
31
+ },
32
+ "EOL normalization" => {
33
+ pin: "spec/consistency/eol_normalization_spec.rb",
34
+ exempt: { oga: "D-02", ox: "D-03" },
35
+ },
36
+ "entity modes" => {
37
+ pin: "spec/moxml/entity_spec.rb",
38
+ },
39
+ "XPath evaluation and declared idioms" => {
40
+ pin: "spec/consistency/xpath_idioms_spec.rb",
41
+ exempt: { oga: "D-01" },
42
+ },
43
+ "serialization shapes (xml/html/xhtml)" => {
44
+ pin: "spec/moxml/xhtml_output_spec.rb",
45
+ },
46
+ "indentation knobs" => {
47
+ pin: "spec/moxml/to_html_spec.rb",
48
+ exempt: { oga: "D-09", ox: "D-09", headed_ox: "D-09",
49
+ libxml: "D-09", leptris: "D-09" },
50
+ },
51
+ "canonicalization" => {
52
+ pin: "spec/moxml/c14n/adapter_delegation_spec.rb",
53
+ exempt: { oga: "D-04", ox: "D-05", headed_ox: "D-05" },
54
+ },
55
+ "XSLT" => {
56
+ pin: "spec/consistency/xslt_spec.rb",
57
+ loud: { oga: "Moxml::NotImplementedError",
58
+ ox: "Moxml::NotImplementedError",
59
+ headed_ox: "Moxml::NotImplementedError",
60
+ rexml: "Moxml::NotImplementedError",
61
+ libxml: "Moxml::NotImplementedError" },
62
+ },
63
+ "SAX" => {
64
+ pin: "spec/moxml/sax_spec.rb",
65
+ },
66
+ "materialization" => {
67
+ pin: "spec/moxml/plan_conformance_spec.rb",
68
+ },
69
+ "loud capability contracts" => {
70
+ pin: "spec/consistency/loud_degradation_spec.rb",
71
+ },
72
+ }.freeze
73
+
74
+ def register_ids
75
+ File.readlines("docs/_pages/divergences.adoc")
76
+ .filter_map { |line| line[/\A\|\s*(D-\d+)\s*$/, 1] }
77
+ .uniq
78
+ end
79
+
80
+ it "governs every face family with a pin" do
81
+ unpinned = MODEL_MATRIX.reject { |_, cell| cell[:pin] }.keys
82
+ expect(unpinned).to be_empty,
83
+ "ungoverned face families (no pin): " \
84
+ "#{unpinned.join(', ')}"
85
+ end
86
+
87
+ it "points every pin at an existing spec" do
88
+ missing = MODEL_MATRIX.filter_map do |family, cell|
89
+ family unless cell[:pin] && File.exist?(cell[:pin])
90
+ end
91
+ expect(missing).to be_empty, "pins missing on disk: #{missing.join(', ')}"
92
+ end
93
+
94
+ it "exempts adapters only via registered divergences" do
95
+ unknown = MODEL_MATRIX.flat_map do |family, cell|
96
+ (cell[:exempt] || {}).filter_map do |adapter, id|
97
+ "#{family}: #{adapter} -> #{id}" unless register_ids.include?(id)
98
+ end
99
+ end
100
+ expect(unknown).to be_empty,
101
+ "exemptions cite unregistered IDs: #{unknown.join('; ')}"
102
+ end
103
+
104
+ it "declares loud cells only with named Moxml errors" do
105
+ bad = MODEL_MATRIX.flat_map do |family, cell|
106
+ (cell[:loud] || {}).filter_map do |adapter, error|
107
+ "#{family}: #{adapter} -> #{error}" unless error.start_with?("Moxml::")
108
+ end
109
+ end
110
+ expect(bad).to be_empty,
111
+ "loud cells must name Moxml errors: #{bad.join('; ')}"
112
+ end
113
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Checker integrity (TODO.model/06): top-level constant names in spec
6
+ # files all resolve through Object inside example blocks, so the same
7
+ # name defined by two files makes the last-loaded file win — the 0.5.143
8
+ # CORPUS collision made the battery fail ~50% of runs with a foreign
9
+ # corpus. This guard kills the class.
10
+ RSpec.describe "spec-file top-level constant uniqueness" do
11
+ it "defines each constant name in at most one spec/support file" do
12
+ seen = {}
13
+
14
+ Dir["spec/**/*.rb"].each do |path|
15
+ File.readlines(path).each_with_index do |line, i|
16
+ match = line.match(/\A\s*([A-Z][A-Z0-9_]*)\s*=[^=]/)
17
+ next unless match
18
+
19
+ name = match[1]
20
+ expect(seen)
21
+ .not_to have_key(name),
22
+ "#{name} defined at #{seen[name]} and #{path}:#{i + 1} " \
23
+ "— constants inside example blocks collide through " \
24
+ "Object; give one file a unique name"
25
+ seen[name] = "#{path}:#{i + 1}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -42,7 +42,7 @@ RSpec.describe "XPath idioms across adapters" do
42
42
  if adapter_name == :oga
43
43
  # oga's own Ruby XPath engine has no xmlns: default-ns
44
44
  # support (third-party; explicit bindings work).
45
- skip "oga's XPath engine does not implement the xmlns: idiom"
45
+ skip "D-01: oga's XPath engine does not implement the xmlns: idiom"
46
46
  end
47
47
  end
48
48
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Issue #401: Text#content= on a detached (parentless) node answered
6
+ # the engine's bare "Invalid argument" with no indication of the cause.
7
+ # The leptris adapter now re-raises it as a named Moxml error carrying
8
+ # the detachment. Created-unattached nodes work (probe), so only the
9
+ # failing detachment is re-raised.
10
+ RSpec.describe "detached text content= names the cause" do
11
+ let(:ctx) { Moxml.new(:leptris) }
12
+
13
+ it "raises a named Moxml error for a removed text node" do
14
+ doc = ctx.parse("<r><a>x</a><b>y</b></r>")
15
+ text = doc.root.children[0].children[0]
16
+ text.remove
17
+
18
+ expect { text.content = "z" }
19
+ .to raise_error(Moxml::Error, /detached \(parentless\).*issue #401/)
20
+ end
21
+
22
+ it "keeps created-unattached nodes mutable" do
23
+ text = ctx.create_document.create_text("x")
24
+ text.content = "y"
25
+ expect(text.content).to eq("y")
26
+ end
27
+
28
+ it "keeps attached nodes mutable" do
29
+ doc = ctx.parse("<r><a>x</a></r>")
30
+ text = doc.root.children[0].children[0]
31
+ text.content = "z"
32
+ expect(text.content).to eq("z")
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Issue #404: Context#parse_html forwards collect_errors (and mode) to
6
+ # the leptris flags entry (leptris#1771's §13.2.5 parse-lint) — the
7
+ # recorded error tokens surface through parse_diagnostics. Face-probed:
8
+ # bindings predating the kwarg skip.
9
+ RSpec.describe "parse_html collect_errors forwarding" do
10
+ let(:ctx) { Moxml.new(:leptris) }
11
+ let(:src) { %(<p class="x>unclosed</p><a href=><p>a<b</p>) }
12
+
13
+ before do
14
+ adapter = ctx.config.adapter
15
+ supported = adapter.singleton_class.const_get(:COLLECT_ERRORS_NATIVE)
16
+ skip "binding lacks the collect_errors kwarg (leptris#1771)" unless supported
17
+ end
18
+
19
+ it "surfaces parse-lint errors through parse_diagnostics" do
20
+ doc = ctx.parse_html(src, collect_errors: true)
21
+ diags = doc.parse_diagnostics
22
+ expect(diags).not_to be_empty
23
+ expect(diags.first).to have_key(:message)
24
+ end
25
+
26
+ it "keeps the default lane silent (no option, no diagnostics)" do
27
+ doc = ctx.parse_html(src)
28
+ expect(doc.parse_diagnostics).to be_empty
29
+ end
30
+
31
+ it "forwards through Context#parse_html options" do
32
+ doc = ctx.parse_html(src, { collect_errors: true })
33
+ expect(doc.parse_diagnostics).not_to be_empty
34
+ end
35
+ end
@@ -343,13 +343,13 @@ RSpec.shared_examples "xml adapter" do
343
343
  it "respects indentation settings" do
344
344
  if described_class.name.include?("Oga") &&
345
345
  !described_class.indents_output?
346
- pending("Oga does not support indentation settings")
346
+ pending("D-09: Oga does not support indentation settings")
347
347
  end
348
348
  if described_class.name.include?("Libxml")
349
- skip("LibXML serialization does not support indentation (documented limitation)")
349
+ skip("D-09: LibXML serialization does not support indentation (documented limitation)")
350
350
  end
351
351
  if described_class.name.include?("Ox")
352
- skip("Ox does not support indentation settings (documented limitation)")
352
+ skip("D-09: Ox does not support indentation settings (documented limitation)")
353
353
  end
354
354
 
355
355
  unindented = described_class.serialize(doc, indent: 0)
@@ -58,9 +58,9 @@ RSpec.describe "Moxml::C14n cross-adapter canonicalization" do
58
58
 
59
59
  describe "document-level nodes keep document order" do
60
60
  # oga surfaces the XML declaration as a <?xml?> PI and keeps the
61
- # separator space in PI content; ox/headed_ox drop PI content on
62
- # parse. Both are pre-existing parse-layer divergences, out of
63
- # scope here.
61
+ # separator space in PI content (D-04); ox/headed_ox drop PI
62
+ # content on parse (D-05). Both are pre-existing parse-layer
63
+ # divergences, out of scope here.
64
64
  let(:adapters) { Moxml::Adapter::AVAILABLE_ADAPTERS - %i[oga ox headed_ox] }
65
65
 
66
66
  it "canonicalizes a leading PI before the root element" do
@@ -53,13 +53,13 @@ RSpec.describe "C14N comments and PIs" do
53
53
  # PIs, and the DataModel.from_document path drops them. Fixing requires
54
54
  # adapter-level work and is tracked separately.
55
55
  it "renders PIs outside the document element (KNOWN LIMITATION)" do
56
- skip "document-level PI rendering not yet implemented"
56
+ skip "D-08: document-level PI rendering not yet implemented"
57
57
  end
58
58
  end
59
59
 
60
60
  describe "comments at document level" do
61
61
  it "renders document-level comments when with_comments: true (KNOWN LIMITATION)" do
62
- skip "document-level comment rendering not yet implemented"
62
+ skip "D-08: document-level comment rendering not yet implemented"
63
63
  end
64
64
  end
65
65
  end
@@ -63,7 +63,7 @@ RSpec.describe "Moxml::C14n subset canonicalization" do
63
63
  next if label == "text selection" && adapter_name == :rexml
64
64
 
65
65
  # rexml's adapter xpath does not return text nodes for
66
- # text() node tests (pre-existing capability gap).
66
+ # text() node tests (D-07, pre-existing capability gap).
67
67
 
68
68
  it "canonicalizes #{label}" do
69
69
  doc = ctx.parse(xml)
@@ -72,9 +72,9 @@ RSpec.describe "Moxml::C14n subset canonicalization" do
72
72
  end
73
73
 
74
74
  # ox/headed_ox strip comment content padding at the parse
75
- # layer (pre-existing, same family as their PI content loss);
76
- # only the rendered-comment case depends on it. rexml's
77
- # comment() selection gap also only affects this case.
75
+ # layer (D-06, pre-existing, same family as their PI content
76
+ # loss); only the rendered-comment case depends on it. rexml's
77
+ # comment() selection gap (D-07) also only affects this case.
78
78
  unless COMMENTS_PADDING_LOSS.include?(adapter_name) ||
79
79
  COMMENT_SELECTION_GAP.include?(adapter_name)
80
80
  it COMMENTS_CASE[0] do
@@ -55,7 +55,8 @@ RSpec.describe "Moxml::C14n W3C REC-xml-c14n examples" do
55
55
 
56
56
  W3C_FULL_FORMS.each_with_index do |(label, xml, expected), idx|
57
57
  # ox/headed_ox strip inter-element whitespace, expand &#xD;
58
- # and pad comments at parse; oga keeps the PI separator space.
58
+ # and pad comments at parse (D-05, D-06); oga keeps the PI
59
+ # separator space (D-04).
59
60
  skip_shapes = { ox: [0, CR_SHAPE, PI_SHAPE],
60
61
  headed_ox: [0, CR_SHAPE, PI_SHAPE],
61
62
  oga: [PI_SHAPE] }[adapter_name] || []
@@ -72,6 +73,7 @@ RSpec.describe "Moxml::C14n W3C REC-xml-c14n examples" do
72
73
 
73
74
  describe "W3C §3 document-subset whitespace" do
74
75
  Moxml::Adapter::AVAILABLE_ADAPTERS.each do |adapter_name|
76
+ # ox/headed_ox strip inter-element whitespace at parse (D-06)
75
77
  next if WHITESPACE_SHAPES.include?(adapter_name)
76
78
 
77
79
  it "renders selected nodes only, whitespace runs in place (#{adapter_name})" do
@@ -4,9 +4,9 @@ require "spec_helper"
4
4
 
5
5
  RSpec.describe "XML Declaration Preservation" do
6
6
  # Test with all available adapters
7
- ADAPTERS = %i[nokogiri oga rexml ox libxml headed_ox].freeze
7
+ DECLARATION_SPECS_ADAPTERS = %i[nokogiri oga rexml ox libxml headed_ox].freeze
8
8
 
9
- ADAPTERS.each do |adapter_name|
9
+ DECLARATION_SPECS_ADAPTERS.each do |adapter_name|
10
10
  context "with #{adapter_name} adapter" do
11
11
  let(:context) { Moxml.new(adapter_name) }
12
12
 
@@ -9,7 +9,7 @@ require "spec_helper"
9
9
  # the contract: a missing base default NoMethodErrors on non-face
10
10
  # adapters, a broken fallback loses attributes or the attach.
11
11
  RSpec.describe Moxml::Element do
12
- ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
12
+ ELEMENT_ADD_ELEMENT_ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
13
13
 
14
14
  def with_adapter(name)
15
15
  ctx = Moxml.new(name)
@@ -19,7 +19,7 @@ RSpec.describe Moxml::Element do
19
19
  end
20
20
 
21
21
  it "attaches a child with all attributes on every available adapter" do
22
- ADAPTERS.each do |name|
22
+ ELEMENT_ADD_ELEMENT_ADAPTERS.each do |name|
23
23
  with_adapter(name) do |ctx|
24
24
  doc = ctx.parse("<catalog/>")
25
25
  root = doc.root
@@ -35,7 +35,7 @@ RSpec.describe Moxml::Element do
35
35
  end
36
36
 
37
37
  it "nests under a non-root element with empty attrs" do
38
- ADAPTERS.each do |name|
38
+ ELEMENT_ADD_ELEMENT_ADAPTERS.each do |name|
39
39
  with_adapter(name) do |ctx|
40
40
  doc = ctx.parse("<catalog><record/></catalog>")
41
41
  record = doc.root.children.first
@@ -12,7 +12,7 @@ RSpec.describe Moxml::Element do
12
12
  '<catalog><record id="r0" kind="k0"><field name="f0" unit="u0">v</field></record></catalog>'
13
13
  end
14
14
 
15
- ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
15
+ ELEMENT_ATTRIBUTE_PAIRS_ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
16
16
 
17
17
  def with_adapter(name)
18
18
  ctx = Moxml.new(name)
@@ -22,7 +22,7 @@ RSpec.describe Moxml::Element do
22
22
  end
23
23
 
24
24
  it "matches the node path on every available adapter" do
25
- results = ADAPTERS.filter_map do |name|
25
+ results = ELEMENT_ATTRIBUTE_PAIRS_ADAPTERS.filter_map do |name|
26
26
  with_adapter(name) do |ctx|
27
27
  root = ctx.parse(xml).root
28
28
  [name, root.attribute_pairs]
@@ -157,7 +157,7 @@ RSpec.describe Moxml::Node do
157
157
 
158
158
  it "locates the node via its path" do
159
159
  # Ox translates only a limited XPath subset (no positional predicates)
160
- skip "ox xpath translation does not support positional predicates" if adapter_name == :ox
160
+ skip "D-12: ox xpath translation does not support positional predicates" if adapter_name == :ox
161
161
 
162
162
  expect(doc.at_xpath(title.path)).to eq(title)
163
163
  end
@@ -27,10 +27,10 @@ RSpec.describe "Moxml::Plan adapter conformance" do
27
27
  end
28
28
  end
29
29
 
30
- ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
30
+ PLAN_CONFORMANCE_ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
31
31
 
32
32
  it "executes on every available adapter" do
33
- available = ADAPTERS.select do |a|
33
+ available = PLAN_CONFORMANCE_ADAPTERS.select do |a|
34
34
  ctx = Moxml.new(a)
35
35
  ctx.parse(xml)
36
36
  true
@@ -63,7 +63,7 @@ RSpec.describe "Moxml::Plan adapter conformance" do
63
63
  on("field") { |attrs, text, _| [attrs["name"], text] }
64
64
  end
65
65
 
66
- ADAPTERS.each do |a|
66
+ PLAN_CONFORMANCE_ADAPTERS.each do |a|
67
67
  begin
68
68
  ctx = Moxml.new(a)
69
69
  ctx.parse(xml)
@@ -32,7 +32,7 @@ module SaxEntityParityFixtures
32
32
  end
33
33
  end
34
34
 
35
- ADAPTERS = %i[nokogiri ox oga rexml libxml headed_ox].freeze
35
+ SAX_ENTITY_PARITY_ADAPTERS = %i[nokogiri ox oga rexml libxml headed_ox].freeze
36
36
 
37
37
  # Each row: [input XML, decoded attribute value, expected attribute
38
38
  # value as it appears in the serialized XML]. The serialized form
@@ -85,7 +85,7 @@ module SaxEntityParityFixtures
85
85
  end
86
86
 
87
87
  RSpec.describe "SAX/DOM entity parity" do
88
- SaxEntityParityFixtures::ADAPTERS.each do |adapter|
88
+ SaxEntityParityFixtures::SAX_ENTITY_PARITY_ADAPTERS.each do |adapter|
89
89
  context "with #{adapter} adapter" do
90
90
  let(:ctx) { Moxml.new(adapter) }
91
91
 
@@ -349,7 +349,7 @@ RSpec.describe "SAX/DOM entity parity" do
349
349
 
350
350
  SaxEntityParityFixtures::ATTRIBUTE_CASES.each do |label, (xml, _expected, expected_serialized)|
351
351
  it "every adapter emits the same attribute form: #{label}" do
352
- results = SaxEntityParityFixtures::ADAPTERS.to_h { |a| [a, serialize_via(a, xml)] }
352
+ results = SaxEntityParityFixtures::SAX_ENTITY_PARITY_ADAPTERS.to_h { |a| [a, serialize_via(a, xml)] }
353
353
  results.each do |a, out|
354
354
  expect(out).to(
355
355
  include(%(x="#{expected_serialized}")),
@@ -413,7 +413,7 @@ RSpec.describe Moxml::SAX do
413
413
  it "documents CDATA limitation" do
414
414
  # Ox SAX does not have separate CDATA events
415
415
  # All CDATA content is delivered as text() events
416
- skip "Ox SAX does not support separate CDATA events"
416
+ skip "D-10: Ox SAX does not support separate CDATA events"
417
417
 
418
418
  xml_with_cdata = "<root><![CDATA[special content]]></root>"
419
419
 
@@ -428,7 +428,7 @@ RSpec.describe Moxml::SAX do
428
428
 
429
429
  it "documents comment limitation" do
430
430
  # Ox SAX does not have separate comment events
431
- skip "Ox SAX does not support separate comment events"
431
+ skip "D-10: Ox SAX does not support separate comment events"
432
432
 
433
433
  xml_with_comment = "<root><!-- comment --><data>content</data></root>"
434
434
 
@@ -443,7 +443,7 @@ RSpec.describe Moxml::SAX do
443
443
 
444
444
  it "documents processing instruction limitation" do
445
445
  # Ox SAX does not have separate PI events
446
- skip "Ox SAX does not support separate processing instruction events"
446
+ skip "D-10: Ox SAX does not support separate processing instruction events"
447
447
 
448
448
  xml_with_pi = '<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>'
449
449
 
@@ -714,7 +714,7 @@ RSpec.describe Moxml::SAX do
714
714
  end
715
715
 
716
716
  it "handles errors gracefully" do
717
- skip "Oga SAX parser may be more lenient with malformed XML"
717
+ skip "D-11: Oga SAX parser may be more lenient with malformed XML"
718
718
 
719
719
  invalid_xml = "<book><title>Unclosed"
720
720
 
@@ -1024,7 +1024,7 @@ RSpec.describe Moxml::SAX do
1024
1024
 
1025
1025
  it "documents CDATA limitation (inherited from Ox)" do
1026
1026
  # HeadedOx inherits Ox's SAX implementation, which does not have separate CDATA events
1027
- skip "HeadedOx SAX (inherited from Ox) does not support separate CDATA events"
1027
+ skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate CDATA events"
1028
1028
 
1029
1029
  xml_with_cdata = "<root><![CDATA[special content]]></root>"
1030
1030
 
@@ -1038,7 +1038,7 @@ RSpec.describe Moxml::SAX do
1038
1038
 
1039
1039
  it "documents comment limitation (inherited from Ox)" do
1040
1040
  # HeadedOx inherits Ox's SAX implementation, which does not have separate comment events
1041
- skip "HeadedOx SAX (inherited from Ox) does not support separate comment events"
1041
+ skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate comment events"
1042
1042
 
1043
1043
  xml_with_comment = "<root><!-- comment --><data>content</data></root>"
1044
1044
 
@@ -1052,7 +1052,7 @@ RSpec.describe Moxml::SAX do
1052
1052
 
1053
1053
  it "documents processing instruction limitation (inherited from Ox)" do
1054
1054
  # HeadedOx inherits Ox's SAX implementation, which does not have separate PI events
1055
- skip "HeadedOx SAX (inherited from Ox) does not support separate processing instruction events"
1055
+ skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate processing instruction events"
1056
1056
 
1057
1057
  xml_with_pi = '<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>'
1058
1058
 
@@ -28,7 +28,7 @@ RSpec.describe Moxml::StructPlan do
28
28
  end
29
29
  end
30
30
 
31
- ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
31
+ STRUCT_PLAN_ADAPTERS = %i[leptris nokogiri ox oga rexml libxml].freeze # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
32
32
 
33
33
  def with_adapter(name)
34
34
  yield Moxml.new(name)
@@ -36,7 +36,7 @@ RSpec.describe Moxml::StructPlan do
36
36
  skip "#{name} unavailable"
37
37
  end
38
38
 
39
- ADAPTERS.each do |adapter_name|
39
+ STRUCT_PLAN_ADAPTERS.each do |adapter_name|
40
40
  describe "on #{adapter_name}" do
41
41
  it "materializes the same structs as Moxml::Plan" do
42
42
  with_adapter(adapter_name) do |ctx|