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
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "benchmark/ips"
5
+ require "yaml"
6
+ require "fileutils"
7
+ require "time"
8
+
9
+ RSpec.describe "XPath Performance Benchmark" do
10
+ if ENV["SKIP_BENCHMARKS"]
11
+ it "skips benchmarks when SKIP_BENCHMARKS is set" do
12
+ skip "Benchmarks skipped. To run benchmarks, use: bundle exec rspec " \
13
+ "spec/moxml/examples/xpath_benchmark_spec.rb or rake benchmark:xpath"
14
+ end
15
+ else
16
+ let(:sample_xml) do
17
+ <<~XML
18
+ <?xml version="1.0"?>
19
+ <library xmlns="http://example.org/library">
20
+ <book id="1" category="fiction">
21
+ <title>Book One</title>
22
+ <author>Author A</author>
23
+ <price>10.99</price>
24
+ </book>
25
+ <book id="2" category="non-fiction">
26
+ <title>Book Two</title>
27
+ <author>Author B</author>
28
+ <price>15.99</price>
29
+ </book>
30
+ <book id="3" category="fiction">
31
+ <title>Book Three</title>
32
+ <author>Author A</author>
33
+ <price>12.99</price>
34
+ </book>
35
+ <magazine id="4">
36
+ <title>Magazine One</title>
37
+ <publisher>Publisher X</publisher>
38
+ </magazine>
39
+ <magazine id="5">
40
+ <title>Magazine Two</title>
41
+ <publisher>Publisher Y</publisher>
42
+ </magazine>
43
+ </library>
44
+ XML
45
+ end
46
+
47
+ let(:adapters) do
48
+ %i[nokogiri libxml oga rexml ox headed_ox]
49
+ end
50
+
51
+ let(:xpath_patterns) do
52
+ {
53
+ "Simple descendant (//book)" => "//book",
54
+ "Absolute path (/library/book)" => "/library/book",
55
+ "Attribute predicate (//book[@id])" => "//book[@id]",
56
+ "Wildcard (//*/title)" => "//*/title",
57
+ }
58
+ end
59
+
60
+ describe "XPath query performance" do
61
+ it "benchmarks XPath operations across all adapters" do
62
+ puts "\n#{'=' * 80}"
63
+ puts "XPath Performance Benchmark - All Adapters"
64
+ puts "=" * 80
65
+
66
+ xpath_patterns.each do |pattern_name, xpath|
67
+ puts "\nPattern: #{pattern_name}"
68
+ puts "-" * 80
69
+
70
+ Benchmark.ips do |x|
71
+ x.config(time: 5, warmup: 2)
72
+
73
+ adapters.each do |adapter|
74
+ x.report(adapter.to_s) do
75
+ Moxml::Config.default_adapter = adapter
76
+ doc = Moxml.new.parse(sample_xml)
77
+ doc.xpath(xpath)
78
+ rescue StandardError
79
+ # Adapter doesn't support this pattern
80
+ nil
81
+ end
82
+ end
83
+
84
+ x.compare!
85
+ end
86
+
87
+ puts "\n"
88
+ end
89
+
90
+ puts "=" * 80
91
+ puts "Benchmark complete"
92
+ puts "=" * 80
93
+ end
94
+
95
+ it "generates detailed performance comparison table" do
96
+ puts "\n#{'=' * 80}"
97
+ puts "Detailed XPath Performance Comparison"
98
+ puts "=" * 80
99
+
100
+ results_table = {}
101
+
102
+ xpath_patterns.each do |pattern_name, xpath|
103
+ results_table[pattern_name] = {}
104
+
105
+ adapters.each do |adapter|
106
+ Moxml::Config.default_adapter = adapter
107
+ doc = Moxml.new.parse(sample_xml)
108
+
109
+ iterations = 0
110
+ elapsed = Benchmark.realtime do
111
+ 1000.times do
112
+ doc.xpath(xpath)
113
+ iterations += 1
114
+ end
115
+ end
116
+
117
+ ops_per_sec = iterations / elapsed
118
+ results_table[pattern_name][adapter] = ops_per_sec
119
+ rescue StandardError
120
+ results_table[pattern_name][adapter] = nil
121
+ end
122
+ end
123
+
124
+ puts "\nResults (operations per second):"
125
+ puts "-" * 80
126
+
127
+ adapters.each do |adapter|
128
+ puts "\n#{adapter.to_s.capitalize}:"
129
+ xpath_patterns.each_key do |pattern_name|
130
+ ops = results_table[pattern_name][adapter]
131
+ if ops
132
+ puts " #{pattern_name}: #{ops.round(2)} ops/sec"
133
+ else
134
+ puts " #{pattern_name}: Not supported"
135
+ end
136
+ end
137
+ end
138
+
139
+ puts "\n#{'=' * 80}"
140
+ puts "Relative Performance (fastest = 1.0x baseline):"
141
+ puts "-" * 80
142
+
143
+ relative_results = {}
144
+ xpath_patterns.each_key do |pattern_name|
145
+ puts "\n#{pattern_name}:"
146
+ valid_results = results_table[pattern_name].compact
147
+ next if valid_results.empty?
148
+
149
+ fastest = valid_results.values.max
150
+ relative_results[pattern_name] = {}
151
+ results_table[pattern_name].each do |adapter, ops|
152
+ if ops
153
+ relative = ops / fastest
154
+ relative_results[pattern_name][adapter] = relative
155
+ puts " #{adapter}: #{relative.round(3)}x " \
156
+ "(#{ops.round(2)} ops/sec)"
157
+ else
158
+ relative_results[pattern_name][adapter] = nil
159
+ puts " #{adapter}: Not supported"
160
+ end
161
+ end
162
+ end
163
+
164
+ # Save results to YAML
165
+ output_dir = File.expand_path("../../../benchmarks", __dir__)
166
+ FileUtils.mkdir_p(output_dir)
167
+ output_file = File.join(output_dir, "xpath_performance.yml")
168
+
169
+ yaml_data = {
170
+ "metadata" => {
171
+ "timestamp" => Time.now.utc.iso8601,
172
+ "ruby_version" => RUBY_VERSION,
173
+ "ruby_platform" => RUBY_PLATFORM,
174
+ },
175
+ "results" => {
176
+ "absolute" => results_table.transform_values do |pattern_results|
177
+ pattern_results.transform_values do |ops|
178
+ ops ? ops.round(2) : "not_supported"
179
+ end
180
+ end,
181
+ "relative" => relative_results.transform_values do |pattern_results|
182
+ pattern_results.transform_values do |rel|
183
+ rel ? rel.round(3) : "not_supported"
184
+ end
185
+ end,
186
+ },
187
+ }
188
+
189
+ File.write(output_file, YAML.dump(yaml_data))
190
+
191
+ puts "\n#{'=' * 80}"
192
+ puts "Results saved to: #{output_file}"
193
+ puts "=" * 80
194
+ puts "Test complete - see output above for results"
195
+ puts "=" * 80
196
+ end
197
+ end
198
+
199
+ describe "Namespace XPath performance" do
200
+ let(:namespaced_xml) do
201
+ <<~XML
202
+ <?xml version="1.0"?>
203
+ <lib:library xmlns:lib="http://example.org/library">
204
+ <lib:book id="1">
205
+ <lib:title>Book One</lib:title>
206
+ </lib:book>
207
+ <lib:book id="2">
208
+ <lib:title>Book Two</lib:title>
209
+ </lib:book>
210
+ </lib:library>
211
+ XML
212
+ end
213
+
214
+ let(:namespace_patterns) do
215
+ {
216
+ "Namespaced query (//ns:book)" =>
217
+ ["//lib:book", { "lib" => "http://example.org/library" }],
218
+ "Namespaced nested (//ns:book/ns:title)" =>
219
+ ["//lib:book/lib:title", { "lib" => "http://example.org/library" }],
220
+ }
221
+ end
222
+
223
+ it "benchmarks namespace-aware XPath" do
224
+ puts "\n#{'=' * 80}"
225
+ puts "Namespace-Aware XPath Performance"
226
+ puts "=" * 80
227
+ puts "\nNote: REXML and Ox do not support namespace-aware XPath"
228
+ puts "-" * 80
229
+
230
+ namespace_capable_adapters = %i[nokogiri libxml oga]
231
+
232
+ namespace_patterns.each do |pattern_name, (xpath, namespaces)|
233
+ puts "\nPattern: #{pattern_name}"
234
+ puts "-" * 80
235
+
236
+ Benchmark.ips do |x|
237
+ x.config(time: 5, warmup: 2)
238
+
239
+ namespace_capable_adapters.each do |adapter|
240
+ x.report(adapter.to_s) do
241
+ Moxml::Config.default_adapter = adapter
242
+ doc = Moxml.new.parse(namespaced_xml)
243
+ doc.xpath(xpath, namespaces)
244
+ rescue StandardError
245
+ nil
246
+ end
247
+ end
248
+
249
+ x.compare!
250
+ end
251
+
252
+ puts "\n"
253
+ end
254
+
255
+ puts "=" * 80
256
+ end
257
+ end
258
+ end
259
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # SimpleCov must be loaded before application code
4
+ if ENV.fetch("COVERAGE", nil) == "true"
5
+ require "simplecov"
6
+
7
+ SimpleCov.start do
8
+ add_filter "/spec/"
9
+ add_filter "/vendor/"
10
+
11
+ add_group "Core", "lib/moxml/*.rb"
12
+ add_group "Adapters", "lib/moxml/adapter"
13
+ add_group "Utilities", "lib/moxml/xml_utils"
14
+
15
+ # Adapter-specific groups
16
+ add_group "Nokogiri Adapter", "lib/moxml/adapter/nokogiri.rb"
17
+ add_group "Oga Adapter", "lib/moxml/adapter/oga.rb"
18
+ add_group "REXML Adapter", "lib/moxml/adapter/rexml.rb"
19
+ add_group "LibXML Adapter", "lib/moxml/adapter/libxml.rb"
20
+ add_group "Ox Adapter", "lib/moxml/adapter/ox.rb"
21
+
22
+ minimum_coverage 90
23
+ minimum_coverage_by_file 80
24
+ end
25
+ end
26
+
3
27
  require "moxml"
4
28
  require "nokogiri"
5
- require "byebug"
6
29
 
30
+ # Load shared examples from new locations
31
+ Dir[File.expand_path("integration/shared_examples/**/*.rb",
32
+ __dir__)].each do |f|
33
+ require f
34
+ end
35
+ Dir[File.expand_path("moxml/adapter/shared_examples/**/*.rb",
36
+ __dir__)].each do |f|
37
+ require f
38
+ end
7
39
  Dir[File.expand_path("support/**/*.rb", __dir__)].each { |f| require f }
40
+ Dir[File.expand_path("performance/*.rb", __dir__)].each { |f| require f }
41
+ Dir[File.expand_path("examples/*.rb", __dir__)].each { |f| require f }
42
+
43
+ # Clear XPath caches immediately to ensure fresh compilation
44
+ # This is critical when code changes affect compiled XPath expressions
45
+ if defined?(Moxml::XPath::Compiler::CACHE)
46
+ Moxml::XPath::Compiler::CACHE.clear
47
+ end
48
+ if defined?(Moxml::XPath::Parser::CACHE)
49
+ Moxml::XPath::Parser::CACHE.clear
50
+ end
51
+
52
+ # Clear XPath caches before each test to ensure fresh compilation
53
+ RSpec.configure do |config|
54
+ config.before do
55
+ Moxml::XPath::Compiler::CACHE.clear if defined?(Moxml::XPath::Compiler::CACHE)
56
+ Moxml::XPath::Parser::CACHE.clear if defined?(Moxml::XPath::Parser::CACHE)
57
+ end
58
+ end
8
59
 
9
60
  RSpec.configure do |config|
10
61
  config.expect_with :rspec do |expectations|
@@ -21,6 +72,12 @@ RSpec.configure do |config|
21
72
  config.disable_monkey_patching!
22
73
  config.warnings = true
23
74
 
75
+ # Configure to skip performance tests by default
76
+ config.filter_run_excluding performance: true unless ENV["RUN_PERFORMANCE"]
77
+
78
+ # Configure to skip examples unless explicitly run
79
+ config.filter_run_excluding examples: true unless ENV["RUN_EXAMPLES"]
80
+
24
81
  config.order = :random
25
82
  Kernel.srand config.seed
26
83
  end
@@ -22,6 +22,6 @@ RSpec::Matchers.define :have_xpath do |xpath, text|
22
22
  end
23
23
 
24
24
  failure_message do |_xml_node|
25
- "expected to find xpath #{xpath} #{text ? "with text '#{text}'" : ""}"
25
+ "expected to find xpath #{xpath} #{"with text '#{text}'" if text}"
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,24 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-15 00:00:00.000000000 Z
11
+ date: 2025-11-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: 'Moxml is a unified XML manipulation library that provides a common API.
14
-
15
- '
13
+ description: |
14
+ Moxml is a unified XML manipulation library that provides a common API
15
+ for XML node navigation, manipulation, building and XPath querying
16
+ interface covering multiple parser implementations including Nokogiri, Oga, REXML, Ox, LibXML.
16
17
  email:
17
18
  - open.source@ribose.com
18
19
  executables: []
19
20
  extensions: []
20
21
  extra_rdoc_files: []
21
22
  files:
23
+ - ".github/workflows/dependent-repos.json"
24
+ - ".github/workflows/dependent-tests.yml"
25
+ - ".github/workflows/docs.yml"
22
26
  - ".github/workflows/rake.yml"
23
27
  - ".github/workflows/release.yml"
24
28
  - ".gitignore"
@@ -30,17 +34,82 @@ files:
30
34
  - LICENSE.md
31
35
  - README.adoc
32
36
  - Rakefile
37
+ - benchmarks/.gitignore
38
+ - benchmarks/generate_report.rb
33
39
  - bin/console
34
40
  - bin/setup
41
+ - docs/Gemfile
42
+ - docs/_config.yml
43
+ - docs/_guides/advanced-features.adoc
44
+ - docs/_guides/development-testing.adoc
45
+ - docs/_guides/index.adoc
46
+ - docs/_guides/modifying-xml.adoc
47
+ - docs/_guides/parsing-xml.adoc
48
+ - docs/_guides/sax-parsing.adoc
49
+ - docs/_guides/working-with-documents.adoc
50
+ - docs/_pages/adapter-compatibility.adoc
51
+ - docs/_pages/adapters/headed-ox.adoc
52
+ - docs/_pages/adapters/index.adoc
53
+ - docs/_pages/adapters/libxml.adoc
54
+ - docs/_pages/adapters/nokogiri.adoc
55
+ - docs/_pages/adapters/oga.adoc
56
+ - docs/_pages/adapters/ox.adoc
57
+ - docs/_pages/adapters/rexml.adoc
58
+ - docs/_pages/best-practices.adoc
59
+ - docs/_pages/compatibility.adoc
60
+ - docs/_pages/configuration.adoc
61
+ - docs/_pages/error-handling.adoc
62
+ - docs/_pages/headed-ox-limitations.adoc
63
+ - docs/_pages/headed-ox.adoc
64
+ - docs/_pages/index.adoc
65
+ - docs/_pages/installation.adoc
66
+ - docs/_pages/node-api-reference.adoc
67
+ - docs/_pages/performance.adoc
68
+ - docs/_pages/quick-start.adoc
69
+ - docs/_pages/thread-safety.adoc
70
+ - docs/_references/document-api.adoc
71
+ - docs/_references/index.adoc
72
+ - docs/_tutorials/basic-usage.adoc
73
+ - docs/_tutorials/builder-pattern.adoc
74
+ - docs/_tutorials/index.adoc
75
+ - docs/_tutorials/namespace-handling.adoc
76
+ - docs/_tutorials/xpath-queries.adoc
77
+ - docs/index.adoc
78
+ - examples/README.md
79
+ - examples/api_client/README.md
80
+ - examples/api_client/api_client.rb
81
+ - examples/api_client/example_response.xml
82
+ - examples/headed_ox_example/README.md
83
+ - examples/headed_ox_example/headed_ox_demo.rb
84
+ - examples/rss_parser/README.md
85
+ - examples/rss_parser/example_feed.xml
86
+ - examples/rss_parser/rss_parser.rb
87
+ - examples/sax_parsing/README.md
88
+ - examples/sax_parsing/data_extractor.rb
89
+ - examples/sax_parsing/example.xml
90
+ - examples/sax_parsing/large_file.rb
91
+ - examples/sax_parsing/simple_parser.rb
92
+ - examples/web_scraper/README.md
93
+ - examples/web_scraper/example_page.html
94
+ - examples/web_scraper/web_scraper.rb
35
95
  - lib/moxml.rb
36
96
  - lib/moxml/adapter.rb
37
97
  - lib/moxml/adapter/base.rb
98
+ - lib/moxml/adapter/customized_libxml/cdata.rb
99
+ - lib/moxml/adapter/customized_libxml/comment.rb
100
+ - lib/moxml/adapter/customized_libxml/declaration.rb
101
+ - lib/moxml/adapter/customized_libxml/element.rb
102
+ - lib/moxml/adapter/customized_libxml/node.rb
103
+ - lib/moxml/adapter/customized_libxml/processing_instruction.rb
104
+ - lib/moxml/adapter/customized_libxml/text.rb
38
105
  - lib/moxml/adapter/customized_oga/xml_declaration.rb
39
106
  - lib/moxml/adapter/customized_oga/xml_generator.rb
40
107
  - lib/moxml/adapter/customized_ox/attribute.rb
41
108
  - lib/moxml/adapter/customized_ox/namespace.rb
42
109
  - lib/moxml/adapter/customized_ox/text.rb
43
110
  - lib/moxml/adapter/customized_rexml/formatter.rb
111
+ - lib/moxml/adapter/headed_ox.rb
112
+ - lib/moxml/adapter/libxml.rb
44
113
  - lib/moxml/adapter/nokogiri.rb
45
114
  - lib/moxml/adapter/oga.rb
46
115
  - lib/moxml/adapter/ox.rb
@@ -61,49 +130,122 @@ files:
61
130
  - lib/moxml/node.rb
62
131
  - lib/moxml/node_set.rb
63
132
  - lib/moxml/processing_instruction.rb
133
+ - lib/moxml/sax.rb
134
+ - lib/moxml/sax/block_handler.rb
135
+ - lib/moxml/sax/element_handler.rb
136
+ - lib/moxml/sax/handler.rb
64
137
  - lib/moxml/text.rb
65
138
  - lib/moxml/version.rb
66
139
  - lib/moxml/xml_utils.rb
67
140
  - lib/moxml/xml_utils/encoder.rb
141
+ - lib/moxml/xpath.rb
142
+ - lib/moxml/xpath/ast/node.rb
143
+ - lib/moxml/xpath/cache.rb
144
+ - lib/moxml/xpath/compiler.rb
145
+ - lib/moxml/xpath/context.rb
146
+ - lib/moxml/xpath/conversion.rb
147
+ - lib/moxml/xpath/engine.rb
148
+ - lib/moxml/xpath/errors.rb
149
+ - lib/moxml/xpath/lexer.rb
150
+ - lib/moxml/xpath/parser.rb
151
+ - lib/moxml/xpath/ruby/generator.rb
152
+ - lib/moxml/xpath/ruby/node.rb
68
153
  - moxml.gemspec
154
+ - old-specs/moxml/adapter/customized_libxml/.gitkeep
69
155
  - sig/moxml.rbs
156
+ - spec/consistency/README.md
157
+ - spec/consistency/adapter_parity_spec.rb
158
+ - spec/examples/README.md
159
+ - spec/examples/attribute_examples_spec.rb
160
+ - spec/examples/basic_usage_spec.rb
161
+ - spec/examples/namespace_examples_spec.rb
162
+ - spec/examples/readme_examples_spec.rb
163
+ - spec/examples/xpath_examples_spec.rb
70
164
  - spec/fixtures/small.xml
165
+ - spec/integration/README.md
166
+ - spec/integration/all_adapters_spec.rb
167
+ - spec/integration/headed_ox_integration_spec.rb
168
+ - spec/integration/shared_examples/edge_cases.rb
169
+ - spec/integration/shared_examples/high_level/.gitkeep
170
+ - spec/integration/shared_examples/high_level/builder_behavior.rb
171
+ - spec/integration/shared_examples/high_level/context_behavior.rb
172
+ - spec/integration/shared_examples/high_level/document_builder_behavior.rb
173
+ - spec/integration/shared_examples/integration_workflows.rb
174
+ - spec/integration/shared_examples/node_wrappers/.gitkeep
175
+ - spec/integration/shared_examples/node_wrappers/attribute_behavior.rb
176
+ - spec/integration/shared_examples/node_wrappers/cdata_behavior.rb
177
+ - spec/integration/shared_examples/node_wrappers/comment_behavior.rb
178
+ - spec/integration/shared_examples/node_wrappers/declaration_behavior.rb
179
+ - spec/integration/shared_examples/node_wrappers/doctype_behavior.rb
180
+ - spec/integration/shared_examples/node_wrappers/document_behavior.rb
181
+ - spec/integration/shared_examples/node_wrappers/element_behavior.rb
182
+ - spec/integration/shared_examples/node_wrappers/namespace_behavior.rb
183
+ - spec/integration/shared_examples/node_wrappers/node_behavior.rb
184
+ - spec/integration/shared_examples/node_wrappers/node_set_behavior.rb
185
+ - spec/integration/shared_examples/node_wrappers/processing_instruction_behavior.rb
186
+ - spec/integration/shared_examples/node_wrappers/text_behavior.rb
187
+ - spec/moxml/README.md
188
+ - spec/moxml/adapter/.gitkeep
189
+ - spec/moxml/adapter/README.md
190
+ - spec/moxml/adapter/base_spec.rb
191
+ - spec/moxml/adapter/headed_ox_spec.rb
192
+ - spec/moxml/adapter/libxml_spec.rb
71
193
  - spec/moxml/adapter/nokogiri_spec.rb
72
194
  - spec/moxml/adapter/oga_spec.rb
73
195
  - spec/moxml/adapter/ox_spec.rb
74
196
  - spec/moxml/adapter/rexml_spec.rb
75
- - spec/moxml/all_with_adapters_spec.rb
197
+ - spec/moxml/adapter/shared_examples/.gitkeep
198
+ - spec/moxml/adapter/shared_examples/adapter_contract.rb
199
+ - spec/moxml/adapter_spec.rb
200
+ - spec/moxml/attribute_spec.rb
201
+ - spec/moxml/builder_spec.rb
202
+ - spec/moxml/cdata_spec.rb
203
+ - spec/moxml/comment_spec.rb
76
204
  - spec/moxml/config_spec.rb
205
+ - spec/moxml/context_spec.rb
206
+ - spec/moxml/declaration_spec.rb
207
+ - spec/moxml/doctype_spec.rb
208
+ - spec/moxml/document_builder_spec.rb
209
+ - spec/moxml/document_spec.rb
210
+ - spec/moxml/element_spec.rb
77
211
  - spec/moxml/error_spec.rb
78
- - spec/moxml/examples/adapter_spec.rb
79
- - spec/moxml_spec.rb
212
+ - spec/moxml/moxml_spec.rb
213
+ - spec/moxml/namespace_spec.rb
214
+ - spec/moxml/node_set_spec.rb
215
+ - spec/moxml/node_spec.rb
216
+ - spec/moxml/processing_instruction_spec.rb
217
+ - spec/moxml/sax_spec.rb
218
+ - spec/moxml/text_spec.rb
219
+ - spec/moxml/version_spec.rb
220
+ - spec/moxml/xml_utils/.gitkeep
221
+ - spec/moxml/xml_utils/encoder_spec.rb
222
+ - spec/moxml/xml_utils_spec.rb
223
+ - spec/moxml/xpath/ast/node_spec.rb
224
+ - spec/moxml/xpath/axes_spec.rb
225
+ - spec/moxml/xpath/cache_spec.rb
226
+ - spec/moxml/xpath/compiler_spec.rb
227
+ - spec/moxml/xpath/context_spec.rb
228
+ - spec/moxml/xpath/conversion_spec.rb
229
+ - spec/moxml/xpath/fixtures/sample.xml
230
+ - spec/moxml/xpath/functions/boolean_functions_spec.rb
231
+ - spec/moxml/xpath/functions/node_functions_spec.rb
232
+ - spec/moxml/xpath/functions/numeric_functions_spec.rb
233
+ - spec/moxml/xpath/functions/position_functions_spec.rb
234
+ - spec/moxml/xpath/functions/special_functions_spec.rb
235
+ - spec/moxml/xpath/functions/string_functions_spec.rb
236
+ - spec/moxml/xpath/lexer_spec.rb
237
+ - spec/moxml/xpath/parser_integration_spec.rb
238
+ - spec/moxml/xpath/parser_spec.rb
239
+ - spec/moxml/xpath/ruby/generator_spec.rb
240
+ - spec/moxml/xpath/ruby/node_spec.rb
241
+ - spec/moxml/xpath_capabilities_spec.rb
242
+ - spec/moxml/xpath_spec.rb
243
+ - spec/performance/README.md
244
+ - spec/performance/benchmark_spec.rb
245
+ - spec/performance/memory_usage_spec.rb
246
+ - spec/performance/thread_safety_spec.rb
247
+ - spec/performance/xpath_benchmark_spec.rb
80
248
  - spec/spec_helper.rb
81
- - spec/support/shared_examples/attribute.rb
82
- - spec/support/shared_examples/builder.rb
83
- - spec/support/shared_examples/cdata.rb
84
- - spec/support/shared_examples/comment.rb
85
- - spec/support/shared_examples/context.rb
86
- - spec/support/shared_examples/declaration.rb
87
- - spec/support/shared_examples/doctype.rb
88
- - spec/support/shared_examples/document.rb
89
- - spec/support/shared_examples/document_builder.rb
90
- - spec/support/shared_examples/edge_cases.rb
91
- - spec/support/shared_examples/element.rb
92
- - spec/support/shared_examples/examples/attribute.rb
93
- - spec/support/shared_examples/examples/basic_usage.rb
94
- - spec/support/shared_examples/examples/benchmark_spec.rb
95
- - spec/support/shared_examples/examples/memory.rb
96
- - spec/support/shared_examples/examples/namespace.rb
97
- - spec/support/shared_examples/examples/readme_examples.rb
98
- - spec/support/shared_examples/examples/thread_safety.rb
99
- - spec/support/shared_examples/examples/xpath.rb
100
- - spec/support/shared_examples/integration.rb
101
- - spec/support/shared_examples/namespace.rb
102
- - spec/support/shared_examples/node.rb
103
- - spec/support/shared_examples/node_set.rb
104
- - spec/support/shared_examples/processing_instruction.rb
105
- - spec/support/shared_examples/text.rb
106
- - spec/support/shared_examples/xml_adapter.rb
107
249
  - spec/support/xml_matchers.rb
108
250
  homepage: https://github.com/lutaml/moxml
109
251
  licenses:
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "benchmark"
4
- require "benchmark/ips"
5
-
6
- RSpec.shared_examples "Performance Examples" do
7
- let(:context) { Moxml.new }
8
-
9
- let(:large_xml) do
10
- xml = "<root>\n"
11
- 1000.times do |i|
12
- xml += "<item id='#{i}'><name>Test #{i}</name><value>#{i}</value></item>\n"
13
- end
14
- xml += "</root>"
15
- xml
16
- end
17
-
18
- it "measures parsing performance" do
19
- doc = nil
20
-
21
- report = Benchmark.ips do |x|
22
- x.config(time: 5, warmup: 2)
23
-
24
- x.report("Parser") do
25
- doc = context.parse(large_xml)
26
- end
27
-
28
- x.report("Serializer") do
29
- _ = doc.to_xml
30
- end
31
-
32
- x.compare!
33
- end
34
-
35
- # first - parser, second - serializer
36
- thresholds = {
37
- nokogiri: [18, 1200],
38
- oga: [12, 110],
39
- rexml: [0, 60],
40
- ox: [2, 2000]
41
- }
42
-
43
- report.entries.each_with_index do |entry, index|
44
- puts "#{entry.label} performance: #{entry.ips.round(2)} ips"
45
- threshold = thresholds[context.config.adapter_name][index]
46
- message = "#{entry.label} performance below threshold: " \
47
- "got #{entry.ips.round(2)} ips, expected >= #{threshold} ips"
48
- expect(entry.ips).to be >= threshold, message
49
- end
50
- end
51
- end