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.
Files changed (215) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +5 -0
  3. data/.github/workflows/dependent-tests.yml +20 -0
  4. data/.github/workflows/docs.yml +59 -0
  5. data/.github/workflows/rake.yml +10 -10
  6. data/.github/workflows/release.yml +5 -3
  7. data/.gitignore +37 -0
  8. data/.rubocop.yml +15 -7
  9. data/.rubocop_todo.yml +224 -43
  10. data/Gemfile +14 -9
  11. data/LICENSE.md +6 -2
  12. data/README.adoc +535 -373
  13. data/Rakefile +53 -0
  14. data/benchmarks/.gitignore +6 -0
  15. data/benchmarks/generate_report.rb +550 -0
  16. data/docs/Gemfile +13 -0
  17. data/docs/_config.yml +138 -0
  18. data/docs/_guides/advanced-features.adoc +87 -0
  19. data/docs/_guides/development-testing.adoc +165 -0
  20. data/docs/_guides/index.adoc +51 -0
  21. data/docs/_guides/modifying-xml.adoc +292 -0
  22. data/docs/_guides/parsing-xml.adoc +230 -0
  23. data/docs/_guides/sax-parsing.adoc +603 -0
  24. data/docs/_guides/working-with-documents.adoc +118 -0
  25. data/docs/_guides/xml-declaration.adoc +450 -0
  26. data/docs/_pages/adapter-compatibility.adoc +369 -0
  27. data/docs/_pages/adapters/headed-ox.adoc +237 -0
  28. data/docs/_pages/adapters/index.adoc +97 -0
  29. data/docs/_pages/adapters/libxml.adoc +285 -0
  30. data/docs/_pages/adapters/nokogiri.adoc +251 -0
  31. data/docs/_pages/adapters/oga.adoc +291 -0
  32. data/docs/_pages/adapters/ox.adoc +56 -0
  33. data/docs/_pages/adapters/rexml.adoc +292 -0
  34. data/docs/_pages/best-practices.adoc +429 -0
  35. data/docs/_pages/compatibility.adoc +467 -0
  36. data/docs/_pages/configuration.adoc +250 -0
  37. data/docs/_pages/error-handling.adoc +349 -0
  38. data/docs/_pages/headed-ox-limitations.adoc +574 -0
  39. data/docs/_pages/headed-ox.adoc +1025 -0
  40. data/docs/_pages/index.adoc +35 -0
  41. data/docs/_pages/installation.adoc +140 -0
  42. data/docs/_pages/node-api-reference.adoc +49 -0
  43. data/docs/_pages/performance.adoc +35 -0
  44. data/docs/_pages/quick-start.adoc +243 -0
  45. data/docs/_pages/thread-safety.adoc +28 -0
  46. data/docs/_references/document-api.adoc +407 -0
  47. data/docs/_references/index.adoc +48 -0
  48. data/docs/_tutorials/basic-usage.adoc +267 -0
  49. data/docs/_tutorials/builder-pattern.adoc +342 -0
  50. data/docs/_tutorials/index.adoc +33 -0
  51. data/docs/_tutorials/namespace-handling.adoc +324 -0
  52. data/docs/_tutorials/xpath-queries.adoc +358 -0
  53. data/docs/index.adoc +122 -0
  54. data/examples/README.md +124 -0
  55. data/examples/api_client/README.md +424 -0
  56. data/examples/api_client/api_client.rb +394 -0
  57. data/examples/api_client/example_response.xml +48 -0
  58. data/examples/headed_ox_example/README.md +90 -0
  59. data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
  60. data/examples/rss_parser/README.md +194 -0
  61. data/examples/rss_parser/example_feed.xml +93 -0
  62. data/examples/rss_parser/rss_parser.rb +189 -0
  63. data/examples/sax_parsing/README.md +50 -0
  64. data/examples/sax_parsing/data_extractor.rb +75 -0
  65. data/examples/sax_parsing/example.xml +21 -0
  66. data/examples/sax_parsing/large_file.rb +78 -0
  67. data/examples/sax_parsing/simple_parser.rb +55 -0
  68. data/examples/web_scraper/README.md +352 -0
  69. data/examples/web_scraper/example_page.html +201 -0
  70. data/examples/web_scraper/web_scraper.rb +312 -0
  71. data/lib/moxml/adapter/base.rb +107 -28
  72. data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
  73. data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
  74. data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
  75. data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
  76. data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
  77. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
  78. data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
  79. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  80. data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
  81. data/lib/moxml/adapter/customized_rexml/formatter.rb +13 -8
  82. data/lib/moxml/adapter/headed_ox.rb +161 -0
  83. data/lib/moxml/adapter/libxml.rb +1564 -0
  84. data/lib/moxml/adapter/nokogiri.rb +156 -9
  85. data/lib/moxml/adapter/oga.rb +190 -15
  86. data/lib/moxml/adapter/ox.rb +322 -28
  87. data/lib/moxml/adapter/rexml.rb +157 -28
  88. data/lib/moxml/adapter.rb +21 -4
  89. data/lib/moxml/attribute.rb +6 -0
  90. data/lib/moxml/builder.rb +40 -4
  91. data/lib/moxml/config.rb +8 -3
  92. data/lib/moxml/context.rb +57 -2
  93. data/lib/moxml/declaration.rb +9 -0
  94. data/lib/moxml/doctype.rb +13 -1
  95. data/lib/moxml/document.rb +53 -6
  96. data/lib/moxml/document_builder.rb +34 -5
  97. data/lib/moxml/element.rb +71 -2
  98. data/lib/moxml/error.rb +175 -6
  99. data/lib/moxml/node.rb +155 -4
  100. data/lib/moxml/node_set.rb +34 -0
  101. data/lib/moxml/sax/block_handler.rb +194 -0
  102. data/lib/moxml/sax/element_handler.rb +124 -0
  103. data/lib/moxml/sax/handler.rb +113 -0
  104. data/lib/moxml/sax.rb +31 -0
  105. data/lib/moxml/version.rb +1 -1
  106. data/lib/moxml/xml_utils/encoder.rb +4 -4
  107. data/lib/moxml/xml_utils.rb +7 -4
  108. data/lib/moxml/xpath/ast/node.rb +159 -0
  109. data/lib/moxml/xpath/cache.rb +91 -0
  110. data/lib/moxml/xpath/compiler.rb +1770 -0
  111. data/lib/moxml/xpath/context.rb +26 -0
  112. data/lib/moxml/xpath/conversion.rb +124 -0
  113. data/lib/moxml/xpath/engine.rb +52 -0
  114. data/lib/moxml/xpath/errors.rb +101 -0
  115. data/lib/moxml/xpath/lexer.rb +304 -0
  116. data/lib/moxml/xpath/parser.rb +485 -0
  117. data/lib/moxml/xpath/ruby/generator.rb +269 -0
  118. data/lib/moxml/xpath/ruby/node.rb +193 -0
  119. data/lib/moxml/xpath.rb +37 -0
  120. data/lib/moxml.rb +5 -2
  121. data/moxml.gemspec +3 -1
  122. data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
  123. data/spec/consistency/README.md +77 -0
  124. data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
  125. data/spec/examples/README.md +75 -0
  126. data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
  127. data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
  128. data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
  129. data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
  130. data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
  131. data/spec/integration/README.md +71 -0
  132. data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
  133. data/spec/integration/headed_ox_integration_spec.rb +326 -0
  134. data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
  135. data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
  136. data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
  137. data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
  138. data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
  139. data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
  140. data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
  141. data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -5
  142. data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
  143. data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
  144. data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
  145. data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
  146. data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
  147. data/spec/moxml/README.md +41 -0
  148. data/spec/moxml/adapter/.gitkeep +0 -0
  149. data/spec/moxml/adapter/README.md +61 -0
  150. data/spec/moxml/adapter/base_spec.rb +27 -0
  151. data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
  152. data/spec/moxml/adapter/libxml_spec.rb +14 -0
  153. data/spec/moxml/adapter/ox_spec.rb +9 -8
  154. data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
  155. data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
  156. data/spec/moxml/adapter_spec.rb +16 -0
  157. data/spec/moxml/attribute_spec.rb +30 -0
  158. data/spec/moxml/builder_spec.rb +33 -0
  159. data/spec/moxml/cdata_spec.rb +31 -0
  160. data/spec/moxml/comment_spec.rb +31 -0
  161. data/spec/moxml/config_spec.rb +3 -3
  162. data/spec/moxml/context_spec.rb +28 -0
  163. data/spec/moxml/declaration_preservation_spec.rb +217 -0
  164. data/spec/moxml/declaration_spec.rb +36 -0
  165. data/spec/moxml/doctype_spec.rb +33 -0
  166. data/spec/moxml/document_builder_spec.rb +30 -0
  167. data/spec/moxml/document_spec.rb +105 -0
  168. data/spec/moxml/element_spec.rb +143 -0
  169. data/spec/moxml/error_spec.rb +266 -22
  170. data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
  171. data/spec/moxml/namespace_spec.rb +32 -0
  172. data/spec/moxml/node_set_spec.rb +39 -0
  173. data/spec/moxml/node_spec.rb +37 -0
  174. data/spec/moxml/processing_instruction_spec.rb +34 -0
  175. data/spec/moxml/sax_spec.rb +1067 -0
  176. data/spec/moxml/text_spec.rb +31 -0
  177. data/spec/moxml/version_spec.rb +14 -0
  178. data/spec/moxml/xml_utils/.gitkeep +0 -0
  179. data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
  180. data/spec/moxml/xml_utils_spec.rb +49 -0
  181. data/spec/moxml/xpath/ast/node_spec.rb +83 -0
  182. data/spec/moxml/xpath/axes_spec.rb +296 -0
  183. data/spec/moxml/xpath/cache_spec.rb +358 -0
  184. data/spec/moxml/xpath/compiler_spec.rb +406 -0
  185. data/spec/moxml/xpath/context_spec.rb +210 -0
  186. data/spec/moxml/xpath/conversion_spec.rb +365 -0
  187. data/spec/moxml/xpath/fixtures/sample.xml +25 -0
  188. data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
  189. data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
  190. data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
  191. data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
  192. data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
  193. data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
  194. data/spec/moxml/xpath/lexer_spec.rb +488 -0
  195. data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
  196. data/spec/moxml/xpath/parser_spec.rb +364 -0
  197. data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
  198. data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
  199. data/spec/moxml/xpath_capabilities_spec.rb +199 -0
  200. data/spec/moxml/xpath_spec.rb +77 -0
  201. data/spec/performance/README.md +83 -0
  202. data/spec/performance/benchmark_spec.rb +64 -0
  203. data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +4 -1
  204. data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
  205. data/spec/performance/xpath_benchmark_spec.rb +259 -0
  206. data/spec/spec_helper.rb +58 -1
  207. data/spec/support/xml_matchers.rb +1 -1
  208. metadata +178 -34
  209. data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
  210. /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
  211. /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
  212. /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
  213. /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
  214. /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
  215. /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
@@ -27,7 +27,7 @@ RSpec.shared_examples "Attribute Examples" do
27
27
  element["special"] = '< > & " \''
28
28
 
29
29
  expect(element.to_xml).to include(
30
- 'special="&lt; &gt; &amp; &quot; \''
30
+ 'special="&lt; &gt; &amp; &quot; \'',
31
31
  )
32
32
  end
33
33
 
@@ -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
@@ -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
@@ -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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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 = ::Moxml::NodeSet.new(nodes, doc.context)
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 "Test all shared examples" do
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", skip: "carriege returns are troublesome" do
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
- pending "Ox doesn't escape the end token" if context.config.adapter_name == :ox
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, "XML comment cannot start or end with a hyphen")
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, "XML comment cannot start or end with a hyphen")
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, "XML comment cannot start or end with a hyphen")
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
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|
@@ -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", skip: "Nokogiri doesn't consider the declaration as a child node" do
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 -->