moxml 0.5.148 → 0.5.150

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +7 -2
  3. data/.github/workflows/dependent-tests.yml +4 -1
  4. data/.github/workflows/nightly-fuzz.yml +80 -0
  5. data/.github/workflows/performance-release.yml +79 -0
  6. data/.github/workflows/performance.yml +59 -0
  7. data/Rakefile +23 -0
  8. data/benchmark/README.md +106 -0
  9. data/benchmark/scoreboard.json +115 -0
  10. data/benchmark/scoreboard.rb +556 -0
  11. data/bin/generate-model-coverage +21 -0
  12. data/docs/_pages/adapter-compatibility.adoc +3 -3
  13. data/docs/_pages/divergences.adoc +88 -39
  14. data/docs/_pages/ecosystem.adoc +62 -0
  15. data/docs/_pages/headed-ox-limitations.adoc +1 -1
  16. data/docs/_pages/headed-ox.adoc +2 -2
  17. data/docs/_pages/index.adoc +4 -0
  18. data/docs/_pages/model-coverage.adoc +57 -0
  19. data/docs/_pages/performance.adoc +29 -0
  20. data/docs/_pages/why-moxml.adoc +87 -0
  21. data/docs/_references/processing-model.adoc +69 -3
  22. data/lib/compat/opal/moxml_boot.rb +4 -0
  23. data/lib/moxml/adapter/base.rb +73 -0
  24. data/lib/moxml/adapter/leptris.rb +293 -23
  25. data/lib/moxml/adapter/libxml.rb +179 -25
  26. data/lib/moxml/adapter/nokogiri.rb +10 -0
  27. data/lib/moxml/adapter/oga.rb +71 -9
  28. data/lib/moxml/adapter/ox.rb +34 -2
  29. data/lib/moxml/adapter/rexml.rb +82 -17
  30. data/lib/moxml/c14n/data_model.rb +8 -0
  31. data/lib/moxml/c14n/processor.rb +17 -8
  32. data/lib/moxml/context.rb +33 -1
  33. data/lib/moxml/document.rb +18 -4
  34. data/lib/moxml/node.rb +75 -8
  35. data/lib/moxml/node_set.rb +42 -1
  36. data/lib/moxml/reference/normalizer.rb +63 -0
  37. data/lib/moxml/reference/parser.rb +417 -0
  38. data/lib/moxml/reference/writer.rb +121 -0
  39. data/lib/moxml/reference.rb +205 -0
  40. data/lib/moxml/relaxng.rb +62 -0
  41. data/lib/moxml/sax/block_handler.rb +7 -0
  42. data/lib/moxml/sax/handler.rb +20 -0
  43. data/lib/moxml/version.rb +1 -1
  44. data/lib/moxml/xpath/compiler.rb +17 -2
  45. data/lib/moxml/xpath/ordering.rb +97 -0
  46. data/lib/moxml/xpath.rb +29 -0
  47. data/lib/moxml.rb +2 -0
  48. data/spec/consistency/adapter_parity_spec.rb +24 -0
  49. data/spec/consistency/citation_hygiene_spec.rb +30 -0
  50. data/spec/consistency/differential_taxonomy_spec.rb +117 -0
  51. data/spec/consistency/divergence_register_spec.rb +9 -1
  52. data/spec/consistency/eol_normalization_spec.rb +2 -0
  53. data/spec/consistency/loud_degradation_spec.rb +2 -0
  54. data/spec/consistency/metamorphic_properties_spec.rb +195 -0
  55. data/spec/consistency/metamorphic_spec.rb +138 -0
  56. data/spec/consistency/model_coverage_audit_spec.rb +144 -61
  57. data/spec/consistency/published_docs_spec.rb +49 -0
  58. data/spec/consistency/reference_differential_spec.rb +186 -0
  59. data/spec/consistency/xpath_idioms_spec.rb +34 -5
  60. data/spec/consistency/xslt_spec.rb +2 -0
  61. data/spec/integration/shared_examples/edge_cases.rb +0 -13
  62. data/spec/moxml/adapter/leptris_attribute_snapshot_spec.rb +10 -7
  63. data/spec/moxml/adapter/leptris_spec.rb +23 -3
  64. data/spec/moxml/adapter/ox_spec.rb +1 -1
  65. data/spec/moxml/adapter/shared_examples/adapter_contract.rb +0 -8
  66. data/spec/moxml/attribute_resolver_spec.rb +35 -0
  67. data/spec/moxml/c14n/adapter_delegation_spec.rb +35 -6
  68. data/spec/moxml/c14n/character_encoding_spec.rb +7 -13
  69. data/spec/moxml/c14n/comments_pis_spec.rb +35 -8
  70. data/spec/moxml/c14n/subset_spec.rb +2 -12
  71. data/spec/moxml/c14n/w3c_examples_spec.rb +2 -5
  72. data/spec/moxml/doctype_spec.rb +7 -0
  73. data/spec/moxml/document_parse_diagnostics_spec.rb +2 -0
  74. data/spec/moxml/element_indent_spec.rb +34 -0
  75. data/spec/moxml/entity_spec.rb +2 -0
  76. data/spec/moxml/node_spec.rb +0 -3
  77. data/spec/moxml/plan_conformance_spec.rb +2 -0
  78. data/spec/moxml/processing_instruction_spec.rb +9 -0
  79. data/spec/moxml/reference_opal_spec.rb +29 -0
  80. data/spec/moxml/reference_spec.rb +162 -0
  81. data/spec/moxml/relax_ng_spec.rb +67 -0
  82. data/spec/moxml/sax_spec.rb +47 -74
  83. data/spec/moxml/text_spec.rb +2 -0
  84. data/spec/moxml/to_html_spec.rb +2 -0
  85. data/spec/moxml/xhtml_output_spec.rb +2 -0
  86. data/spec/moxml/xpath/axes_spec.rb +2 -2
  87. data/spec/moxml/xpath_capabilities_spec.rb +11 -38
  88. data/spec/moxml/xsd_spec.rb +38 -0
  89. data/spec/support/input_generator.rb +154 -0
  90. data/spec/support/input_taxonomy.rb +219 -0
  91. data/spec/support/model_coverage.rb +159 -0
  92. data/spec/support/xml_generator.rb +54 -0
  93. metadata +32 -2
@@ -84,6 +84,13 @@ RSpec.describe Moxml::Doctype do
84
84
  expect(doctype.external_id).to be_nil
85
85
  expect(doctype.system_id).to be_nil
86
86
  end
87
+
88
+ it "preserves a DOCTYPE internal subset on serialization" do
89
+ xml = '<!DOCTYPE r [<!ENTITY x "v">]><r>&x;</r>'
90
+ parsed = ctx.parse(xml)
91
+
92
+ expect(parsed.to_xml(indent: 0)).to include('<!ENTITY x "v">')
93
+ end
87
94
  end
88
95
  end
89
96
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Parsing (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  # Document#parse_diagnostics (issue #271, option A — additive):
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Element-level indentation is a declared capability (model, Serialization;
6
+ # D-09): engines that cannot honor the knob raise the named AdapterError
7
+ # instead of answering unindented output. indent: 0 and the no-option
8
+ # path stay common — consumers use them for canonical shapes.
9
+ RSpec.describe "element-level serialization indentation" do
10
+ it "honors a positive indent on engines that support it" do
11
+ %i[nokogiri rexml].each do |name|
12
+ doc = Moxml.new(name).parse("<r><a/></r>")
13
+ expect(doc.root.to_xml(indent: 2)).to include("\n"),
14
+ "#{name}: indent not honored"
15
+ end
16
+ end
17
+
18
+ it "raises the named AdapterError on engines that ignore the knob" do
19
+ %i[oga ox headed_ox libxml leptris].each do |name|
20
+ doc = Moxml.new(name).parse("<r><a/></r>")
21
+ expect { doc.root.to_xml(indent: 2) }
22
+ .to raise_error(Moxml::AdapterError, /indentation/),
23
+ "#{name}: silent unindented answer"
24
+ end
25
+ end
26
+
27
+ it "keeps indent: 0 and the no-option path common on every adapter" do
28
+ Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
29
+ doc = Moxml.new(name).parse("<r><a/></r>")
30
+ expect(doc.root.to_xml(indent: 0)).to include("<a")
31
+ expect(doc.root.to_xml).to include("<a")
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Parsing (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  RSpec.describe Moxml::Entity do
@@ -156,9 +156,6 @@ RSpec.describe Moxml::Node do
156
156
  end
157
157
 
158
158
  it "locates the node via its path" do
159
- # Ox translates only a limited XPath subset (no positional predicates)
160
- skip "D-12: ox xpath translation does not support positional predicates" if adapter_name == :ox
161
-
162
159
  expect(doc.at_xpath(title.path)).to eq(title)
163
160
  end
164
161
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Materialization (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  # Moxml::Plan adapter-contract conformance (leptris follow-up to
@@ -21,6 +21,15 @@ RSpec.describe Moxml::ProcessingInstruction do
21
21
  expect(pi.content).to include("type=")
22
22
  expect(pi.content).to include("href=")
23
23
  end
24
+
25
+ it "answers the empty string for a PI with no data on every adapter" do
26
+ Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
27
+ parsed = Moxml.new(name).parse("<r><?pi?></r>")
28
+ pi = parsed.children.first.children
29
+ .find { |n| n.is_a?(described_class) }
30
+ expect(pi.content).to eq(""), "empty PI content on #{name}"
31
+ end
32
+ end
24
33
  end
25
34
 
26
35
  describe "creation" do
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # The engine-free lane (TODO.sota/03): the reference is pure Ruby and
6
+ # answers the model where no native engine exists. This spec proves it
7
+ # transpiles and runs under Opal.
8
+ RSpec.describe "Moxml::Reference under Opal", if: RUBY_ENGINE == "opal" do
9
+ it "parses, answers faces, and writes without a native engine" do
10
+ document = Moxml::Reference.parse(
11
+ '<r a="1"><b>t&amp;u</b><![CDATA[c]]><?pi d?></r>',
12
+ )
13
+ expect(Moxml::Reference.element_names(document)).to eq(%w[r b])
14
+ expect(Moxml::Reference.child_element_names(document)).to eq([["b"], []])
15
+ element = document.root.children.find(&:element?)
16
+ expect(element.children.map(&:kind)).to eq([:text])
17
+ expect(element.children.first.data).to eq("t&u")
18
+ expect(document.root.text).to eq("t&uc")
19
+ expect(Moxml::Reference.processing_instructions(document))
20
+ .to eq([["pi", "d"]])
21
+ expect(Moxml::Reference.to_xml(document))
22
+ .to include("<r a=\"1\">", "<b>t&amp;u</b>", "<![CDATA[c]]>")
23
+ end
24
+
25
+ it "raises a named ParseError for malformed input" do
26
+ expect { Moxml::Reference.parse("<r><c></r>") }
27
+ .to raise_error(Moxml::ParseError)
28
+ end
29
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "support/input_taxonomy"
5
+ require "support/xml_generator"
6
+
7
+ # The executable reference (TODO.sota/03): a pure-Ruby reader,
8
+ # writer, and normalizer that answer the processing model with no
9
+ # engine involved. The differential suite tests adapters against it;
10
+ # these specs pin the reference itself.
11
+ RSpec.describe Moxml::Reference do
12
+ describe "parsing" do
13
+ it "reads the object model, declaration, and document parts" do
14
+ doc = described_class.parse(
15
+ '<?xml version="1.0" encoding="UTF-8"?><!--lead--><r a="1"><b>t</b></r>',
16
+ )
17
+ expect(doc.declaration[:version]).to eq("1.0")
18
+ expect(doc.declaration[:encoding]).to eq("UTF-8")
19
+ expect(described_class.element_names(doc)).to eq(%w[r b])
20
+ expect(doc.root.attribute_pairs).to eq([["a", "1"]])
21
+ expect(described_class.comments(doc)).to eq(["lead"])
22
+ end
23
+
24
+ it "answers bare local names for prefixed sources" do
25
+ doc = described_class.parse('<r xmlns:p="urn:a"><p:c p:x="1"/></r>')
26
+ expect(described_class.element_names(doc)).to eq(%w[r c])
27
+ expect(described_class.child_element_names(doc)).to eq([["c"], []])
28
+ expect(doc.root.children.first.attribute_pairs).to eq([["x", "1"]])
29
+ end
30
+
31
+ it "expands built-in and numeric references, never comments or PI data" do
32
+ doc = described_class.parse(
33
+ "<r>&amp;&#65;&#x42;<![CDATA[<raw>]]><!-- c --><?pi d?></r>",
34
+ )
35
+ expect(doc.root.text).to eq("&AB<raw>")
36
+ expect(described_class.processing_instructions(doc)).to eq([["pi", "d"]])
37
+ end
38
+
39
+ it "preserves DTD-declared general entities lexically (uniform lane)" do
40
+ doc = described_class.parse(
41
+ '<!DOCTYPE r [<!ENTITY x "expanded">]><r>&x;</r>',
42
+ )
43
+ expect(doc.root.text).to eq("&x;")
44
+ end
45
+
46
+ it "normalizes literal line endings but not character references" do
47
+ doc = described_class.parse("<r>a\r\nb\rc&#xD;d</r>")
48
+ expect(doc.root.text).to eq("a\nb\nc\rd")
49
+ end
50
+
51
+ it "normalizes literal attribute whitespace" do
52
+ doc = described_class.parse(%(<r a="x\ty\nz"/>))
53
+ expect(doc.root.attribute_pairs).to eq([["a", "x y z"]])
54
+ end
55
+
56
+ it "drops the PI separator from PI data" do
57
+ doc = described_class.parse("<r><?pi data ?></r>")
58
+ expect(described_class.processing_instructions(doc))
59
+ .to eq([["pi", "data "]])
60
+ end
61
+
62
+ it "answers the empty string for PI with no data" do
63
+ doc = described_class.parse("<r><?pi?></r>")
64
+ expect(described_class.processing_instructions(doc)).to eq([["pi", ""]])
65
+ end
66
+ end
67
+
68
+ describe "totality over the input taxonomy" do
69
+ it "parses every well-formed taxonomy fixture" do
70
+ InputTaxonomy.well_formed.each do |input|
71
+ expect { described_class.parse(input.xml) }
72
+ .not_to raise_error, "#{input.id} must parse in the reference"
73
+ end
74
+ end
75
+
76
+ it "raises a named ParseError for every malformed taxonomy fixture" do
77
+ InputTaxonomy.malformed.each do |input|
78
+ expect { described_class.parse(input.xml) }
79
+ .to raise_error(Moxml::ParseError),
80
+ "#{input.id} must raise in the reference"
81
+ end
82
+ end
83
+
84
+ it "parses every generated document" do
85
+ (1..10).each do |seed|
86
+ expect { described_class.parse(XmlGenerator.generate(seed)) }
87
+ .not_to raise_error
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "writers" do
93
+ let(:doc) do
94
+ described_class.parse(
95
+ '<r a="&lt;&amp;&quot;"><b/>t<!--c--><![CDATA[x<y]]></r>',
96
+ )
97
+ end
98
+
99
+ it "emits XML that re-parses to the same information set" do
100
+ xml = described_class.to_xml(doc)
101
+ again = described_class.parse(xml)
102
+ expect(described_class.element_names(again))
103
+ .to eq(described_class.element_names(doc))
104
+ expect(again.root.attribute_pairs).to eq(doc.root.attribute_pairs)
105
+ expect(again.root.text).to eq(doc.root.text)
106
+ end
107
+
108
+ it "emits the canonical facade escaping" do
109
+ expect(described_class.to_xml(doc))
110
+ .to include('<r a="&lt;&amp;&quot;">')
111
+ expect(described_class.to_xml(doc)).to include("<![CDATA[x<y]]>")
112
+ expect(described_class.to_xml(doc)).to include("<b/>")
113
+ end
114
+
115
+ it "splits a CDATA end sequence" do
116
+ document = Moxml::Reference::Document.new
117
+ root = Moxml::Reference::Node.new(kind: :element, name: "r")
118
+ root.children << Moxml::Reference::Node.new(kind: :cdata, data: "a]]>b")
119
+ document.children << root
120
+ expect(described_class.to_xml(document)).to include("]]]]><![CDATA[>")
121
+ end
122
+
123
+ it "leaves void elements unclosed in HTML and self-closed in XHTML" do
124
+ html_doc = described_class.parse("<html><body>a<br/>b</body></html>")
125
+ expect(described_class.to_html(html_doc)).to include("<br>")
126
+ expect(described_class.to_xhtml(html_doc)).to include("<br/>")
127
+ end
128
+
129
+ it "emits raw text inside HTML script and style, escaped in XHTML" do
130
+ html_doc = described_class.parse("<html><script>a &lt; b</script></html>")
131
+ expect(described_class.to_html(html_doc))
132
+ .to include("<script>a < b</script>")
133
+ expect(described_class.to_xhtml(html_doc))
134
+ .to include("<script>a &lt; b</script>")
135
+ end
136
+
137
+ it "emits no XML declaration unless the source had one" do
138
+ expect(described_class.to_html(described_class.parse("<r/>")))
139
+ .to eq("<r></r>")
140
+ with_declaration = described_class.parse('<?xml version="1.0"?><r/>')
141
+ expect(described_class.to_xml(with_declaration))
142
+ .to start_with('<?xml version="1.0"?>')
143
+ end
144
+ end
145
+
146
+ describe "normalizer" do
147
+ it "normalizes CRLF and bare CR to LF" do
148
+ expect(Moxml::Reference::Normalizer.normalize_eol("a\r\nb\rc"))
149
+ .to eq("a\nb\nc")
150
+ end
151
+
152
+ it "expands built-ins and numeric references" do
153
+ expect(Moxml::Reference::Normalizer.expand_entities("&lt;&#65;"))
154
+ .to eq("<A")
155
+ end
156
+
157
+ it "raises for an undefined reference" do
158
+ expect { Moxml::Reference::Normalizer.expand_entities("&nope;") }
159
+ .to raise_error(Moxml::ParseError)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "tempfile"
5
+
6
+ # RELAX NG facade (TODO.sota/05): engines with an RNG validator
7
+ # answer valid?/validate/validate_errors; engines without one raise
8
+ # the named NotImplementedError at compile and answer supported?
9
+ # without raising.
10
+ RSpec.describe Moxml::RelaxNG do
11
+ RNG_SCHEMA = <<~RNG # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
12
+ <element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
13
+ <attribute name="a"><text/></attribute>
14
+ </element>
15
+ RNG
16
+
17
+ let(:valid_doc) { Moxml.new(:leptris).parse('<r a="x"/>') }
18
+ let(:invalid_doc) { Moxml.new(:leptris).parse("<r/>") }
19
+
20
+ it "answers supported? without raising on every adapter" do
21
+ Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
22
+ ctx = Moxml.new(name)
23
+ expect { described_class.supported?(ctx) }.not_to raise_error
24
+ end
25
+ end
26
+
27
+ it "validates against a compiled schema and reports findings" do
28
+ ctx = Moxml.new(:leptris)
29
+ skip "leptris binding has no RNG face" unless described_class.supported?(ctx)
30
+
31
+ schema = ctx.relaxng(RNG_SCHEMA)
32
+ expect(schema.valid?(valid_doc)).to be(true)
33
+ expect(schema.validate(valid_doc)).to eq([])
34
+ expect(schema.valid?(invalid_doc)).to be(false)
35
+ error = schema.validate_errors(invalid_doc).first
36
+ expect(error[:message]).to include("missing required attribute")
37
+ expect(error[:line]).to eq(1)
38
+ end
39
+
40
+ it "compiles a schema from a file" do
41
+ ctx = Moxml.new(:leptris)
42
+ skip "leptris binding has no RNG face" unless described_class.supported?(ctx)
43
+
44
+ Tempfile.create(["schema", ".rng"]) do |file|
45
+ file.write(RNG_SCHEMA)
46
+ file.flush
47
+ schema = ctx.relaxng_file(file.path)
48
+ expect(schema.valid?(valid_doc)).to be(true)
49
+ expect(schema.valid?(invalid_doc)).to be(false)
50
+ end
51
+ end
52
+
53
+ it "raises a named NotImplementedError on engines without an RNG validator" do
54
+ ctx = Moxml.new(:nokogiri)
55
+ expect(described_class.supported?(ctx)).to be(false)
56
+ expect { ctx.relaxng(RNG_SCHEMA) }
57
+ .to raise_error(Moxml::NotImplementedError, /RELAX NG/)
58
+ end
59
+
60
+ it "names a malformed schema as a ValidationError" do
61
+ ctx = Moxml.new(:leptris)
62
+ skip "leptris binding has no RNG face" unless described_class.supported?(ctx)
63
+
64
+ expect { ctx.relaxng("<not-a-schema/>") }
65
+ .to raise_error(Moxml::ValidationError, /RELAX NG/)
66
+ end
67
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §SAX (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  RSpec.describe Moxml::SAX do
@@ -410,50 +412,39 @@ RSpec.describe Moxml::SAX do
410
412
  expect(handler.error_message).to match(/invalid|unclosed|premature|mismatch|not closed/i)
411
413
  end
412
414
 
413
- it "documents CDATA limitation" do
414
- # Ox SAX does not have separate CDATA events
415
- # All CDATA content is delivered as text() events
416
- skip "D-10: Ox SAX does not support separate CDATA events"
417
-
418
- xml_with_cdata = "<root><![CDATA[special content]]></root>"
419
-
420
- cdata_found = false
421
- context.sax_parse(xml_with_cdata) do
422
- cdata { |_text| cdata_found = true }
423
- end
424
-
425
- # This will fail because Ox doesn't support separate CDATA events
426
- expect(cdata_found).to be true
415
+ it "declares CDATA event subscriptions loudly (D-10)" do
416
+ # Ox SAX has no separate CDATA events; a subscriber gets the
417
+ # named error rather than an incomplete stream.
418
+ expect do
419
+ context.sax_parse("<root><![CDATA[special content]]></root>") do
420
+ cdata { |_text| nil }
421
+ end
422
+ end.to raise_error(Moxml::NotImplementedError, /cdata/)
427
423
  end
428
424
 
429
- it "documents comment limitation" do
430
- # Ox SAX does not have separate comment events
431
- skip "D-10: Ox SAX does not support separate comment events"
432
-
433
- xml_with_comment = "<root><!-- comment --><data>content</data></root>"
434
-
435
- comment_found = false
436
- context.sax_parse(xml_with_comment) do
437
- comment { |_text| comment_found = true }
438
- end
439
-
440
- # This will fail because Ox doesn't support comment events
441
- expect(comment_found).to be true
425
+ it "declares comment event subscriptions loudly (D-10)" do
426
+ expect do
427
+ context.sax_parse("<root><!-- comment --><data>content</data></root>") do
428
+ comment { |_text| nil }
429
+ end
430
+ end.to raise_error(Moxml::NotImplementedError, /comment/)
442
431
  end
443
432
 
444
- it "documents processing instruction limitation" do
445
- # Ox SAX does not have separate PI events
446
- skip "D-10: Ox SAX does not support separate processing instruction events"
447
-
448
- xml_with_pi = '<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>'
433
+ it "declares PI event subscriptions loudly (D-10)" do
434
+ expect do
435
+ context.sax_parse('<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>') do
436
+ processing_instruction { |_target, _data| nil }
437
+ end
438
+ end.to raise_error(Moxml::NotImplementedError, /processing_instruction/)
439
+ end
449
440
 
450
- pi_found = false
451
- context.sax_parse(xml_with_pi) do
452
- processing_instruction { |_target, _data| pi_found = true }
453
- end
441
+ it "declares unsupported families loudly for class handlers too" do
442
+ handler = Class.new(Moxml::SAX::Handler) do
443
+ def on_cdata(_text); end
444
+ end.new
454
445
 
455
- # This will fail because Ox doesn't support PI events
456
- expect(pi_found).to be true
446
+ expect { context.sax_parse("<root/>", handler) }
447
+ .to raise_error(Moxml::NotImplementedError, /cdata/)
457
448
  end
458
449
  end
459
450
 
@@ -1022,46 +1013,28 @@ RSpec.describe Moxml::SAX do
1022
1013
  expect(handler.error_message).to match(/invalid|unclosed|premature|mismatch|not closed/i)
1023
1014
  end
1024
1015
 
1025
- it "documents CDATA limitation (inherited from Ox)" do
1026
- # HeadedOx inherits Ox's SAX implementation, which does not have separate CDATA events
1027
- skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate CDATA events"
1028
-
1029
- xml_with_cdata = "<root><![CDATA[special content]]></root>"
1030
-
1031
- cdata_found = false
1032
- context.sax_parse(xml_with_cdata) do
1033
- cdata { |_text| cdata_found = true }
1034
- end
1035
-
1036
- expect(cdata_found).to be true
1016
+ it "declares CDATA event subscriptions loudly (inherited from Ox, D-10)" do
1017
+ expect do
1018
+ context.sax_parse("<root><![CDATA[special content]]></root>") do
1019
+ cdata { |_text| nil }
1020
+ end
1021
+ end.to raise_error(Moxml::NotImplementedError, /cdata/)
1037
1022
  end
1038
1023
 
1039
- it "documents comment limitation (inherited from Ox)" do
1040
- # HeadedOx inherits Ox's SAX implementation, which does not have separate comment events
1041
- skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate comment events"
1042
-
1043
- xml_with_comment = "<root><!-- comment --><data>content</data></root>"
1044
-
1045
- comment_found = false
1046
- context.sax_parse(xml_with_comment) do
1047
- comment { |_text| comment_found = true }
1048
- end
1049
-
1050
- expect(comment_found).to be true
1024
+ it "declares comment event subscriptions loudly (inherited from Ox, D-10)" do
1025
+ expect do
1026
+ context.sax_parse("<root><!-- comment --><data>content</data></root>") do
1027
+ comment { |_text| nil }
1028
+ end
1029
+ end.to raise_error(Moxml::NotImplementedError, /comment/)
1051
1030
  end
1052
1031
 
1053
- it "documents processing instruction limitation (inherited from Ox)" do
1054
- # HeadedOx inherits Ox's SAX implementation, which does not have separate PI events
1055
- skip "D-10: HeadedOx SAX (inherited from Ox) does not support separate processing instruction events"
1056
-
1057
- xml_with_pi = '<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>'
1058
-
1059
- pi_found = false
1060
- context.sax_parse(xml_with_pi) do
1061
- processing_instruction { |_target, _data| pi_found = true }
1062
- end
1063
-
1064
- expect(pi_found).to be true
1032
+ it "declares PI event subscriptions loudly (inherited from Ox, D-10)" do
1033
+ expect do
1034
+ context.sax_parse('<?xml-stylesheet type="text/xsl" href="style.xsl"?><root/>') do
1035
+ processing_instruction { |_target, _data| nil }
1036
+ end
1037
+ end.to raise_error(Moxml::NotImplementedError, /processing_instruction/)
1065
1038
  end
1066
1039
  end
1067
1040
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Access semantics (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  RSpec.describe Moxml::Text do
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Serialization (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  # Document#to_html (issue #354): HTML-method serialization with the
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Processing model §Serialization (normative): pinned here.
4
+
3
5
  require "spec_helper"
4
6
 
5
7
  # Issue #390: XHTML-shaped serialization — void elements self-closed
@@ -217,11 +217,11 @@ RSpec.describe "XPath Axes" do
217
217
  end
218
218
 
219
219
  it "selects ancestors with ancestor::" do
220
- expect(select("//c/ancestor::*")).to eq(%w[parent root])
220
+ expect(select("//c/ancestor::*")).to eq(%w[root parent])
221
221
  end
222
222
 
223
223
  it "includes the context node with ancestor-or-self::" do
224
- expect(select("//c/ancestor-or-self::*")).to eq(%w[c parent root])
224
+ expect(select("//c/ancestor-or-self::*")).to eq(%w[root parent c])
225
225
  end
226
226
 
227
227
  it "selects following siblings" do