moxml 0.1.7 → 0.1.9
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 +4 -4
- data/.github/workflows/dependent-repos.json +5 -0
- data/.github/workflows/dependent-tests.yml +20 -0
- data/.github/workflows/docs.yml +59 -0
- data/.github/workflows/rake.yml +10 -10
- data/.github/workflows/release.yml +5 -3
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -7
- data/.rubocop_todo.yml +224 -43
- data/Gemfile +14 -9
- data/LICENSE.md +6 -2
- data/README.adoc +535 -373
- data/Rakefile +53 -0
- data/benchmarks/.gitignore +6 -0
- data/benchmarks/generate_report.rb +550 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +138 -0
- data/docs/_guides/advanced-features.adoc +87 -0
- data/docs/_guides/development-testing.adoc +165 -0
- data/docs/_guides/index.adoc +51 -0
- data/docs/_guides/modifying-xml.adoc +292 -0
- data/docs/_guides/parsing-xml.adoc +230 -0
- data/docs/_guides/sax-parsing.adoc +603 -0
- data/docs/_guides/working-with-documents.adoc +118 -0
- data/docs/_guides/xml-declaration.adoc +450 -0
- data/docs/_pages/adapter-compatibility.adoc +369 -0
- data/docs/_pages/adapters/headed-ox.adoc +237 -0
- data/docs/_pages/adapters/index.adoc +97 -0
- data/docs/_pages/adapters/libxml.adoc +285 -0
- data/docs/_pages/adapters/nokogiri.adoc +251 -0
- data/docs/_pages/adapters/oga.adoc +291 -0
- data/docs/_pages/adapters/ox.adoc +56 -0
- data/docs/_pages/adapters/rexml.adoc +292 -0
- data/docs/_pages/best-practices.adoc +429 -0
- data/docs/_pages/compatibility.adoc +467 -0
- data/docs/_pages/configuration.adoc +250 -0
- data/docs/_pages/error-handling.adoc +349 -0
- data/docs/_pages/headed-ox-limitations.adoc +574 -0
- data/docs/_pages/headed-ox.adoc +1025 -0
- data/docs/_pages/index.adoc +35 -0
- data/docs/_pages/installation.adoc +140 -0
- data/docs/_pages/node-api-reference.adoc +49 -0
- data/docs/_pages/performance.adoc +35 -0
- data/docs/_pages/quick-start.adoc +243 -0
- data/docs/_pages/thread-safety.adoc +28 -0
- data/docs/_references/document-api.adoc +407 -0
- data/docs/_references/index.adoc +48 -0
- data/docs/_tutorials/basic-usage.adoc +267 -0
- data/docs/_tutorials/builder-pattern.adoc +342 -0
- data/docs/_tutorials/index.adoc +33 -0
- data/docs/_tutorials/namespace-handling.adoc +324 -0
- data/docs/_tutorials/xpath-queries.adoc +358 -0
- data/docs/index.adoc +122 -0
- data/examples/README.md +124 -0
- data/examples/api_client/README.md +424 -0
- data/examples/api_client/api_client.rb +394 -0
- data/examples/api_client/example_response.xml +48 -0
- data/examples/headed_ox_example/README.md +90 -0
- data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
- data/examples/rss_parser/README.md +194 -0
- data/examples/rss_parser/example_feed.xml +93 -0
- data/examples/rss_parser/rss_parser.rb +189 -0
- data/examples/sax_parsing/README.md +50 -0
- data/examples/sax_parsing/data_extractor.rb +75 -0
- data/examples/sax_parsing/example.xml +21 -0
- data/examples/sax_parsing/large_file.rb +78 -0
- data/examples/sax_parsing/simple_parser.rb +55 -0
- data/examples/web_scraper/README.md +352 -0
- data/examples/web_scraper/example_page.html +201 -0
- data/examples/web_scraper/web_scraper.rb +312 -0
- data/lib/moxml/adapter/base.rb +107 -28
- data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
- data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
- data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
- data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
- data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
- data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
- data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +13 -8
- data/lib/moxml/adapter/headed_ox.rb +161 -0
- data/lib/moxml/adapter/libxml.rb +1564 -0
- data/lib/moxml/adapter/nokogiri.rb +156 -9
- data/lib/moxml/adapter/oga.rb +190 -15
- data/lib/moxml/adapter/ox.rb +322 -28
- data/lib/moxml/adapter/rexml.rb +157 -28
- data/lib/moxml/adapter.rb +21 -4
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/builder.rb +40 -4
- data/lib/moxml/config.rb +8 -3
- data/lib/moxml/context.rb +57 -2
- data/lib/moxml/declaration.rb +9 -0
- data/lib/moxml/doctype.rb +13 -1
- data/lib/moxml/document.rb +53 -6
- data/lib/moxml/document_builder.rb +34 -5
- data/lib/moxml/element.rb +71 -2
- data/lib/moxml/error.rb +175 -6
- data/lib/moxml/node.rb +155 -4
- data/lib/moxml/node_set.rb +34 -0
- data/lib/moxml/sax/block_handler.rb +194 -0
- data/lib/moxml/sax/element_handler.rb +124 -0
- data/lib/moxml/sax/handler.rb +113 -0
- data/lib/moxml/sax.rb +31 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +4 -4
- data/lib/moxml/xml_utils.rb +7 -4
- data/lib/moxml/xpath/ast/node.rb +159 -0
- data/lib/moxml/xpath/cache.rb +91 -0
- data/lib/moxml/xpath/compiler.rb +1770 -0
- data/lib/moxml/xpath/context.rb +26 -0
- data/lib/moxml/xpath/conversion.rb +124 -0
- data/lib/moxml/xpath/engine.rb +52 -0
- data/lib/moxml/xpath/errors.rb +101 -0
- data/lib/moxml/xpath/lexer.rb +304 -0
- data/lib/moxml/xpath/parser.rb +485 -0
- data/lib/moxml/xpath/ruby/generator.rb +269 -0
- data/lib/moxml/xpath/ruby/node.rb +193 -0
- data/lib/moxml/xpath.rb +37 -0
- data/lib/moxml.rb +5 -2
- data/moxml.gemspec +3 -1
- data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
- data/spec/consistency/README.md +77 -0
- data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
- data/spec/examples/README.md +75 -0
- data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
- data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
- data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
- data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
- data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
- data/spec/integration/README.md +71 -0
- data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
- data/spec/integration/headed_ox_integration_spec.rb +326 -0
- data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
- data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
- data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
- data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
- data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
- data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
- data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
- data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -5
- data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
- data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
- data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
- data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
- data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
- data/spec/moxml/README.md +41 -0
- data/spec/moxml/adapter/.gitkeep +0 -0
- data/spec/moxml/adapter/README.md +61 -0
- data/spec/moxml/adapter/base_spec.rb +27 -0
- data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
- data/spec/moxml/adapter/libxml_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +9 -8
- data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
- data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
- data/spec/moxml/adapter_spec.rb +16 -0
- data/spec/moxml/attribute_spec.rb +30 -0
- data/spec/moxml/builder_spec.rb +33 -0
- data/spec/moxml/cdata_spec.rb +31 -0
- data/spec/moxml/comment_spec.rb +31 -0
- data/spec/moxml/config_spec.rb +3 -3
- data/spec/moxml/context_spec.rb +28 -0
- data/spec/moxml/declaration_preservation_spec.rb +217 -0
- data/spec/moxml/declaration_spec.rb +36 -0
- data/spec/moxml/doctype_spec.rb +33 -0
- data/spec/moxml/document_builder_spec.rb +30 -0
- data/spec/moxml/document_spec.rb +105 -0
- data/spec/moxml/element_spec.rb +143 -0
- data/spec/moxml/error_spec.rb +266 -22
- data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
- data/spec/moxml/namespace_spec.rb +32 -0
- data/spec/moxml/node_set_spec.rb +39 -0
- data/spec/moxml/node_spec.rb +37 -0
- data/spec/moxml/processing_instruction_spec.rb +34 -0
- data/spec/moxml/sax_spec.rb +1067 -0
- data/spec/moxml/text_spec.rb +31 -0
- data/spec/moxml/version_spec.rb +14 -0
- data/spec/moxml/xml_utils/.gitkeep +0 -0
- data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
- data/spec/moxml/xml_utils_spec.rb +49 -0
- data/spec/moxml/xpath/ast/node_spec.rb +83 -0
- data/spec/moxml/xpath/axes_spec.rb +296 -0
- data/spec/moxml/xpath/cache_spec.rb +358 -0
- data/spec/moxml/xpath/compiler_spec.rb +406 -0
- data/spec/moxml/xpath/context_spec.rb +210 -0
- data/spec/moxml/xpath/conversion_spec.rb +365 -0
- data/spec/moxml/xpath/fixtures/sample.xml +25 -0
- data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
- data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
- data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
- data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
- data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
- data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
- data/spec/moxml/xpath/lexer_spec.rb +488 -0
- data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
- data/spec/moxml/xpath/parser_spec.rb +364 -0
- data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
- data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
- data/spec/moxml/xpath_capabilities_spec.rb +199 -0
- data/spec/moxml/xpath_spec.rb +77 -0
- data/spec/performance/README.md +83 -0
- data/spec/performance/benchmark_spec.rb +64 -0
- data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +4 -1
- data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
- data/spec/performance/xpath_benchmark_spec.rb +259 -0
- data/spec/spec_helper.rb +58 -1
- data/spec/support/xml_matchers.rb +1 -1
- metadata +178 -34
- data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
- /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Context do
|
|
6
|
+
let(:context) { described_class.new }
|
|
7
|
+
|
|
8
|
+
describe "#parse" do
|
|
9
|
+
it "parses XML string" do
|
|
10
|
+
doc = context.parse("<root><child/></root>")
|
|
11
|
+
expect(doc).to be_a(Moxml::Document)
|
|
12
|
+
expect(doc.root.name).to eq("root")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "#config" do
|
|
17
|
+
it "has a configuration" do
|
|
18
|
+
expect(context.config).to be_a(Moxml::Config)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "adapter access" do
|
|
23
|
+
it "provides adapter through config" do
|
|
24
|
+
expect(context.config.adapter).to be_a(Class)
|
|
25
|
+
expect(context.config.adapter.ancestors).to include(Moxml::Adapter::Base)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "XML Declaration Preservation" do
|
|
6
|
+
# Test with all available adapters
|
|
7
|
+
ADAPTERS = %i[nokogiri oga rexml ox libxml headed_ox].freeze
|
|
8
|
+
|
|
9
|
+
ADAPTERS.each do |adapter_name|
|
|
10
|
+
context "with #{adapter_name} adapter" do
|
|
11
|
+
let(:context) { Moxml.new(adapter_name) }
|
|
12
|
+
|
|
13
|
+
describe "automatic preservation" do
|
|
14
|
+
context "when input has no XML declaration" do
|
|
15
|
+
let(:xml_without_decl) { '<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>' }
|
|
16
|
+
|
|
17
|
+
it "does not add XML declaration to output" do
|
|
18
|
+
doc = context.parse(xml_without_decl)
|
|
19
|
+
output = doc.to_xml
|
|
20
|
+
|
|
21
|
+
expect(output).not_to include("<?xml")
|
|
22
|
+
expect(output).to include("<svg")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "sets has_xml_declaration to false" do
|
|
26
|
+
doc = context.parse(xml_without_decl)
|
|
27
|
+
expect(doc.has_xml_declaration).to be false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "when input has XML declaration" do
|
|
32
|
+
let(:xml_with_decl) do
|
|
33
|
+
'<?xml version="1.0" encoding="UTF-8"?><root><child/></root>'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "preserves XML declaration in output" do
|
|
37
|
+
doc = context.parse(xml_with_decl)
|
|
38
|
+
output = doc.to_xml
|
|
39
|
+
|
|
40
|
+
expect(output).to include("<?xml")
|
|
41
|
+
expect(output).to include('version="1.0"')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "sets has_xml_declaration to true" do
|
|
45
|
+
doc = context.parse(xml_with_decl)
|
|
46
|
+
expect(doc.has_xml_declaration).to be true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "when input has declaration with standalone attribute" do
|
|
51
|
+
let(:xml_with_standalone) do
|
|
52
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root/>'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "preserves the declaration" do
|
|
56
|
+
doc = context.parse(xml_with_standalone)
|
|
57
|
+
output = doc.to_xml
|
|
58
|
+
|
|
59
|
+
expect(output).to include("<?xml")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "explicit override" do
|
|
65
|
+
let(:xml_without_decl) { "<root><child/></root>" }
|
|
66
|
+
let(:xml_with_decl) { '<?xml version="1.0"?><root><child/></root>' }
|
|
67
|
+
|
|
68
|
+
context "when forcing declaration on document without one" do
|
|
69
|
+
it "adds declaration when declaration: true" do
|
|
70
|
+
doc = context.parse(xml_without_decl)
|
|
71
|
+
output = doc.to_xml(declaration: true)
|
|
72
|
+
|
|
73
|
+
expect(output).to include("<?xml")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context "when removing declaration from document with one" do
|
|
78
|
+
it "removes declaration when declaration: false" do
|
|
79
|
+
doc = context.parse(xml_with_decl)
|
|
80
|
+
output = doc.to_xml(declaration: false)
|
|
81
|
+
|
|
82
|
+
expect(output).not_to include("<?xml")
|
|
83
|
+
expect(output).to include("<root")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "when explicitly preserving declaration" do
|
|
88
|
+
it "keeps declaration when declaration: true" do
|
|
89
|
+
doc = context.parse(xml_with_decl)
|
|
90
|
+
output = doc.to_xml(declaration: true)
|
|
91
|
+
|
|
92
|
+
expect(output).to include("<?xml")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "round-trip fidelity" do
|
|
98
|
+
context "for document without declaration" do
|
|
99
|
+
let(:original) { "<root><item id=\"1\"/></root>" }
|
|
100
|
+
|
|
101
|
+
it "maintains absence of declaration through parse and serialize" do
|
|
102
|
+
doc = context.parse(original)
|
|
103
|
+
output = doc.to_xml
|
|
104
|
+
|
|
105
|
+
expect(output).not_to include("<?xml")
|
|
106
|
+
|
|
107
|
+
# Parse again and verify
|
|
108
|
+
doc2 = context.parse(output)
|
|
109
|
+
expect(doc2.has_xml_declaration).to be false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "for document with declaration" do
|
|
114
|
+
let(:original) do
|
|
115
|
+
'<?xml version="1.0" encoding="UTF-8"?><root><item id="1"/></root>'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "maintains presence of declaration through parse and serialize" do
|
|
119
|
+
doc = context.parse(original)
|
|
120
|
+
output = doc.to_xml
|
|
121
|
+
|
|
122
|
+
expect(output).to include("<?xml")
|
|
123
|
+
|
|
124
|
+
# Parse again and verify
|
|
125
|
+
doc2 = context.parse(output)
|
|
126
|
+
expect(doc2.has_xml_declaration).to be true
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "edge cases" do
|
|
132
|
+
context "with empty document" do
|
|
133
|
+
it "does not add declaration to empty document" do
|
|
134
|
+
doc = context.create_document
|
|
135
|
+
|
|
136
|
+
# Empty documents should not have declaration by default
|
|
137
|
+
expect(doc.has_xml_declaration).to be false
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "with built document" do
|
|
142
|
+
it "does not add declaration to programmatically built document" do
|
|
143
|
+
doc = context.create_document
|
|
144
|
+
root = doc.create_element("root")
|
|
145
|
+
doc.root = root
|
|
146
|
+
|
|
147
|
+
output = doc.to_xml
|
|
148
|
+
|
|
149
|
+
expect(output).not_to include("<?xml")
|
|
150
|
+
expect(doc.has_xml_declaration).to be false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "can explicitly add declaration to built document" do
|
|
154
|
+
doc = context.create_document
|
|
155
|
+
root = doc.create_element("root")
|
|
156
|
+
doc.root = root
|
|
157
|
+
|
|
158
|
+
output = doc.to_xml(declaration: true)
|
|
159
|
+
|
|
160
|
+
expect(output).to include("<?xml")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe "non-document nodes" do
|
|
166
|
+
let(:xml) { '<?xml version="1.0"?><root><child>text</child></root>' }
|
|
167
|
+
|
|
168
|
+
it "does not add declaration when serializing element nodes" do
|
|
169
|
+
doc = context.parse(xml)
|
|
170
|
+
root = doc.root
|
|
171
|
+
output = root.to_xml
|
|
172
|
+
|
|
173
|
+
expect(output).not_to include("<?xml")
|
|
174
|
+
expect(output).to include("<root>")
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe "integration with svg_conform use case" do
|
|
181
|
+
let(:context) { Moxml.new }
|
|
182
|
+
|
|
183
|
+
context "remediating SVG without declaration" do
|
|
184
|
+
let(:svg_input) { '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect x="10" y="10" width="80" height="80"/></svg>' }
|
|
185
|
+
|
|
186
|
+
it "does not add declaration to remediated output" do
|
|
187
|
+
doc = context.parse(svg_input)
|
|
188
|
+
|
|
189
|
+
# Simulate remediation: add viewport
|
|
190
|
+
root = doc.root
|
|
191
|
+
root["viewBox"] = "0 0 100 100"
|
|
192
|
+
|
|
193
|
+
output = doc.to_xml
|
|
194
|
+
|
|
195
|
+
expect(output).not_to include("<?xml")
|
|
196
|
+
expect(output).to include('viewBox="0 0 100 100"')
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
context "remediating SVG with declaration" do
|
|
201
|
+
let(:svg_input) { '<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>' }
|
|
202
|
+
|
|
203
|
+
it "preserves declaration in remediated output" do
|
|
204
|
+
doc = context.parse(svg_input)
|
|
205
|
+
|
|
206
|
+
# Simulate remediation
|
|
207
|
+
root = doc.root
|
|
208
|
+
root["viewBox"] = "0 0 100 100"
|
|
209
|
+
|
|
210
|
+
output = doc.to_xml
|
|
211
|
+
|
|
212
|
+
expect(output).to include("<?xml")
|
|
213
|
+
expect(output).to include('viewBox="0 0 100 100"')
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Declaration do
|
|
6
|
+
let(:context) { Moxml.new }
|
|
7
|
+
let(:doc) { context.create_document }
|
|
8
|
+
|
|
9
|
+
describe "#version" do
|
|
10
|
+
it "returns XML version" do
|
|
11
|
+
decl = doc.create_declaration("1.0", "UTF-8")
|
|
12
|
+
expect(decl.version).to eq("1.0")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "#encoding" do
|
|
17
|
+
it "returns encoding" do
|
|
18
|
+
decl = doc.create_declaration("1.0", "UTF-8")
|
|
19
|
+
expect(decl.encoding).to eq("UTF-8")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "creation" do
|
|
24
|
+
it "creates declaration with defaults" do
|
|
25
|
+
decl = doc.create_declaration
|
|
26
|
+
expect(decl).to be_a(described_class)
|
|
27
|
+
expect(decl.version).to eq("1.0")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "creates declaration with custom values" do
|
|
31
|
+
decl = doc.create_declaration("1.0", "ISO-8859-1")
|
|
32
|
+
expect(decl.version).to eq("1.0")
|
|
33
|
+
expect(decl.encoding).to eq("ISO-8859-1")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Doctype do
|
|
6
|
+
let(:context) { Moxml.new }
|
|
7
|
+
let(:doc) { context.create_document }
|
|
8
|
+
|
|
9
|
+
describe "#name" do
|
|
10
|
+
it "returns doctype name" do
|
|
11
|
+
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
12
|
+
doctype = doc.create_doctype("root", nil, "test.dtd")
|
|
13
|
+
expect(doctype.name).to eq("root")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "#system_id" do
|
|
18
|
+
it "returns system identifier" do
|
|
19
|
+
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
20
|
+
doctype = doc.create_doctype("root", nil, "test.dtd")
|
|
21
|
+
expect(doctype.system_id).to eq("test.dtd")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "creation" do
|
|
26
|
+
it "creates a doctype" do
|
|
27
|
+
skip "Doctype accessor methods not yet implemented in all adapters"
|
|
28
|
+
doctype = doc.create_doctype("html", nil, nil)
|
|
29
|
+
expect(doctype).to be_a(described_class)
|
|
30
|
+
expect(doctype.name).to eq("html")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# DocumentBuilder is an internal class for parsing
|
|
6
|
+
# These tests verify it correctly builds Moxml document from native documents
|
|
7
|
+
RSpec.describe Moxml::DocumentBuilder do
|
|
8
|
+
let(:context) { Moxml.new }
|
|
9
|
+
|
|
10
|
+
describe "#build" do
|
|
11
|
+
it "builds a Moxml document from native document" do
|
|
12
|
+
native_doc = context.parse("<root><child>content</child></root>").native
|
|
13
|
+
builder = described_class.new(context)
|
|
14
|
+
doc = builder.build(native_doc)
|
|
15
|
+
|
|
16
|
+
expect(doc).to be_a(Moxml::Document)
|
|
17
|
+
expect(doc.root.name).to eq("root")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "element handling" do
|
|
22
|
+
it "handles nested elements" do
|
|
23
|
+
native_doc = context.parse("<parent><child1/><child2/></parent>").native
|
|
24
|
+
builder = described_class.new(context)
|
|
25
|
+
doc = builder.build(native_doc)
|
|
26
|
+
|
|
27
|
+
expect(doc.root.children.length).to eq(2)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Document do
|
|
6
|
+
let(:context) { Moxml.new }
|
|
7
|
+
let(:doc) { context.parse("<root/>") }
|
|
8
|
+
|
|
9
|
+
describe "document creation" do
|
|
10
|
+
it "creates a document via parsing" do
|
|
11
|
+
expect(doc).to be_a(described_class)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "has a context" do
|
|
15
|
+
expect(doc.context).to be_a(Moxml::Context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "has a root element" do
|
|
19
|
+
expect(doc.root).to be_a(Moxml::Element)
|
|
20
|
+
expect(doc.root.name).to eq("root")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#create_element" do
|
|
25
|
+
it "creates an element" do
|
|
26
|
+
element = doc.create_element("test")
|
|
27
|
+
expect(element).to be_a(Moxml::Element)
|
|
28
|
+
expect(element.name).to eq("test")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#to_xml" do
|
|
33
|
+
it "serializes to XML" do
|
|
34
|
+
doc = context.parse("<root><child>text</child></root>")
|
|
35
|
+
xml = doc.to_xml
|
|
36
|
+
expect(xml).to include("<root>")
|
|
37
|
+
expect(xml).to include("<child>")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "convenience API methods" do
|
|
42
|
+
describe "#add_element" do
|
|
43
|
+
it "creates and adds element with attributes" do
|
|
44
|
+
fresh_doc = context.create_document
|
|
45
|
+
elem = fresh_doc.add_element("book", id: "123", title: "Ruby")
|
|
46
|
+
|
|
47
|
+
expect(elem).to be_a(Moxml::Element)
|
|
48
|
+
expect(elem.name).to eq("book")
|
|
49
|
+
expect(elem["id"]).to eq("123")
|
|
50
|
+
expect(elem["title"]).to eq("Ruby")
|
|
51
|
+
expect(fresh_doc.children).to include(elem)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "accepts a block for further customization" do
|
|
55
|
+
fresh_doc = context.create_document
|
|
56
|
+
elem = fresh_doc.add_element("book", id: "123") do |e|
|
|
57
|
+
e.text = "Content"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
expect(elem.text).to eq("Content")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "returns the created element" do
|
|
64
|
+
fresh_doc = context.create_document
|
|
65
|
+
elem = fresh_doc.add_element("book")
|
|
66
|
+
|
|
67
|
+
expect(elem).to be_a(Moxml::Element)
|
|
68
|
+
expect(elem.name).to eq("book")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "#find" do
|
|
73
|
+
it "finds first element matching xpath" do
|
|
74
|
+
doc = context.parse("<root><book id='1'/><book id='2'/></root>")
|
|
75
|
+
result = doc.find("//book")
|
|
76
|
+
|
|
77
|
+
expect(result).to be_a(Moxml::Element)
|
|
78
|
+
expect(result["id"]).to eq("1")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "returns nil when not found" do
|
|
82
|
+
result = doc.find("//nonexistent")
|
|
83
|
+
|
|
84
|
+
expect(result).to be_nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#find_all" do
|
|
89
|
+
it "finds all elements matching xpath" do
|
|
90
|
+
doc = context.parse("<root><book id='1'/><book id='2'/></root>")
|
|
91
|
+
results = doc.find_all("//book")
|
|
92
|
+
|
|
93
|
+
expect(results).to be_an(Array)
|
|
94
|
+
expect(results.length).to eq(2)
|
|
95
|
+
expect(results.map { |r| r["id"] }).to eq(%w[1 2])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "returns empty array when not found" do
|
|
99
|
+
results = doc.find_all("//nonexistent")
|
|
100
|
+
|
|
101
|
+
expect(results).to eq([])
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Element do
|
|
6
|
+
let(:context) { Moxml.new }
|
|
7
|
+
let(:doc) { context.parse("<root><child>text</child></root>") }
|
|
8
|
+
let(:element) { doc.root }
|
|
9
|
+
|
|
10
|
+
describe "#name" do
|
|
11
|
+
it "returns the element name" do
|
|
12
|
+
expect(element.name).to eq("root")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "#children" do
|
|
17
|
+
it "returns child elements" do
|
|
18
|
+
children = element.children
|
|
19
|
+
expect(children).not_to be_empty
|
|
20
|
+
expect(children.first.name).to eq("child")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#[]" do
|
|
25
|
+
it "gets attribute value" do
|
|
26
|
+
elem = context.parse('<root id="123"/>').root
|
|
27
|
+
expect(elem["id"]).to eq("123")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#[]=" do
|
|
32
|
+
it "sets attribute value" do
|
|
33
|
+
elem = doc.create_element("test")
|
|
34
|
+
elem["id"] = "456"
|
|
35
|
+
expect(elem["id"]).to eq("456")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#text" do
|
|
40
|
+
it "returns text content" do
|
|
41
|
+
child = element.children.first
|
|
42
|
+
expect(child.text).to eq("text")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "convenience API methods" do
|
|
47
|
+
describe "#set_attributes" do
|
|
48
|
+
it "sets multiple attributes at once" do
|
|
49
|
+
elem = doc.create_element("book")
|
|
50
|
+
elem.set_attributes(id: "123", title: "Ruby", year: "2024")
|
|
51
|
+
|
|
52
|
+
expect(elem["id"]).to eq("123")
|
|
53
|
+
expect(elem["title"]).to eq("Ruby")
|
|
54
|
+
expect(elem["year"]).to eq("2024")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "returns self for chaining" do
|
|
58
|
+
elem = doc.create_element("book")
|
|
59
|
+
result = elem.set_attributes(id: "123")
|
|
60
|
+
|
|
61
|
+
expect(result).to eq(elem)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "handles empty hash" do
|
|
65
|
+
elem = doc.create_element("book")
|
|
66
|
+
expect { elem.set_attributes({}) }.not_to raise_error
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#with_child" do
|
|
71
|
+
it "adds a child and returns self" do
|
|
72
|
+
elem = doc.create_element("parent")
|
|
73
|
+
child = doc.create_element("child")
|
|
74
|
+
|
|
75
|
+
result = elem.with_child(child)
|
|
76
|
+
|
|
77
|
+
expect(result).to eq(elem)
|
|
78
|
+
expect(elem.children.map(&:name)).to include("child")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "can be chained" do
|
|
82
|
+
elem = doc.create_element("parent")
|
|
83
|
+
child1 = doc.create_element("child1")
|
|
84
|
+
child2 = doc.create_element("child2")
|
|
85
|
+
|
|
86
|
+
elem.with_child(child1).with_child(child2)
|
|
87
|
+
|
|
88
|
+
expect(elem.children.map(&:name)).to eq(%w[child1 child2])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "#find_element" do
|
|
93
|
+
it "finds first element matching xpath" do
|
|
94
|
+
doc = context.parse("<root><book id='1'/><book id='2'/></root>")
|
|
95
|
+
result = doc.root.find_element(".//book")
|
|
96
|
+
|
|
97
|
+
expect(result).to be_a(described_class)
|
|
98
|
+
expect(result["id"]).to eq("1")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "returns nil when not found" do
|
|
102
|
+
result = element.find_element(".//nonexistent")
|
|
103
|
+
|
|
104
|
+
expect(result).to be_nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe "#find_all" do
|
|
109
|
+
it "finds all elements matching xpath" do
|
|
110
|
+
doc = context.parse("<root><book id='1'/><book id='2'/></root>")
|
|
111
|
+
results = doc.root.find_all(".//book")
|
|
112
|
+
|
|
113
|
+
expect(results).to be_an(Array)
|
|
114
|
+
expect(results.length).to eq(2)
|
|
115
|
+
expect(results.map { |r| r["id"] }).to eq(%w[1 2])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "returns empty array when not found" do
|
|
119
|
+
results = element.find_all(".//nonexistent")
|
|
120
|
+
|
|
121
|
+
expect(results).to eq([])
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "method chaining" do
|
|
126
|
+
it "chains with_namespace, set_attributes, and with_child" do
|
|
127
|
+
elem = doc.create_element("book")
|
|
128
|
+
child = doc.create_element("title")
|
|
129
|
+
child.text = "Ruby Programming"
|
|
130
|
+
|
|
131
|
+
elem
|
|
132
|
+
.with_namespace("dc", "http://purl.org/dc/elements/1.1/")
|
|
133
|
+
.set_attributes(id: "123", type: "technical")
|
|
134
|
+
.with_child(child)
|
|
135
|
+
|
|
136
|
+
expect(elem["id"]).to eq("123")
|
|
137
|
+
expect(elem["type"]).to eq("technical")
|
|
138
|
+
expect(elem.children.first.name).to eq("title")
|
|
139
|
+
expect(elem.namespaces.map(&:uri)).to include("http://purl.org/dc/elements/1.1/")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|