moxml 0.1.6 → 0.1.8
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 +12 -4
- data/.github/workflows/release.yml +5 -3
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -7
- data/.rubocop_todo.yml +238 -40
- 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 +45 -0
- data/docs/_guides/modifying-xml.adoc +293 -0
- data/docs/_guides/parsing-xml.adoc +231 -0
- data/docs/_guides/sax-parsing.adoc +603 -0
- data/docs/_guides/working-with-documents.adoc +118 -0
- data/docs/_pages/adapter-compatibility.adoc +369 -0
- data/docs/_pages/adapters/headed-ox.adoc +237 -0
- data/docs/_pages/adapters/index.adoc +98 -0
- data/docs/_pages/adapters/libxml.adoc +286 -0
- data/docs/_pages/adapters/nokogiri.adoc +252 -0
- data/docs/_pages/adapters/oga.adoc +292 -0
- data/docs/_pages/adapters/ox.adoc +55 -0
- data/docs/_pages/adapters/rexml.adoc +293 -0
- data/docs/_pages/best-practices.adoc +430 -0
- data/docs/_pages/compatibility.adoc +468 -0
- data/docs/_pages/configuration.adoc +251 -0
- data/docs/_pages/error-handling.adoc +350 -0
- data/docs/_pages/headed-ox-limitations.adoc +558 -0
- data/docs/_pages/headed-ox.adoc +1025 -0
- data/docs/_pages/index.adoc +35 -0
- data/docs/_pages/installation.adoc +141 -0
- data/docs/_pages/node-api-reference.adoc +50 -0
- data/docs/_pages/performance.adoc +36 -0
- data/docs/_pages/quick-start.adoc +244 -0
- data/docs/_pages/thread-safety.adoc +29 -0
- data/docs/_references/document-api.adoc +408 -0
- data/docs/_references/index.adoc +48 -0
- data/docs/_tutorials/basic-usage.adoc +268 -0
- data/docs/_tutorials/builder-pattern.adoc +343 -0
- data/docs/_tutorials/index.adoc +33 -0
- data/docs/_tutorials/namespace-handling.adoc +325 -0
- data/docs/_tutorials/xpath-queries.adoc +359 -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 -3
- data/lib/moxml/adapter/customized_ox/namespace.rb +0 -2
- data/lib/moxml/adapter/customized_ox/text.rb +0 -2
- data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
- data/lib/moxml/adapter/headed_ox.rb +161 -0
- data/lib/moxml/adapter/libxml.rb +1548 -0
- data/lib/moxml/adapter/nokogiri.rb +121 -9
- data/lib/moxml/adapter/oga.rb +123 -12
- data/lib/moxml/adapter/ox.rb +283 -27
- data/lib/moxml/adapter/rexml.rb +127 -20
- 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 +39 -1
- data/lib/moxml/doctype.rb +13 -1
- data/lib/moxml/document.rb +39 -6
- data/lib/moxml/document_builder.rb +27 -5
- data/lib/moxml/element.rb +71 -2
- data/lib/moxml/error.rb +175 -6
- data/lib/moxml/node.rb +94 -3
- 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 +1768 -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 -2
- 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_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} +3 -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 +176 -35
- data/lib/ox/node.rb +0 -9
- 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
|
@@ -20,7 +20,7 @@ RSpec.shared_examples "Basic Usage Examples" do
|
|
|
20
20
|
|
|
21
21
|
expect(doc.to_xml).to include(
|
|
22
22
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
23
|
-
"<book></book>"
|
|
23
|
+
"<book></book>",
|
|
24
24
|
)
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -33,7 +33,7 @@ RSpec.shared_examples "Basic Usage Examples" do
|
|
|
33
33
|
|
|
34
34
|
expect(doc.to_xml).to include(
|
|
35
35
|
'<?xml-stylesheet type="text/xsl" href="style.xsl"?>',
|
|
36
|
-
"<root></root>"
|
|
36
|
+
"<root></root>",
|
|
37
37
|
)
|
|
38
38
|
end
|
|
39
39
|
end
|
data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb}
RENAMED
|
@@ -25,7 +25,7 @@ RSpec.shared_examples "Namespace Examples" do
|
|
|
25
25
|
|
|
26
26
|
expect(doc.to_xml).to include(
|
|
27
27
|
'xmlns:dc="http://purl.org/dc/elements/1.1/"',
|
|
28
|
-
"<dc:title>Test</dc:title>"
|
|
28
|
+
"<dc:title>Test</dc:title>",
|
|
29
29
|
)
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -42,7 +42,7 @@ RSpec.shared_examples "Namespace Examples" do
|
|
|
42
42
|
|
|
43
43
|
expect(doc.to_xml).to include(
|
|
44
44
|
"<ns:child>",
|
|
45
|
-
"<ns:grandchild>"
|
|
45
|
+
"<ns:grandchild>",
|
|
46
46
|
)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -58,7 +58,7 @@ RSpec.shared_examples "Namespace Examples" do
|
|
|
58
58
|
|
|
59
59
|
expect(doc.to_xml).to include(
|
|
60
60
|
'xmlns:ns="http://example.org/1"',
|
|
61
|
-
'xmlns:ns="http://example.org/2"'
|
|
61
|
+
'xmlns:ns="http://example.org/2"',
|
|
62
62
|
)
|
|
63
63
|
end
|
|
64
64
|
end
|
data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb}
RENAMED
|
@@ -22,7 +22,7 @@ RSpec.shared_examples "README Examples" do
|
|
|
22
22
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
23
23
|
'<book xmlns:dc="http://purl.org/dc/elements/1.1/">',
|
|
24
24
|
"<dc:title>XML Processing with Ruby</dc:title>",
|
|
25
|
-
"</book>"
|
|
25
|
+
"</book>",
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -57,7 +57,7 @@ RSpec.shared_examples "README Examples" do
|
|
|
57
57
|
"<!--Publication details-->",
|
|
58
58
|
'<published year="2024"></published>',
|
|
59
59
|
"<![CDATA[<custom>metadata</custom>]]>",
|
|
60
|
-
"</book>"
|
|
60
|
+
"</book>",
|
|
61
61
|
)
|
|
62
62
|
end
|
|
63
63
|
end
|
|
@@ -102,7 +102,7 @@ RSpec.shared_examples "README Examples" do
|
|
|
102
102
|
"<dc:title>Ruby</dc:title>",
|
|
103
103
|
"<![CDATA[About Ruby...]]>",
|
|
104
104
|
"<dc:title>XML</dc:title>",
|
|
105
|
-
"<![CDATA[About XML...]]>"
|
|
105
|
+
"<![CDATA[About XML...]]>",
|
|
106
106
|
)
|
|
107
107
|
end
|
|
108
108
|
end
|
|
@@ -110,7 +110,9 @@ RSpec.shared_examples "README Examples" do
|
|
|
110
110
|
describe "Error handling example" do
|
|
111
111
|
it "handles errors as shown in README" do
|
|
112
112
|
context = Moxml.new
|
|
113
|
-
|
|
113
|
+
if context.config.adapter_name == :ox
|
|
114
|
+
pending "Ox doesn't have a native XPath"
|
|
115
|
+
end
|
|
114
116
|
|
|
115
117
|
expect do
|
|
116
118
|
context.parse("<invalid>")
|
|
@@ -18,15 +18,15 @@ RSpec.shared_examples "XPath Examples" do
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it "finds nodes by XPath" do
|
|
21
|
-
pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
|
|
22
|
-
|
|
23
21
|
books = doc.xpath("//book")
|
|
24
22
|
expect(books.size).to eq(2)
|
|
25
23
|
expect(books.map { _1["id"] }).to eq(%w[1 2])
|
|
26
24
|
end
|
|
27
25
|
|
|
28
26
|
it "finds nodes with namespaces" do
|
|
29
|
-
|
|
27
|
+
if context.config.adapter_name == :ox
|
|
28
|
+
pending "Ox doesn't have a native XPath"
|
|
29
|
+
end
|
|
30
30
|
|
|
31
31
|
titles = doc.xpath("//dc:title",
|
|
32
32
|
"dc" => "http://purl.org/dc/elements/1.1/")
|
|
@@ -34,7 +34,9 @@ RSpec.shared_examples "XPath Examples" do
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
it "finds nodes by attributes" do
|
|
37
|
-
|
|
37
|
+
if %i[ox headed_ox].include?(context.config.adapter_name)
|
|
38
|
+
pending "Ox doesn't have native XPath, HeadedOx parser doesn't support .// syntax yet"
|
|
39
|
+
end
|
|
38
40
|
|
|
39
41
|
book = doc.at_xpath('//book[@id="2"]')
|
|
40
42
|
expect(book).not_to be_nil
|
|
@@ -44,7 +46,9 @@ RSpec.shared_examples "XPath Examples" do
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
it "finds nested attributes efficiently" do
|
|
47
|
-
|
|
49
|
+
if %i[ox headed_ox].include?(context.config.adapter_name)
|
|
50
|
+
pending "Ox doesn't have native XPath, HeadedOx parser doesn't support .// syntax yet"
|
|
51
|
+
end
|
|
48
52
|
# More efficient - specific path
|
|
49
53
|
titles1 = doc.xpath("//book/dc:title")
|
|
50
54
|
|
|
@@ -59,7 +63,7 @@ RSpec.shared_examples "XPath Examples" do
|
|
|
59
63
|
# Each book is a mapped Moxml::Element
|
|
60
64
|
book.at_xpath(".//dc:title", "dc" => "http://purl.org/dc/elements/1.1/").native
|
|
61
65
|
end
|
|
62
|
-
titles4 =
|
|
66
|
+
titles4 = Moxml::NodeSet.new(nodes, doc.context)
|
|
63
67
|
|
|
64
68
|
expect(titles1.count).to eq(2)
|
|
65
69
|
expect(titles1).to eq(titles2)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Integration Tests
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This directory contains integration tests that verify Moxml wrapper classes work correctly with all adapters. These tests use shared examples to ensure consistent behavior across different adapter implementations.
|
|
6
|
+
|
|
7
|
+
## What Should Be Placed Here
|
|
8
|
+
|
|
9
|
+
- ✅ Tests for wrapper classes working with all adapters
|
|
10
|
+
- ✅ Tests for complex workflows involving multiple classes
|
|
11
|
+
- ✅ Tests for edge cases that involve adapter interaction
|
|
12
|
+
- ✅ Shared examples that run for each adapter
|
|
13
|
+
- ✅ Cross-adapter behavioral consistency tests
|
|
14
|
+
|
|
15
|
+
## What Should NOT Be Placed Here
|
|
16
|
+
|
|
17
|
+
- ❌ Pure unit tests (use unit/ instead)
|
|
18
|
+
- ❌ Adapter-specific implementation tests (use adapter/ instead)
|
|
19
|
+
- ❌ Performance benchmarks (use performance/ instead)
|
|
20
|
+
- ❌ Documentation examples (use examples/ instead)
|
|
21
|
+
|
|
22
|
+
## How to Run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Run all integration tests
|
|
26
|
+
bundle exec rake spec:integration
|
|
27
|
+
|
|
28
|
+
# Run specific integration test
|
|
29
|
+
bundle exec rspec spec/integration/all_adapters_spec.rb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Directory Structure
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
integration/
|
|
36
|
+
├── shared_examples/
|
|
37
|
+
│ ├── node_wrappers/ # Node wrapper behavior tests
|
|
38
|
+
│ │ ├── attribute_behavior.rb
|
|
39
|
+
│ │ ├── cdata_behavior.rb
|
|
40
|
+
│ │ ├── comment_behavior.rb
|
|
41
|
+
│ │ ├── declaration_behavior.rb
|
|
42
|
+
│ │ ├── doctype_behavior.rb
|
|
43
|
+
│ │ ├── document_behavior.rb
|
|
44
|
+
│ │ ├── element_behavior.rb
|
|
45
|
+
│ │ ├── namespace_behavior.rb
|
|
46
|
+
│ │ ├── node_behavior.rb
|
|
47
|
+
│ │ ├── node_set_behavior.rb
|
|
48
|
+
│ │ ├── processing_instruction_behavior.rb
|
|
49
|
+
│ │ └── text_behavior.rb
|
|
50
|
+
│ ├── high_level/ # High-level pattern tests
|
|
51
|
+
│ │ ├── builder_behavior.rb
|
|
52
|
+
│ │ ├── context_behavior.rb
|
|
53
|
+
│ │ └── document_builder_behavior.rb
|
|
54
|
+
│ ├── edge_cases.rb # Edge case scenarios
|
|
55
|
+
│ └── integration_workflows.rb # Complete workflows
|
|
56
|
+
└── all_adapters_spec.rb # Runs all shared examples for all adapters
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Writing Integration Tests
|
|
60
|
+
|
|
61
|
+
Integration tests should be written as shared examples that can run with any adapter:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
RSpec.shared_examples "element wrapper behavior" do
|
|
65
|
+
it "creates elements" do
|
|
66
|
+
# Test that works with any adapter
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `all_adapters_spec.rb` file runs these shared examples for each adapter.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
RSpec.describe "
|
|
3
|
+
RSpec.describe "Cross-adapter integration" do
|
|
4
|
+
# Integration shared examples - use the names as defined in the files
|
|
4
5
|
all_shared_examples = [
|
|
5
6
|
"Moxml::Node",
|
|
6
7
|
"Moxml::Namespace",
|
|
@@ -26,7 +27,7 @@ RSpec.describe "Test all shared examples" do
|
|
|
26
27
|
"XPath Examples",
|
|
27
28
|
"Memory Usage Examples",
|
|
28
29
|
"Thread Safety Examples",
|
|
29
|
-
"Performance Examples"
|
|
30
|
+
"Performance Examples",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
Moxml::Adapter::AVALIABLE_ADAPTERS.each do |adapter_name|
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "HeadedOx Integration" do
|
|
6
|
+
let(:xml) do
|
|
7
|
+
<<~XML
|
|
8
|
+
<library>
|
|
9
|
+
<book id="1" price="15.99">
|
|
10
|
+
<title>Ruby Programming</title>
|
|
11
|
+
<author>Alice</author>
|
|
12
|
+
<year>2020</year>
|
|
13
|
+
</book>
|
|
14
|
+
<book id="2" price="25.99">
|
|
15
|
+
<title>Python Programming</title>
|
|
16
|
+
<author>Bob</author>
|
|
17
|
+
<year>2021</year>
|
|
18
|
+
</book>
|
|
19
|
+
<book id="3" price="12.99">
|
|
20
|
+
<title>JavaScript Programming</title>
|
|
21
|
+
<author>Charlie</author>
|
|
22
|
+
<year>2022</year>
|
|
23
|
+
</book>
|
|
24
|
+
</library>
|
|
25
|
+
XML
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
let(:context) { Moxml.new(:headed_ox) }
|
|
29
|
+
let(:doc) { context.parse(xml) }
|
|
30
|
+
|
|
31
|
+
describe "parsing and querying XML end-to-end" do
|
|
32
|
+
it "parses XML document" do
|
|
33
|
+
expect(doc).to be_a(Moxml::Document)
|
|
34
|
+
expect(doc.root.name).to eq("library")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "executes simple XPath queries" do
|
|
38
|
+
books = doc.xpath("//book")
|
|
39
|
+
|
|
40
|
+
expect(books.size).to eq(3)
|
|
41
|
+
expect(books).to all(be_a(Moxml::Element))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "executes XPath with numeric predicates" do
|
|
45
|
+
cheap_books = doc.xpath("//book[@price < 20]")
|
|
46
|
+
|
|
47
|
+
expect(cheap_books.size).to eq(2)
|
|
48
|
+
expect(cheap_books.map { |b| b["id"] }).to contain_exactly("1", "3")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "executes XPath with function calls" do
|
|
52
|
+
count = doc.xpath("count(//book)")
|
|
53
|
+
|
|
54
|
+
expect(count).to eq(3.0)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "executes complex XPath expressions" do
|
|
58
|
+
titles = doc.xpath("//book[@price < 20]/title")
|
|
59
|
+
|
|
60
|
+
expect(titles.size).to eq(2)
|
|
61
|
+
expect(titles.map(&:text)).to contain_exactly(
|
|
62
|
+
"Ruby Programming",
|
|
63
|
+
"JavaScript Programming",
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "XPath function categories" do
|
|
69
|
+
context "string functions" do
|
|
70
|
+
it "retrieves text content with string()" do
|
|
71
|
+
title = doc.xpath("string(//book[1]/title)")
|
|
72
|
+
|
|
73
|
+
expect(title).to eq("Ruby Programming")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "searches with contains()" do
|
|
77
|
+
ruby_books = doc.xpath("//book[contains(title, 'Ruby')]")
|
|
78
|
+
|
|
79
|
+
expect(ruby_books.size).to eq(1)
|
|
80
|
+
expect(ruby_books.first["id"]).to eq("1")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "filters with starts-with()" do
|
|
84
|
+
prog_books = doc.xpath("//book[starts-with(title, 'Python')]")
|
|
85
|
+
|
|
86
|
+
expect(prog_books.size).to eq(1)
|
|
87
|
+
expect(prog_books.first["id"]).to eq("2")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "concatenates strings with concat()" do
|
|
91
|
+
result = doc.xpath("concat('Book: ', //book[1]/title)")
|
|
92
|
+
|
|
93
|
+
expect(result).to eq("Book: Ruby Programming")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "normalizes whitespace with normalize-space()" do
|
|
97
|
+
result = doc.xpath("normalize-space(' hello world ')")
|
|
98
|
+
|
|
99
|
+
expect(result).to eq("hello world")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "numeric functions" do
|
|
104
|
+
it "converts to number with number()" do
|
|
105
|
+
price = doc.xpath("number(//book[1]/@price)")
|
|
106
|
+
|
|
107
|
+
expect(price).to eq(15.99)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "sums values with sum()" do
|
|
111
|
+
total = doc.xpath("sum(//book/@price)")
|
|
112
|
+
|
|
113
|
+
expect(total).to be_within(0.01).of(54.97)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "counts nodes with count()" do
|
|
117
|
+
count = doc.xpath("count(//book)")
|
|
118
|
+
|
|
119
|
+
expect(count).to eq(3.0)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "applies floor() function" do
|
|
123
|
+
result = doc.xpath("floor(15.99)")
|
|
124
|
+
|
|
125
|
+
expect(result).to eq(15.0)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "applies ceiling() function" do
|
|
129
|
+
result = doc.xpath("ceiling(12.01)")
|
|
130
|
+
|
|
131
|
+
expect(result).to eq(13.0)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "applies round() function" do
|
|
135
|
+
result = doc.xpath("round(15.5)")
|
|
136
|
+
|
|
137
|
+
expect(result).to eq(16.0)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "boolean functions" do
|
|
142
|
+
it "converts to boolean with boolean()" do
|
|
143
|
+
has_books = doc.xpath("boolean(//book)")
|
|
144
|
+
|
|
145
|
+
expect(has_books).to be true
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "negates with not()" do
|
|
149
|
+
result = doc.xpath("not(false())")
|
|
150
|
+
|
|
151
|
+
expect(result).to be true
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "returns true with true()" do
|
|
155
|
+
result = doc.xpath("true()")
|
|
156
|
+
|
|
157
|
+
expect(result).to be true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "returns false with false()" do
|
|
161
|
+
result = doc.xpath("false()")
|
|
162
|
+
|
|
163
|
+
expect(result).to be false
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
context "node functions" do
|
|
168
|
+
it "gets node name with name()" do
|
|
169
|
+
name = doc.xpath("name(//book[1])")
|
|
170
|
+
|
|
171
|
+
expect(name).to eq("book")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "gets local name with local-name()" do
|
|
175
|
+
name = doc.xpath("local-name(//book[1])")
|
|
176
|
+
|
|
177
|
+
expect(name).to eq("book")
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context "position functions" do
|
|
182
|
+
it "filters by position with position()" do
|
|
183
|
+
second_book = doc.xpath("//book[position() = 2]")
|
|
184
|
+
|
|
185
|
+
expect(second_book.size).to eq(1)
|
|
186
|
+
expect(second_book.first["id"]).to eq("2")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "selects last node with last()" do
|
|
190
|
+
skip "HeadedOx limitation: last() in predicate context needs temporary nodeset. See docs/HEADED_OX_LIMITATIONS.md"
|
|
191
|
+
last_book = doc.xpath("//book[position() = last()]")
|
|
192
|
+
|
|
193
|
+
expect(last_book.size).to eq(1)
|
|
194
|
+
expect(last_book.first["id"]).to eq("3")
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
describe "real-world use cases" do
|
|
200
|
+
it "finds books by author and price range" do
|
|
201
|
+
skip "HeadedOx limitation: Text content access from nested elements needs investigation. See docs/HEADED_OX_LIMITATIONS.md"
|
|
202
|
+
results = doc.xpath(
|
|
203
|
+
"//book[contains(author, 'Alice') and @price < 20]",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
expect(results.size).to eq(1)
|
|
207
|
+
expect(results.first.xpath("title").first.text).to eq("Ruby Programming")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "calculates statistics" do
|
|
211
|
+
avg_price = doc.xpath("sum(//book/@price) div count(//book)")
|
|
212
|
+
|
|
213
|
+
expect(avg_price).to be_within(0.01).of(18.32)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "combines multiple axes and predicates" do
|
|
217
|
+
# Books with price < 20, get their authors
|
|
218
|
+
authors = doc.xpath("//book[@price < 20]/author")
|
|
219
|
+
|
|
220
|
+
expect(authors.size).to eq(2)
|
|
221
|
+
expect(authors.map(&:text)).to contain_exactly("Alice", "Charlie")
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "uses parent axis for traversal" do
|
|
225
|
+
# Find title "Ruby Programming", then get parent book's author
|
|
226
|
+
author = doc.xpath(
|
|
227
|
+
"//title[contains(., 'Ruby')]/parent::book/author",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
expect(author.size).to eq(1)
|
|
231
|
+
expect(author.first.text).to eq("Alice")
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe "comparison with standard Ox adapter" do
|
|
236
|
+
let(:ox_context) { Moxml.new(:ox) }
|
|
237
|
+
let(:ox_doc) { ox_context.parse(xml) }
|
|
238
|
+
|
|
239
|
+
it "handles predicates that Ox cannot" do
|
|
240
|
+
# HeadedOx handles complex predicates
|
|
241
|
+
headed_result = doc.xpath("//book[@price < 20]")
|
|
242
|
+
expect(headed_result.size).to eq(2)
|
|
243
|
+
|
|
244
|
+
# Standard Ox would have issues with this predicate
|
|
245
|
+
# (it would need translation to locate() syntax)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "provides same parsing speed as Ox" do
|
|
249
|
+
# Both use Ox for parsing, so speed should be similar
|
|
250
|
+
start = Time.now
|
|
251
|
+
context.parse(xml)
|
|
252
|
+
headed_time = Time.now - start
|
|
253
|
+
|
|
254
|
+
start = Time.now
|
|
255
|
+
ox_context.parse(xml)
|
|
256
|
+
ox_time = Time.now - start
|
|
257
|
+
|
|
258
|
+
# Should be within same magnitude (both very fast)
|
|
259
|
+
expect(headed_time).to be < 0.1
|
|
260
|
+
expect(ox_time).to be < 0.1
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe "edge cases" do
|
|
265
|
+
it "handles empty results" do
|
|
266
|
+
result = doc.xpath("//nonexistent")
|
|
267
|
+
|
|
268
|
+
expect(result).to be_a(Moxml::NodeSet)
|
|
269
|
+
expect(result).to be_empty
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "handles nested predicates" do
|
|
273
|
+
result = doc.xpath(
|
|
274
|
+
"//book[author[contains(., 'Alice')]]",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
expect(result.size).to eq(1)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "handles union expressions" do
|
|
281
|
+
result = doc.xpath("//title | //author")
|
|
282
|
+
|
|
283
|
+
expect(result.size).to eq(6) # 3 titles + 3 authors
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it "handles attribute selection" do
|
|
287
|
+
prices = doc.xpath("//book/@price")
|
|
288
|
+
|
|
289
|
+
expect(prices.size).to eq(3)
|
|
290
|
+
expect(prices).to all(be_a(Moxml::Attribute))
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe "namespace support" do
|
|
295
|
+
let(:ns_xml) do
|
|
296
|
+
<<~XML
|
|
297
|
+
<library xmlns:bk="http://example.com/books">
|
|
298
|
+
<bk:book>
|
|
299
|
+
<bk:title>Test Book</bk:title>
|
|
300
|
+
</bk:book>
|
|
301
|
+
</library>
|
|
302
|
+
XML
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
let(:ns_doc) { context.parse(ns_xml) }
|
|
306
|
+
|
|
307
|
+
it "queries with namespace prefixes" do
|
|
308
|
+
result = ns_doc.xpath(
|
|
309
|
+
"//bk:book",
|
|
310
|
+
{ "bk" => "http://example.com/books" },
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
expect(result.size).to eq(1)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "queries namespace elements with functions" do
|
|
317
|
+
result = ns_doc.xpath(
|
|
318
|
+
"//bk:title",
|
|
319
|
+
{ "bk" => "http://example.com/books" },
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
expect(result.size).to eq(1)
|
|
323
|
+
expect(result.first.text).to eq("Test Book")
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
@@ -4,7 +4,8 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
4
4
|
let(:context) { Moxml.new }
|
|
5
5
|
|
|
6
6
|
describe "special characters handling" do
|
|
7
|
-
it "handles all kinds of whitespace",
|
|
7
|
+
it "handles all kinds of whitespace",
|
|
8
|
+
skip: "carriege returns are troublesome" do
|
|
8
9
|
# Nokogiri can't handle carriege returns properly
|
|
9
10
|
# https://github.com/sparklemotion/nokogiri/issues/1356
|
|
10
11
|
xml = "<root>\u0020\u0009 \u000D\u000A \u000D</root>"
|
|
@@ -31,12 +32,17 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
31
32
|
|
|
32
33
|
describe "malformed content handling" do
|
|
33
34
|
it "handles CDATA with nested markers" do
|
|
34
|
-
|
|
35
|
+
if context.config.adapter_name == :ox
|
|
36
|
+
pending "Ox doesn't escape the end token"
|
|
37
|
+
end
|
|
38
|
+
if context.config.adapter_name == :headed_ox
|
|
39
|
+
skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/HEADED_OX_LIMITATIONS.md"
|
|
40
|
+
end
|
|
35
41
|
cdata_text = "]]>]]>]]>"
|
|
36
42
|
doc = context.create_document
|
|
37
43
|
cdata = doc.create_cdata(cdata_text)
|
|
38
44
|
expect(cdata.to_xml).to include(
|
|
39
|
-
"]]]]><![CDATA[>]]]]><![CDATA[>]]]]><![CDATA[>"
|
|
45
|
+
"]]]]><![CDATA[>]]]]><![CDATA[>]]]]><![CDATA[>",
|
|
40
46
|
)
|
|
41
47
|
end
|
|
42
48
|
|
|
@@ -51,21 +57,24 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
51
57
|
doc = context.create_document
|
|
52
58
|
expect do
|
|
53
59
|
doc.create_comment("-- test -- comment --")
|
|
54
|
-
end.to raise_error(Moxml::ValidationError,
|
|
60
|
+
end.to raise_error(Moxml::ValidationError,
|
|
61
|
+
"XML comment cannot start or end with a hyphen")
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
it "rejects comments starting with hyphen" do
|
|
58
65
|
doc = context.create_document
|
|
59
66
|
expect do
|
|
60
67
|
doc.create_comment("-starting with hyphen")
|
|
61
|
-
end.to raise_error(Moxml::ValidationError,
|
|
68
|
+
end.to raise_error(Moxml::ValidationError,
|
|
69
|
+
"XML comment cannot start or end with a hyphen")
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
it "rejects comments ending with hyphen" do
|
|
65
73
|
doc = context.create_document
|
|
66
74
|
expect do
|
|
67
75
|
doc.create_comment("ending with hyphen-")
|
|
68
|
-
end.to raise_error(Moxml::ValidationError,
|
|
76
|
+
end.to raise_error(Moxml::ValidationError,
|
|
77
|
+
"XML comment cannot start or end with a hyphen")
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
it "accepts valid comments" do
|
|
@@ -77,7 +86,15 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
77
86
|
|
|
78
87
|
describe "namespace edge cases" do
|
|
79
88
|
it "handles default namespace changes" do
|
|
80
|
-
|
|
89
|
+
if context.config.adapter_name == :ox
|
|
90
|
+
pending "Ox doesn't have a native XPath"
|
|
91
|
+
end
|
|
92
|
+
if context.config.adapter_name == :headed_ox
|
|
93
|
+
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/HEADED_OX_LIMITATIONS.md"
|
|
94
|
+
end
|
|
95
|
+
if context.config.adapter_name == :libxml
|
|
96
|
+
skip "LibXML cannot query empty default namespace with XPath (documented limitation)"
|
|
97
|
+
end
|
|
81
98
|
xml = <<~XML
|
|
82
99
|
<root xmlns="http://default1.org">
|
|
83
100
|
<child xmlns="http://default2.org">
|
|
@@ -92,7 +109,12 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
92
109
|
end
|
|
93
110
|
|
|
94
111
|
it "handles recursive namespace definitions" do
|
|
95
|
-
|
|
112
|
+
if context.config.adapter_name == :ox
|
|
113
|
+
pending "Ox doesn't have a native XPath"
|
|
114
|
+
end
|
|
115
|
+
if context.config.adapter_name == :headed_ox
|
|
116
|
+
skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/HEADED_OX_LIMITATIONS.md"
|
|
117
|
+
end
|
|
96
118
|
|
|
97
119
|
xml = <<~XML
|
|
98
120
|
<root xmlns:a="http://a.org">
|
|
@@ -110,7 +132,12 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
110
132
|
|
|
111
133
|
describe "attribute edge cases" do
|
|
112
134
|
it "handles attributes with same local name but different namespaces" do
|
|
113
|
-
|
|
135
|
+
if context.config.adapter_name == :headed_ox
|
|
136
|
+
skip "HeadedOx limitation: Namespace-prefixed attribute access needs Ox namespace API enhancement. See docs/HEADED_OX_LIMITATIONS.md"
|
|
137
|
+
end
|
|
138
|
+
if context.config.adapter_name == :ox
|
|
139
|
+
skip "Ox doesn't have a native XPath"
|
|
140
|
+
end
|
|
114
141
|
|
|
115
142
|
xml = <<~XML
|
|
116
143
|
<root xmlns:a="http://a.org" xmlns:b="http://b.org">
|
|
@@ -133,7 +160,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
|
|
|
133
160
|
"space" => " ",
|
|
134
161
|
"tabs_newlines" => "\t\n",
|
|
135
162
|
"unicode" => "⚡",
|
|
136
|
-
"entities" => "<&>'\""
|
|
163
|
+
"entities" => "<&>'\"",
|
|
137
164
|
}
|
|
138
165
|
|
|
139
166
|
special_values.each do |name, value|
|
|
File without changes
|
|
@@ -16,7 +16,8 @@ RSpec.shared_examples "Moxml::Context" do
|
|
|
16
16
|
expect(doc.root.children.first.children.first).to be_a(Moxml::Text)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
it "maintains document structure",
|
|
19
|
+
it "maintains document structure",
|
|
20
|
+
skip: "Nokogiri doesn't consider the declaration as a child node" do
|
|
20
21
|
xml = <<~XML
|
|
21
22
|
<?xml version="1.0"?>
|
|
22
23
|
<!-- comment -->
|