moxml 0.1.7 → 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.
Files changed (212) 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 +238 -40
  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 +45 -0
  21. data/docs/_guides/modifying-xml.adoc +293 -0
  22. data/docs/_guides/parsing-xml.adoc +231 -0
  23. data/docs/_guides/sax-parsing.adoc +603 -0
  24. data/docs/_guides/working-with-documents.adoc +118 -0
  25. data/docs/_pages/adapter-compatibility.adoc +369 -0
  26. data/docs/_pages/adapters/headed-ox.adoc +237 -0
  27. data/docs/_pages/adapters/index.adoc +98 -0
  28. data/docs/_pages/adapters/libxml.adoc +286 -0
  29. data/docs/_pages/adapters/nokogiri.adoc +252 -0
  30. data/docs/_pages/adapters/oga.adoc +292 -0
  31. data/docs/_pages/adapters/ox.adoc +55 -0
  32. data/docs/_pages/adapters/rexml.adoc +293 -0
  33. data/docs/_pages/best-practices.adoc +430 -0
  34. data/docs/_pages/compatibility.adoc +468 -0
  35. data/docs/_pages/configuration.adoc +251 -0
  36. data/docs/_pages/error-handling.adoc +350 -0
  37. data/docs/_pages/headed-ox-limitations.adoc +558 -0
  38. data/docs/_pages/headed-ox.adoc +1025 -0
  39. data/docs/_pages/index.adoc +35 -0
  40. data/docs/_pages/installation.adoc +141 -0
  41. data/docs/_pages/node-api-reference.adoc +50 -0
  42. data/docs/_pages/performance.adoc +36 -0
  43. data/docs/_pages/quick-start.adoc +244 -0
  44. data/docs/_pages/thread-safety.adoc +29 -0
  45. data/docs/_references/document-api.adoc +408 -0
  46. data/docs/_references/index.adoc +48 -0
  47. data/docs/_tutorials/basic-usage.adoc +268 -0
  48. data/docs/_tutorials/builder-pattern.adoc +343 -0
  49. data/docs/_tutorials/index.adoc +33 -0
  50. data/docs/_tutorials/namespace-handling.adoc +325 -0
  51. data/docs/_tutorials/xpath-queries.adoc +359 -0
  52. data/docs/index.adoc +122 -0
  53. data/examples/README.md +124 -0
  54. data/examples/api_client/README.md +424 -0
  55. data/examples/api_client/api_client.rb +394 -0
  56. data/examples/api_client/example_response.xml +48 -0
  57. data/examples/headed_ox_example/README.md +90 -0
  58. data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
  59. data/examples/rss_parser/README.md +194 -0
  60. data/examples/rss_parser/example_feed.xml +93 -0
  61. data/examples/rss_parser/rss_parser.rb +189 -0
  62. data/examples/sax_parsing/README.md +50 -0
  63. data/examples/sax_parsing/data_extractor.rb +75 -0
  64. data/examples/sax_parsing/example.xml +21 -0
  65. data/examples/sax_parsing/large_file.rb +78 -0
  66. data/examples/sax_parsing/simple_parser.rb +55 -0
  67. data/examples/web_scraper/README.md +352 -0
  68. data/examples/web_scraper/example_page.html +201 -0
  69. data/examples/web_scraper/web_scraper.rb +312 -0
  70. data/lib/moxml/adapter/base.rb +107 -28
  71. data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
  72. data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
  73. data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
  74. data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
  75. data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
  76. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
  77. data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
  78. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  79. data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
  80. data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
  81. data/lib/moxml/adapter/headed_ox.rb +161 -0
  82. data/lib/moxml/adapter/libxml.rb +1548 -0
  83. data/lib/moxml/adapter/nokogiri.rb +121 -9
  84. data/lib/moxml/adapter/oga.rb +123 -12
  85. data/lib/moxml/adapter/ox.rb +282 -26
  86. data/lib/moxml/adapter/rexml.rb +127 -20
  87. data/lib/moxml/adapter.rb +21 -4
  88. data/lib/moxml/attribute.rb +6 -0
  89. data/lib/moxml/builder.rb +40 -4
  90. data/lib/moxml/config.rb +8 -3
  91. data/lib/moxml/context.rb +39 -1
  92. data/lib/moxml/doctype.rb +13 -1
  93. data/lib/moxml/document.rb +39 -6
  94. data/lib/moxml/document_builder.rb +27 -5
  95. data/lib/moxml/element.rb +71 -2
  96. data/lib/moxml/error.rb +175 -6
  97. data/lib/moxml/node.rb +94 -3
  98. data/lib/moxml/node_set.rb +34 -0
  99. data/lib/moxml/sax/block_handler.rb +194 -0
  100. data/lib/moxml/sax/element_handler.rb +124 -0
  101. data/lib/moxml/sax/handler.rb +113 -0
  102. data/lib/moxml/sax.rb +31 -0
  103. data/lib/moxml/version.rb +1 -1
  104. data/lib/moxml/xml_utils/encoder.rb +4 -4
  105. data/lib/moxml/xml_utils.rb +7 -4
  106. data/lib/moxml/xpath/ast/node.rb +159 -0
  107. data/lib/moxml/xpath/cache.rb +91 -0
  108. data/lib/moxml/xpath/compiler.rb +1768 -0
  109. data/lib/moxml/xpath/context.rb +26 -0
  110. data/lib/moxml/xpath/conversion.rb +124 -0
  111. data/lib/moxml/xpath/engine.rb +52 -0
  112. data/lib/moxml/xpath/errors.rb +101 -0
  113. data/lib/moxml/xpath/lexer.rb +304 -0
  114. data/lib/moxml/xpath/parser.rb +485 -0
  115. data/lib/moxml/xpath/ruby/generator.rb +269 -0
  116. data/lib/moxml/xpath/ruby/node.rb +193 -0
  117. data/lib/moxml/xpath.rb +37 -0
  118. data/lib/moxml.rb +5 -2
  119. data/moxml.gemspec +3 -1
  120. data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
  121. data/spec/consistency/README.md +77 -0
  122. data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
  123. data/spec/examples/README.md +75 -0
  124. data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
  125. data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
  126. data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
  127. data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
  128. data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
  129. data/spec/integration/README.md +71 -0
  130. data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
  131. data/spec/integration/headed_ox_integration_spec.rb +326 -0
  132. data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
  133. data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
  134. data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
  135. data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
  136. data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
  137. data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
  138. data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
  139. data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -2
  140. data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
  141. data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
  142. data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
  143. data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
  144. data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
  145. data/spec/moxml/README.md +41 -0
  146. data/spec/moxml/adapter/.gitkeep +0 -0
  147. data/spec/moxml/adapter/README.md +61 -0
  148. data/spec/moxml/adapter/base_spec.rb +27 -0
  149. data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
  150. data/spec/moxml/adapter/libxml_spec.rb +14 -0
  151. data/spec/moxml/adapter/ox_spec.rb +9 -8
  152. data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
  153. data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
  154. data/spec/moxml/adapter_spec.rb +16 -0
  155. data/spec/moxml/attribute_spec.rb +30 -0
  156. data/spec/moxml/builder_spec.rb +33 -0
  157. data/spec/moxml/cdata_spec.rb +31 -0
  158. data/spec/moxml/comment_spec.rb +31 -0
  159. data/spec/moxml/config_spec.rb +3 -3
  160. data/spec/moxml/context_spec.rb +28 -0
  161. data/spec/moxml/declaration_spec.rb +36 -0
  162. data/spec/moxml/doctype_spec.rb +33 -0
  163. data/spec/moxml/document_builder_spec.rb +30 -0
  164. data/spec/moxml/document_spec.rb +105 -0
  165. data/spec/moxml/element_spec.rb +143 -0
  166. data/spec/moxml/error_spec.rb +266 -22
  167. data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
  168. data/spec/moxml/namespace_spec.rb +32 -0
  169. data/spec/moxml/node_set_spec.rb +39 -0
  170. data/spec/moxml/node_spec.rb +37 -0
  171. data/spec/moxml/processing_instruction_spec.rb +34 -0
  172. data/spec/moxml/sax_spec.rb +1067 -0
  173. data/spec/moxml/text_spec.rb +31 -0
  174. data/spec/moxml/version_spec.rb +14 -0
  175. data/spec/moxml/xml_utils/.gitkeep +0 -0
  176. data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
  177. data/spec/moxml/xml_utils_spec.rb +49 -0
  178. data/spec/moxml/xpath/ast/node_spec.rb +83 -0
  179. data/spec/moxml/xpath/axes_spec.rb +296 -0
  180. data/spec/moxml/xpath/cache_spec.rb +358 -0
  181. data/spec/moxml/xpath/compiler_spec.rb +406 -0
  182. data/spec/moxml/xpath/context_spec.rb +210 -0
  183. data/spec/moxml/xpath/conversion_spec.rb +365 -0
  184. data/spec/moxml/xpath/fixtures/sample.xml +25 -0
  185. data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
  186. data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
  187. data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
  188. data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
  189. data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
  190. data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
  191. data/spec/moxml/xpath/lexer_spec.rb +488 -0
  192. data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
  193. data/spec/moxml/xpath/parser_spec.rb +364 -0
  194. data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
  195. data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
  196. data/spec/moxml/xpath_capabilities_spec.rb +199 -0
  197. data/spec/moxml/xpath_spec.rb +77 -0
  198. data/spec/performance/README.md +83 -0
  199. data/spec/performance/benchmark_spec.rb +64 -0
  200. data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +3 -1
  201. data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
  202. data/spec/performance/xpath_benchmark_spec.rb +259 -0
  203. data/spec/spec_helper.rb +58 -1
  204. data/spec/support/xml_matchers.rb +1 -1
  205. metadata +176 -34
  206. data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
  207. /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
  208. /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
  209. /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
  210. /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
  211. /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
  212. /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
@@ -5,6 +5,7 @@ RSpec.shared_examples "Moxml Integration" do
5
5
 
6
6
  describe "complete document workflow" do
7
7
  let(:doc) { context.create_document }
8
+
8
9
  before do
9
10
  # Create document with declaration
10
11
  doc.add_child(doc.create_declaration("1.0", "UTF-8"))
@@ -16,7 +17,8 @@ RSpec.shared_examples "Moxml Integration" do
16
17
  doc.add_child(root)
17
18
 
18
19
  # Add processing instruction
19
- style_pi = doc.create_processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
20
+ style_pi = doc.create_processing_instruction("xml-stylesheet",
21
+ 'type="text/xsl" href="style.xsl"')
20
22
  root.add_previous_sibling(style_pi)
21
23
 
22
24
  # Add mixed content
@@ -44,7 +46,7 @@ RSpec.shared_examples "Moxml Integration" do
44
46
  "<!-- Mixed content example -->",
45
47
  "<text>Some text <![CDATA[<with><markup/>]]> and more text</text>",
46
48
  '<item id="123" xs:type="custom"></item>',
47
- "</root>"
49
+ "</root>",
48
50
  ]
49
51
 
50
52
  expected_xml_tags.each do |expected_xml|
@@ -54,7 +56,12 @@ RSpec.shared_examples "Moxml Integration" do
54
56
  end
55
57
 
56
58
  it "handles xpath queries" do
57
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
59
+ if context.config.adapter_name == :ox
60
+ pending "Ox doesn't support namespace-aware XPath with predicates"
61
+ end
62
+ if context.config.adapter_name == :headed_ox
63
+ skip "HeadedOx limitation: Namespace-aware XPath with predicates needs investigation. See docs/HEADED_OX_LIMITATIONS.md"
64
+ end
58
65
  # Test XPath queries
59
66
  #
60
67
  # XPath with a default namespace is a problem
@@ -69,7 +76,12 @@ RSpec.shared_examples "Moxml Integration" do
69
76
 
70
77
  describe "namespace handling" do
71
78
  it "handles complex namespace scenarios" do
72
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
79
+ if context.config.adapter_name == :ox
80
+ pending "Ox doesn't have a native XPath"
81
+ end
82
+ if context.config.adapter_name == :headed_ox
83
+ skip "HeadedOx limitation: Namespace methods not implemented in adapter. Requires Ox namespace API enhancement. See docs/HEADED_OX_LIMITATIONS.md"
84
+ end
73
85
  xml = <<~XML
74
86
  <root xmlns="http://default.org" xmlns:a="http://a.org" xmlns:b="http://b.org">
75
87
  <child>
@@ -110,7 +122,12 @@ RSpec.shared_examples "Moxml Integration" do
110
122
  let(:doc) { context.parse("<root><a>1</a><b>2</b></root>") }
111
123
 
112
124
  it "handles complex modifications" do
113
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
125
+ if context.config.adapter_name == :headed_ox
126
+ skip "HeadedOx limitation: Parent setter not implemented. Requires Ox node reparenting API. See docs/HEADED_OX_LIMITATIONS.md"
127
+ end
128
+ if context.config.adapter_name == :ox
129
+ skip "Ox doesn't have a native XPath"
130
+ end
114
131
 
115
132
  # Move nodes
116
133
  b_node = doc.at_xpath("//b")
@@ -132,7 +149,7 @@ RSpec.shared_examples "Moxml Integration" do
132
149
  expect(doc.root.children.map(&:name)).to eq(%w[b c a])
133
150
  expect(doc.to_xml).to include(
134
151
  '<root id="main">',
135
- "<b>2<!-- comment --><![CDATA[<tag>]]></b>"
152
+ "<b>2<!-- comment --><![CDATA[<tag>]]></b>",
136
153
  )
137
154
  end
138
155
  end
@@ -37,7 +37,12 @@ RSpec.shared_examples "Moxml::Cdata" do
37
37
 
38
38
  it "escapes CDATA end marker" do
39
39
  # pending for Ox: https://github.com/ohler55/ox/issues/377
40
- pending "Ox doesn't escape the end token" if context.config.adapter_name == :ox
40
+ if context.config.adapter_name == :ox
41
+ pending "Ox doesn't escape the end token"
42
+ end
43
+ if context.config.adapter_name == :headed_ox
44
+ skip "HeadedOx limitation: Ox doesn't escape CDATA end markers. See docs/HEADED_OX_LIMITATIONS.md"
45
+ end
41
46
  cdata.content = "content]]>more"
42
47
  expect(cdata.to_xml).to eq("<![CDATA[content]]]]><![CDATA[>more]]>")
43
48
  end
@@ -32,7 +32,8 @@ RSpec.shared_examples "Moxml::Comment" do
32
32
 
33
33
  it "raises an error on double hyphens" do
34
34
  expect { comment.content = "test -- comment" }
35
- .to raise_error(Moxml::ValidationError, "XML comment cannot contain double hyphens (--)")
35
+ .to raise_error(Moxml::ValidationError,
36
+ "XML comment cannot contain double hyphens (--)")
36
37
  end
37
38
 
38
39
  it "handles special characters" do
@@ -36,7 +36,9 @@ RSpec.shared_examples "Moxml::Declaration" do
36
36
  end
37
37
 
38
38
  it "normalizes encoding" do
39
- pending("Rexml Encoding upcases the string") if Moxml.new.config.adapter.name.include?("Rexml")
39
+ if Moxml.new.config.adapter.name.include?("Rexml")
40
+ pending("Rexml Encoding upcases the string")
41
+ end
40
42
  declaration.encoding = "utf-8"
41
43
  expect(declaration.encoding).to eq("utf-8")
42
44
  end
@@ -54,7 +56,8 @@ RSpec.shared_examples "Moxml::Declaration" do
54
56
 
55
57
  it "validates standalone value" do
56
58
  expect { declaration.standalone = "maybe" }
57
- .to raise_error(Moxml::ValidationError, "Invalid standalone value: maybe")
59
+ .to raise_error(Moxml::ValidationError,
60
+ "Invalid standalone value: maybe")
58
61
  end
59
62
 
60
63
  it "allows nil standalone" do
@@ -7,7 +7,7 @@ RSpec.shared_examples "Moxml::Doctype" do
7
7
  doc.create_doctype(
8
8
  "html",
9
9
  "-//W3C//DTD HTML 4.01 Transitional//EN",
10
- "http://www.w3.org/TR/html4/loose.dtd"
10
+ "http://www.w3.org/TR/html4/loose.dtd",
11
11
  )
12
12
  end
13
13
 
@@ -19,7 +19,7 @@ RSpec.shared_examples "Moxml::Doctype" do
19
19
  it "wraps content in doctype markers" do
20
20
  doc.add_child(doctype)
21
21
  expect(doc.to_xml.strip).to end_with(
22
- '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
22
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
23
23
  )
24
24
  end
25
25
  end
@@ -103,7 +103,7 @@ RSpec.shared_examples "Moxml::Document" do
103
103
  '<?xml version="1.0" encoding="UTF-8"?>',
104
104
  "<root>",
105
105
  "<child>text</child>",
106
- "</root>"
106
+ "</root>",
107
107
  )
108
108
  end
109
109
 
@@ -96,7 +96,9 @@ RSpec.shared_examples "Moxml::Node" do
96
96
  let(:doc) { context.parse("<root><a><b>1</b></a><a><b>2</b></a></root>") }
97
97
 
98
98
  it "finds nodes by xpath" do
99
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
99
+ if context.config.adapter_name == :ox
100
+ pending "Ox doesn't have a native XPath"
101
+ end
100
102
 
101
103
  nodes = doc.xpath("//b")
102
104
  expect(nodes.size).to eq(2)
@@ -104,7 +106,12 @@ RSpec.shared_examples "Moxml::Node" do
104
106
  end
105
107
 
106
108
  it "finds first node by xpath" do
107
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
109
+ if context.config.adapter_name == :ox
110
+ pending "Ox doesn't have a native XPath"
111
+ end
112
+ if context.config.adapter_name == :headed_ox
113
+ skip "HeadedOx limitation: Text content access from nested elements needs investigation. See docs/HEADED_OX_LIMITATIONS.md"
114
+ end
108
115
 
109
116
  node = doc.at_xpath("//b")
110
117
  expect(node.text).to eq("1")
@@ -25,47 +25,34 @@ RSpec.shared_examples "Moxml::NodeSet" do
25
25
 
26
26
  describe "enumeration" do
27
27
  it "iterates over nodes" do
28
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
29
-
30
- texts = []
31
- nodes.each { |node| texts << node.text }
28
+ texts = nodes.map(&:text)
32
29
  expect(texts).to eq(%w[First Second Third])
33
30
  end
34
31
 
35
32
  it "maps nodes" do
36
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
37
-
38
33
  texts = nodes.map(&:text)
39
34
  expect(texts).to eq(%w[First Second Third])
40
35
  end
41
36
 
42
37
  it "selects nodes" do
43
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
44
-
45
38
  selected = nodes.select { |node| node.text.include?("i") }
46
39
  expect(selected.size).to eq(2)
47
40
  expect(selected.map(&:text)).to eq(%w[First Third])
48
41
  end
49
42
 
50
43
  it "compares nodes" do
51
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
52
-
53
44
  expect(doc.xpath("//child")).to eq(doc.root.children)
54
45
  end
55
46
  end
56
47
 
57
48
  describe "access methods" do
58
49
  it "accesses by index" do
59
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
60
-
61
50
  expect(nodes[0].text).to eq("First")
62
51
  expect(nodes[1].text).to eq("Second")
63
52
  expect(nodes[-1].text).to eq("Third")
64
53
  end
65
54
 
66
55
  it "accesses by range" do
67
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
68
-
69
56
  subset = nodes[0..1]
70
57
  expect(subset).to be_a(described_class)
71
58
  expect(subset.size).to eq(2)
@@ -73,8 +60,6 @@ RSpec.shared_examples "Moxml::NodeSet" do
73
60
  end
74
61
 
75
62
  it "provides first and last" do
76
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
77
-
78
63
  expect(nodes.first.text).to eq("First")
79
64
  expect(nodes.last.text).to eq("Third")
80
65
  end
@@ -95,8 +80,6 @@ RSpec.shared_examples "Moxml::NodeSet" do
95
80
 
96
81
  describe "concatenation" do
97
82
  it "combines node sets" do
98
- pending "Ox doesn't have a native XPath" if context.config.adapter_name == :ox
99
-
100
83
  other_doc = context.parse("<root><item>Fourth</item></root>")
101
84
  other_nodes = other_doc.xpath("//item")
102
85
  combined = nodes + other_nodes
@@ -3,7 +3,10 @@
3
3
  RSpec.shared_examples "Moxml::ProcessingInstruction" do
4
4
  let(:context) { Moxml.new }
5
5
  let(:doc) { context.create_document }
6
- let(:pi) { doc.create_processing_instruction("xml-stylesheet", 'href="style.xsl" type="text/xsl"') }
6
+ let(:pi) do
7
+ doc.create_processing_instruction("xml-stylesheet",
8
+ 'href="style.xsl" type="text/xsl"')
9
+ end
7
10
 
8
11
  it "identifies as processing instruction node" do
9
12
  expect(pi).to be_processing_instruction
@@ -76,7 +79,8 @@ RSpec.shared_examples "Moxml::ProcessingInstruction" do
76
79
 
77
80
  describe "common use cases" do
78
81
  it "creates stylesheet instruction" do
79
- pi = doc.create_processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
82
+ pi = doc.create_processing_instruction("xml-stylesheet",
83
+ 'type="text/xsl" href="style.xsl"')
80
84
  expect(pi.to_xml).to eq('<?xml-stylesheet type="text/xsl" href="style.xsl"?>')
81
85
  end
82
86
 
@@ -0,0 +1,41 @@
1
+ # Unit Tests
2
+
3
+ ## Purpose
4
+
5
+ This directory contains pure unit tests for individual classes without adapter switching. Each test focuses on a single class's behavior, state management, and convenience APIs.
6
+
7
+ ## What Should Be Placed Here
8
+
9
+ - ✅ Tests for individual class methods
10
+ - ✅ Tests for convenience APIs (e.g., `Element#set_attributes`)
11
+ - ✅ Tests for internal utilities
12
+ - ✅ Tests for configuration and setup
13
+ - ✅ Tests that use a single adapter (usually the default)
14
+ - ✅ Tests for class-level functionality independent of adapter implementation
15
+
16
+ ## What Should NOT Be Placed Here
17
+
18
+ - ❌ Tests that switch between multiple adapters
19
+ - ❌ Tests for cross-adapter behavior
20
+ - ❌ Integration tests that involve multiple classes
21
+ - ❌ Performance benchmarks
22
+ - ❌ Documentation examples
23
+
24
+ ## How to Run
25
+
26
+ ```bash
27
+ # Run all unit tests
28
+ bundle exec rake spec:unit
29
+
30
+ # Run specific unit test file
31
+ bundle exec rspec spec/unit/moxml/element_spec.rb
32
+ ```
33
+
34
+ ## Directory Structure
35
+
36
+ ```
37
+ unit/
38
+ ├── moxml/ # Core moxml classes
39
+ │ ├── adapter/ # Adapter registry and base
40
+ │ └── xml_utils/ # Utility classes
41
+ └── moxml_spec.rb # Top-level module tests
File without changes
@@ -0,0 +1,61 @@
1
+ # Adapter Tests
2
+ #
3
+ # ## Purpose
4
+ #
5
+ # This directory contains tests for adapter implementations. Each test verifies that an adapter correctly implements the adapter API contract and tests adapter-specific features.
6
+ #
7
+ # ## What Should Be Placed Here
8
+ #
9
+ # - ✅ Tests for adapter API contract compliance (parse, create_*, serialize, xpath, etc.)
10
+ # - ✅ Tests for adapter-specific features
11
+ # - ✅ Tests for adapter workarounds/customizations
12
+ # - ✅ Each adapter tested independently
13
+ # - ✅ Adapter initialization and configuration tests
14
+ #
15
+ # ## What Should NOT Be Placed Here
16
+ #
17
+ # - ❌ Tests for high-level wrapper behavior (use integration/ instead)
18
+ # - ❌ Tests for cross-adapter consistency (use consistency/ instead)
19
+ # - ❌ Tests for Moxml wrapper classes
20
+ # - ❌ Performance benchmarks (use performance/ instead)
21
+ #
22
+ # ## How to Run
23
+ #
24
+ # ```bash
25
+ # # Run all adapter tests
26
+ # bundle exec rake spec:adapter
27
+ #
28
+ # # Run specific adapter test
29
+ # bundle exec rspec spec/moxml/adapter/nokogiri_spec.rb
30
+ # ```
31
+ #
32
+ # ## Directory Structure
33
+ #
34
+ # ```
35
+ # moxml/adapter/
36
+ # ├── shared_examples/
37
+ # │ └── adapter_contract.rb # Defines the adapter API contract
38
+ # ├── base_spec.rb # Base adapter tests
39
+ # ├── libxml_spec.rb # LibXML adapter implementation
40
+ # ├── nokogiri_spec.rb # Nokogiri adapter implementation
41
+ # ├── oga_spec.rb # Oga adapter implementation
42
+ # ├── ox_spec.rb # Ox adapter implementation
43
+ # └── rexml_spec.rb # REXML adapter implementation
44
+ # ```
45
+ #
46
+ # ## Adding a New Adapter
47
+ #
48
+ # When adding a new adapter:
49
+ #
50
+ # 1. Create `spec/moxml/adapter/<adapter_name>_spec.rb`
51
+ # 2. Include the adapter contract:
52
+ # ```ruby
53
+ # RSpec.describe Moxml::Adapter::YourAdapter do
54
+ # around do |example|
55
+ # Moxml.with_config(:your_adapter, true, "UTF-8") do
56
+ # example.run
57
+ # end
58
+ # end
59
+ #
60
+ # it_behaves_like "adapter contract"
61
+ # end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Moxml::Adapter::Base do
6
+ describe ".name" do
7
+ it "returns adapter name" do
8
+ expect(described_class.name).to include("Base")
9
+ end
10
+ end
11
+
12
+ describe "interface methods" do
13
+ it "defines parse method" do
14
+ expect(described_class).to respond_to(:parse)
15
+ end
16
+
17
+ it "defines create_document method" do
18
+ expect(described_class).to respond_to(:create_document)
19
+ end
20
+
21
+ it "defines serialize method" do
22
+ # Base class doesn't implement serialize - each adapter does
23
+ # This is tested in the individual adapter specs
24
+ skip "Serialize is adapter-specific, not in Base"
25
+ end
26
+ end
27
+ end