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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-repos.json +5 -0
- data/.github/workflows/dependent-tests.yml +20 -0
- data/.github/workflows/docs.yml +59 -0
- data/.github/workflows/rake.yml +10 -10
- data/.github/workflows/release.yml +5 -3
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -7
- data/.rubocop_todo.yml +238 -40
- data/Gemfile +14 -9
- data/LICENSE.md +6 -2
- data/README.adoc +535 -373
- data/Rakefile +53 -0
- data/benchmarks/.gitignore +6 -0
- data/benchmarks/generate_report.rb +550 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +138 -0
- data/docs/_guides/advanced-features.adoc +87 -0
- data/docs/_guides/development-testing.adoc +165 -0
- data/docs/_guides/index.adoc +45 -0
- data/docs/_guides/modifying-xml.adoc +293 -0
- data/docs/_guides/parsing-xml.adoc +231 -0
- data/docs/_guides/sax-parsing.adoc +603 -0
- data/docs/_guides/working-with-documents.adoc +118 -0
- data/docs/_pages/adapter-compatibility.adoc +369 -0
- data/docs/_pages/adapters/headed-ox.adoc +237 -0
- data/docs/_pages/adapters/index.adoc +98 -0
- data/docs/_pages/adapters/libxml.adoc +286 -0
- data/docs/_pages/adapters/nokogiri.adoc +252 -0
- data/docs/_pages/adapters/oga.adoc +292 -0
- data/docs/_pages/adapters/ox.adoc +55 -0
- data/docs/_pages/adapters/rexml.adoc +293 -0
- data/docs/_pages/best-practices.adoc +430 -0
- data/docs/_pages/compatibility.adoc +468 -0
- data/docs/_pages/configuration.adoc +251 -0
- data/docs/_pages/error-handling.adoc +350 -0
- data/docs/_pages/headed-ox-limitations.adoc +558 -0
- data/docs/_pages/headed-ox.adoc +1025 -0
- data/docs/_pages/index.adoc +35 -0
- data/docs/_pages/installation.adoc +141 -0
- data/docs/_pages/node-api-reference.adoc +50 -0
- data/docs/_pages/performance.adoc +36 -0
- data/docs/_pages/quick-start.adoc +244 -0
- data/docs/_pages/thread-safety.adoc +29 -0
- data/docs/_references/document-api.adoc +408 -0
- data/docs/_references/index.adoc +48 -0
- data/docs/_tutorials/basic-usage.adoc +268 -0
- data/docs/_tutorials/builder-pattern.adoc +343 -0
- data/docs/_tutorials/index.adoc +33 -0
- data/docs/_tutorials/namespace-handling.adoc +325 -0
- data/docs/_tutorials/xpath-queries.adoc +359 -0
- data/docs/index.adoc +122 -0
- data/examples/README.md +124 -0
- data/examples/api_client/README.md +424 -0
- data/examples/api_client/api_client.rb +394 -0
- data/examples/api_client/example_response.xml +48 -0
- data/examples/headed_ox_example/README.md +90 -0
- data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
- data/examples/rss_parser/README.md +194 -0
- data/examples/rss_parser/example_feed.xml +93 -0
- data/examples/rss_parser/rss_parser.rb +189 -0
- data/examples/sax_parsing/README.md +50 -0
- data/examples/sax_parsing/data_extractor.rb +75 -0
- data/examples/sax_parsing/example.xml +21 -0
- data/examples/sax_parsing/large_file.rb +78 -0
- data/examples/sax_parsing/simple_parser.rb +55 -0
- data/examples/web_scraper/README.md +352 -0
- data/examples/web_scraper/example_page.html +201 -0
- data/examples/web_scraper/web_scraper.rb +312 -0
- data/lib/moxml/adapter/base.rb +107 -28
- data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
- data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
- data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
- data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
- data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
- data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
- data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
- data/lib/moxml/adapter/headed_ox.rb +161 -0
- data/lib/moxml/adapter/libxml.rb +1548 -0
- data/lib/moxml/adapter/nokogiri.rb +121 -9
- data/lib/moxml/adapter/oga.rb +123 -12
- data/lib/moxml/adapter/ox.rb +282 -26
- data/lib/moxml/adapter/rexml.rb +127 -20
- data/lib/moxml/adapter.rb +21 -4
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/builder.rb +40 -4
- data/lib/moxml/config.rb +8 -3
- data/lib/moxml/context.rb +39 -1
- data/lib/moxml/doctype.rb +13 -1
- data/lib/moxml/document.rb +39 -6
- data/lib/moxml/document_builder.rb +27 -5
- data/lib/moxml/element.rb +71 -2
- data/lib/moxml/error.rb +175 -6
- data/lib/moxml/node.rb +94 -3
- data/lib/moxml/node_set.rb +34 -0
- data/lib/moxml/sax/block_handler.rb +194 -0
- data/lib/moxml/sax/element_handler.rb +124 -0
- data/lib/moxml/sax/handler.rb +113 -0
- data/lib/moxml/sax.rb +31 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +4 -4
- data/lib/moxml/xml_utils.rb +7 -4
- data/lib/moxml/xpath/ast/node.rb +159 -0
- data/lib/moxml/xpath/cache.rb +91 -0
- data/lib/moxml/xpath/compiler.rb +1768 -0
- data/lib/moxml/xpath/context.rb +26 -0
- data/lib/moxml/xpath/conversion.rb +124 -0
- data/lib/moxml/xpath/engine.rb +52 -0
- data/lib/moxml/xpath/errors.rb +101 -0
- data/lib/moxml/xpath/lexer.rb +304 -0
- data/lib/moxml/xpath/parser.rb +485 -0
- data/lib/moxml/xpath/ruby/generator.rb +269 -0
- data/lib/moxml/xpath/ruby/node.rb +193 -0
- data/lib/moxml/xpath.rb +37 -0
- data/lib/moxml.rb +5 -2
- data/moxml.gemspec +3 -1
- data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
- data/spec/consistency/README.md +77 -0
- data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
- data/spec/examples/README.md +75 -0
- data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
- data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
- data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
- data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
- data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
- data/spec/integration/README.md +71 -0
- data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
- data/spec/integration/headed_ox_integration_spec.rb +326 -0
- data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
- data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
- data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
- data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
- data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
- data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
- data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
- data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -2
- data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
- data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
- data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
- data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
- data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
- data/spec/moxml/README.md +41 -0
- data/spec/moxml/adapter/.gitkeep +0 -0
- data/spec/moxml/adapter/README.md +61 -0
- data/spec/moxml/adapter/base_spec.rb +27 -0
- data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
- data/spec/moxml/adapter/libxml_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +9 -8
- data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
- data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
- data/spec/moxml/adapter_spec.rb +16 -0
- data/spec/moxml/attribute_spec.rb +30 -0
- data/spec/moxml/builder_spec.rb +33 -0
- data/spec/moxml/cdata_spec.rb +31 -0
- data/spec/moxml/comment_spec.rb +31 -0
- data/spec/moxml/config_spec.rb +3 -3
- data/spec/moxml/context_spec.rb +28 -0
- data/spec/moxml/declaration_spec.rb +36 -0
- data/spec/moxml/doctype_spec.rb +33 -0
- data/spec/moxml/document_builder_spec.rb +30 -0
- data/spec/moxml/document_spec.rb +105 -0
- data/spec/moxml/element_spec.rb +143 -0
- data/spec/moxml/error_spec.rb +266 -22
- data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
- data/spec/moxml/namespace_spec.rb +32 -0
- data/spec/moxml/node_set_spec.rb +39 -0
- data/spec/moxml/node_spec.rb +37 -0
- data/spec/moxml/processing_instruction_spec.rb +34 -0
- data/spec/moxml/sax_spec.rb +1067 -0
- data/spec/moxml/text_spec.rb +31 -0
- data/spec/moxml/version_spec.rb +14 -0
- data/spec/moxml/xml_utils/.gitkeep +0 -0
- data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
- data/spec/moxml/xml_utils_spec.rb +49 -0
- data/spec/moxml/xpath/ast/node_spec.rb +83 -0
- data/spec/moxml/xpath/axes_spec.rb +296 -0
- data/spec/moxml/xpath/cache_spec.rb +358 -0
- data/spec/moxml/xpath/compiler_spec.rb +406 -0
- data/spec/moxml/xpath/context_spec.rb +210 -0
- data/spec/moxml/xpath/conversion_spec.rb +365 -0
- data/spec/moxml/xpath/fixtures/sample.xml +25 -0
- data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
- data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
- data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
- data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
- data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
- data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
- data/spec/moxml/xpath/lexer_spec.rb +488 -0
- data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
- data/spec/moxml/xpath/parser_spec.rb +364 -0
- data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
- data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
- data/spec/moxml/xpath_capabilities_spec.rb +199 -0
- data/spec/moxml/xpath_spec.rb +77 -0
- data/spec/performance/README.md +83 -0
- data/spec/performance/benchmark_spec.rb +64 -0
- data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +3 -1
- data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
- data/spec/performance/xpath_benchmark_spec.rb +259 -0
- data/spec/spec_helper.rb +58 -1
- data/spec/support/xml_matchers.rb +1 -1
- metadata +176 -34
- data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
- /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module XPath
|
|
5
|
+
module Ruby
|
|
6
|
+
# Class for converting a Ruby AST to a String.
|
|
7
|
+
#
|
|
8
|
+
# This class takes a {Moxml::XPath::Ruby::Node} instance and converts it
|
|
9
|
+
# (and its child nodes) to a String that can be passed to `eval`.
|
|
10
|
+
#
|
|
11
|
+
# @private
|
|
12
|
+
class Generator
|
|
13
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
14
|
+
# @return [String]
|
|
15
|
+
def process(ast)
|
|
16
|
+
handler = :"on_#{ast.type}"
|
|
17
|
+
unless respond_to?(handler, true)
|
|
18
|
+
raise NotImplementedError,
|
|
19
|
+
"Generator missing handler for node type :#{ast.type}. Node: #{ast.inspect}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
send(handler, ast)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
26
|
+
# @return [String]
|
|
27
|
+
def on_followed_by(ast)
|
|
28
|
+
ast.to_a.map { |child| process(child) }.join("\n\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Processes an assignment node.
|
|
32
|
+
#
|
|
33
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
34
|
+
# @return [String]
|
|
35
|
+
def on_assign(ast)
|
|
36
|
+
var, val = *ast
|
|
37
|
+
|
|
38
|
+
var_str = process(var)
|
|
39
|
+
val_str = process(val)
|
|
40
|
+
|
|
41
|
+
"#{var_str} = #{val_str}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Processes a mass assignment node.
|
|
45
|
+
#
|
|
46
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
47
|
+
# @return [String]
|
|
48
|
+
def on_massign(ast)
|
|
49
|
+
vars, val = *ast
|
|
50
|
+
|
|
51
|
+
var_names = vars.map { |var| process(var) }
|
|
52
|
+
val_str = process(val)
|
|
53
|
+
|
|
54
|
+
"#{var_names.join(', ')} = #{val_str}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Processes a `begin` node.
|
|
58
|
+
#
|
|
59
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
60
|
+
# @return [String]
|
|
61
|
+
def on_begin(ast)
|
|
62
|
+
body = process(ast.to_a[0])
|
|
63
|
+
|
|
64
|
+
<<~RUBY
|
|
65
|
+
begin
|
|
66
|
+
#{body}
|
|
67
|
+
end
|
|
68
|
+
RUBY
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Processes an equality node.
|
|
72
|
+
#
|
|
73
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
74
|
+
# @return [String]
|
|
75
|
+
def on_eq(ast)
|
|
76
|
+
left, right = *ast
|
|
77
|
+
|
|
78
|
+
left_str = process(left)
|
|
79
|
+
right_str = process(right)
|
|
80
|
+
|
|
81
|
+
"#{left_str} == #{right_str}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Processes a boolean "and" node.
|
|
85
|
+
#
|
|
86
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
87
|
+
# @return [String]
|
|
88
|
+
def on_and(ast)
|
|
89
|
+
left, right = *ast
|
|
90
|
+
|
|
91
|
+
left_str = process(left)
|
|
92
|
+
right_str = process(right)
|
|
93
|
+
|
|
94
|
+
"#{left_str} && #{right_str}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Processes a boolean "or" node.
|
|
98
|
+
#
|
|
99
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
100
|
+
# @return [String]
|
|
101
|
+
def on_or(ast)
|
|
102
|
+
left, right = *ast
|
|
103
|
+
|
|
104
|
+
left_str = process(left)
|
|
105
|
+
right_str = process(right)
|
|
106
|
+
|
|
107
|
+
"(#{left_str} || #{right_str})"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Processes an if statement node.
|
|
111
|
+
#
|
|
112
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
113
|
+
# @return [String]
|
|
114
|
+
def on_if(ast)
|
|
115
|
+
cond, body, else_body = *ast
|
|
116
|
+
|
|
117
|
+
cond_str = process(cond)
|
|
118
|
+
body_str = process(body)
|
|
119
|
+
|
|
120
|
+
if else_body
|
|
121
|
+
else_str = process(else_body)
|
|
122
|
+
|
|
123
|
+
<<~RUBY
|
|
124
|
+
if #{cond_str}
|
|
125
|
+
#{body_str}
|
|
126
|
+
else
|
|
127
|
+
#{else_str}
|
|
128
|
+
end
|
|
129
|
+
RUBY
|
|
130
|
+
else
|
|
131
|
+
<<~RUBY
|
|
132
|
+
if #{cond_str}
|
|
133
|
+
#{body_str}
|
|
134
|
+
end
|
|
135
|
+
RUBY
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Processes a while statement node.
|
|
140
|
+
#
|
|
141
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
142
|
+
# @return [String]
|
|
143
|
+
def on_while(ast)
|
|
144
|
+
cond, body = *ast
|
|
145
|
+
|
|
146
|
+
cond_str = process(cond)
|
|
147
|
+
body_str = process(body)
|
|
148
|
+
|
|
149
|
+
<<~RUBY
|
|
150
|
+
while #{cond_str}
|
|
151
|
+
#{body_str}
|
|
152
|
+
end
|
|
153
|
+
RUBY
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Processes a method call node.
|
|
157
|
+
#
|
|
158
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
159
|
+
# @return [String]
|
|
160
|
+
def on_send(ast)
|
|
161
|
+
children = ast.to_a
|
|
162
|
+
receiver = children[0]
|
|
163
|
+
name = children[1]
|
|
164
|
+
args = children[2..] || []
|
|
165
|
+
|
|
166
|
+
call = name
|
|
167
|
+
brackets = name == "[]"
|
|
168
|
+
|
|
169
|
+
unless args.empty?
|
|
170
|
+
arg_strs = []
|
|
171
|
+
args.each do |arg|
|
|
172
|
+
result = process(arg)
|
|
173
|
+
# Keep processing if we got a Node back (happens with nested send nodes)
|
|
174
|
+
while result.respond_to?(:type)
|
|
175
|
+
result = process(result)
|
|
176
|
+
end
|
|
177
|
+
arg_strs << result
|
|
178
|
+
end
|
|
179
|
+
arg_str = arg_strs.join(", ")
|
|
180
|
+
call = brackets ? "[#{arg_str}]" : "#{call}(#{arg_str})"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if receiver
|
|
184
|
+
rec_str = process(receiver)
|
|
185
|
+
# Keep processing if we got a Node back
|
|
186
|
+
while rec_str.respond_to?(:type)
|
|
187
|
+
rec_str = process(rec_str)
|
|
188
|
+
end
|
|
189
|
+
call = brackets ? "#{rec_str}#{call}" : "#{rec_str}.#{call}"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
call
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Processes a block node.
|
|
196
|
+
#
|
|
197
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
198
|
+
# @return [String]
|
|
199
|
+
def on_block(ast)
|
|
200
|
+
receiver, args, body = *ast
|
|
201
|
+
|
|
202
|
+
receiver_str = process(receiver)
|
|
203
|
+
body_str = body ? process(body) : nil
|
|
204
|
+
arg_strs = args.map { |arg| process(arg) }
|
|
205
|
+
|
|
206
|
+
<<~RUBY
|
|
207
|
+
#{receiver_str} do |#{arg_strs.join(', ')}|
|
|
208
|
+
#{body_str}
|
|
209
|
+
end
|
|
210
|
+
RUBY
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Processes a Range node.
|
|
214
|
+
#
|
|
215
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
216
|
+
# @return [String]
|
|
217
|
+
def on_range(ast)
|
|
218
|
+
start, stop = *ast
|
|
219
|
+
|
|
220
|
+
start_str = process(start)
|
|
221
|
+
stop_str = process(stop)
|
|
222
|
+
|
|
223
|
+
"(#{start_str}..#{stop_str})"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Processes a string node.
|
|
227
|
+
#
|
|
228
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
229
|
+
# @return [String]
|
|
230
|
+
def on_string(ast)
|
|
231
|
+
ast.to_a[0].inspect
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Processes a Symbol node.
|
|
235
|
+
#
|
|
236
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
237
|
+
# @return [String]
|
|
238
|
+
def on_symbol(ast)
|
|
239
|
+
ast.to_a[0].to_sym.inspect
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Processes a literal node.
|
|
243
|
+
#
|
|
244
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
245
|
+
# @return [String]
|
|
246
|
+
def on_lit(ast)
|
|
247
|
+
ast.to_a[0]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Processes a constant reference node (e.g., Moxml::Document).
|
|
251
|
+
#
|
|
252
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
253
|
+
# @return [String]
|
|
254
|
+
def on_const(ast)
|
|
255
|
+
ast.to_a.join("::")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Processes an array literal node.
|
|
259
|
+
#
|
|
260
|
+
# @param [Moxml::XPath::Ruby::Node] ast
|
|
261
|
+
# @return [String]
|
|
262
|
+
def on_array(ast)
|
|
263
|
+
elements = ast.to_a.map { |elem| process(elem) }
|
|
264
|
+
"[#{elements.join(', ')}]"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module XPath
|
|
5
|
+
module Ruby
|
|
6
|
+
# Class representing a single node in a Ruby AST.
|
|
7
|
+
#
|
|
8
|
+
# This class provides a fluent DSL for building Ruby code dynamically.
|
|
9
|
+
# It's modeled after the "ast" gem but simplified to avoid method conflicts.
|
|
10
|
+
#
|
|
11
|
+
# @example Building an if statement
|
|
12
|
+
# number1 = Node.new(:lit, ['10'])
|
|
13
|
+
# number2 = Node.new(:lit, ['20'])
|
|
14
|
+
#
|
|
15
|
+
# (number2 > number1).if_true do
|
|
16
|
+
# Node.new(:lit, ['30'])
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @private
|
|
20
|
+
class Node < BasicObject
|
|
21
|
+
undef_method :!, :!=
|
|
22
|
+
|
|
23
|
+
# @return [Symbol]
|
|
24
|
+
attr_reader :type
|
|
25
|
+
|
|
26
|
+
# @param [Symbol] type The type of AST node
|
|
27
|
+
# @param [Array] children Child nodes or values
|
|
28
|
+
def initialize(type, children = [])
|
|
29
|
+
@type = type.to_sym
|
|
30
|
+
@children = children
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Array]
|
|
34
|
+
def to_a
|
|
35
|
+
@children
|
|
36
|
+
end
|
|
37
|
+
alias to_ary to_a
|
|
38
|
+
|
|
39
|
+
# Returns a "to_a" call node.
|
|
40
|
+
#
|
|
41
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
42
|
+
def to_array
|
|
43
|
+
Node.new(:send, [self, :to_a])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns an assignment node.
|
|
47
|
+
#
|
|
48
|
+
# Wraps assigned values in a begin/end block to ensure that
|
|
49
|
+
# multiple lines of code result in the proper value being assigned.
|
|
50
|
+
#
|
|
51
|
+
# @param [Moxml::XPath::Ruby::Node] other
|
|
52
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
53
|
+
def assign(other)
|
|
54
|
+
other = other.wrap if other.type == :followed_by
|
|
55
|
+
|
|
56
|
+
Node.new(:assign, [self, other])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns an equality expression node.
|
|
60
|
+
#
|
|
61
|
+
# @param [Moxml::XPath::Ruby::Node] other
|
|
62
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
63
|
+
def eq(other)
|
|
64
|
+
Node.new(:eq, [self, other])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns a boolean "and" node.
|
|
68
|
+
#
|
|
69
|
+
# @param [Moxml::XPath::Ruby::Node] other
|
|
70
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
71
|
+
def and(other)
|
|
72
|
+
Node.new(:and, [self, other])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns a boolean "or" node.
|
|
76
|
+
#
|
|
77
|
+
# @param [Moxml::XPath::Ruby::Node] other
|
|
78
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
79
|
+
def or(other)
|
|
80
|
+
Node.new(:or, [self, other])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a node that evaluates to its inverse.
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# foo.not # => !foo
|
|
87
|
+
#
|
|
88
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
89
|
+
def not
|
|
90
|
+
!self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns a node for Ruby's "is_a?" method.
|
|
94
|
+
#
|
|
95
|
+
# @param [Class] klass
|
|
96
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
97
|
+
def is_a?(klass)
|
|
98
|
+
# If klass is already a Node (e.g., a const node), use it directly
|
|
99
|
+
# Otherwise wrap it in a lit node
|
|
100
|
+
klass_node = if klass.respond_to?(:type)
|
|
101
|
+
klass
|
|
102
|
+
else
|
|
103
|
+
Node.new(:lit, [klass.to_s])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Node.new(:send, [self, "is_a?", klass_node])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Wraps the current node in a block.
|
|
110
|
+
#
|
|
111
|
+
# @param [Array] args Arguments (as Node instances) to pass to the block
|
|
112
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
113
|
+
def add_block(*args)
|
|
114
|
+
Node.new(:block, [self, args, yield])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Wraps the current node in a `begin` node.
|
|
118
|
+
#
|
|
119
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
120
|
+
def wrap
|
|
121
|
+
Node.new(:begin, [self])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Wraps the current node in an if statement node.
|
|
125
|
+
#
|
|
126
|
+
# The body of this statement is set to the return value of the supplied
|
|
127
|
+
# block.
|
|
128
|
+
#
|
|
129
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
130
|
+
def if_true
|
|
131
|
+
Node.new(:if, [self, yield])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Wraps the current node in an `if !...` statement.
|
|
135
|
+
#
|
|
136
|
+
# @see [#if_true]
|
|
137
|
+
def if_false(&block)
|
|
138
|
+
self.not.if_true(&block)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Wraps the current node in a `while` statement.
|
|
142
|
+
#
|
|
143
|
+
# The body of this statement is set to the return value of the supplied
|
|
144
|
+
# block.
|
|
145
|
+
#
|
|
146
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
147
|
+
def while_true
|
|
148
|
+
Node.new(:while, [self, yield])
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Adds an "else" statement to the current node.
|
|
152
|
+
#
|
|
153
|
+
# This method assumes it's being called only on "if" nodes.
|
|
154
|
+
#
|
|
155
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
156
|
+
def else
|
|
157
|
+
Node.new(:if, @children + [yield])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Chains two nodes together.
|
|
161
|
+
#
|
|
162
|
+
# @param [Moxml::XPath::Ruby::Node] other
|
|
163
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
164
|
+
def followed_by(other = nil)
|
|
165
|
+
other = yield if ::Kernel.block_given?
|
|
166
|
+
|
|
167
|
+
Node.new(:followed_by, [self, other])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns a node for a method call.
|
|
171
|
+
#
|
|
172
|
+
# @param [Symbol] name The name of the method to call
|
|
173
|
+
# @param [Array] args Any arguments (as Node instances) to pass
|
|
174
|
+
# @return [Moxml::XPath::Ruby::Node]
|
|
175
|
+
def method_missing(name, *args)
|
|
176
|
+
Node.new(:send, [self, name.to_s, *args])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Prevent implicit string conversion - Nodes must be explicitly processed
|
|
180
|
+
def to_str
|
|
181
|
+
::Kernel.raise ::TypeError, "Cannot implicitly
|
|
182
|
+
|
|
183
|
+
convert #{self.class} to String. Use Generator#process instead."
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @return [String]
|
|
187
|
+
def inspect
|
|
188
|
+
"(#{type} #{@children.map(&:inspect).join(' ')})"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
data/lib/moxml/xpath.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
# XPath 1.0 implementation for Moxml
|
|
5
|
+
#
|
|
6
|
+
# This module provides a complete XPath 1.0 engine for querying XML
|
|
7
|
+
# documents, particularly for the Ox adapter which has limited native
|
|
8
|
+
# XPath support.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# engine = Moxml::XPath::Engine.new(document)
|
|
12
|
+
# results = engine.evaluate("//book[@id='123']")
|
|
13
|
+
#
|
|
14
|
+
module XPath
|
|
15
|
+
autoload :Engine, "moxml/xpath/engine"
|
|
16
|
+
autoload :Context, "moxml/xpath/context"
|
|
17
|
+
autoload :Conversion, "moxml/xpath/conversion"
|
|
18
|
+
autoload :Cache, "moxml/xpath/cache"
|
|
19
|
+
autoload :Lexer, "moxml/xpath/lexer"
|
|
20
|
+
autoload :Parser, "moxml/xpath/parser"
|
|
21
|
+
autoload :Compiler, "moxml/xpath/compiler"
|
|
22
|
+
|
|
23
|
+
# Require errors directly so classes are immediately available
|
|
24
|
+
require_relative "xpath/errors"
|
|
25
|
+
|
|
26
|
+
# AST nodes for expression representation
|
|
27
|
+
module AST
|
|
28
|
+
autoload :Node, "moxml/xpath/ast/node"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Ruby AST generation for compiling XPath
|
|
32
|
+
module Ruby
|
|
33
|
+
autoload :Node, "moxml/xpath/ruby/node"
|
|
34
|
+
autoload :Generator, "moxml/xpath/ruby/generator"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/moxml.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Moxml
|
|
|
4
4
|
class << self
|
|
5
5
|
def new(adapter = nil, &block)
|
|
6
6
|
context = Context.new(adapter)
|
|
7
|
-
context.config.instance_eval(&block) if
|
|
7
|
+
context.config.instance_eval(&block) if block
|
|
8
8
|
context
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -12,7 +12,8 @@ module Moxml
|
|
|
12
12
|
yield Config.default if block_given?
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def with_config(adapter_name = nil, strict_parsing = nil,
|
|
15
|
+
def with_config(adapter_name = nil, strict_parsing = nil,
|
|
16
|
+
default_encoding = nil)
|
|
16
17
|
original_config = Config.default.dup
|
|
17
18
|
|
|
18
19
|
configure do |config|
|
|
@@ -42,3 +43,5 @@ require_relative "moxml/builder"
|
|
|
42
43
|
require_relative "moxml/config"
|
|
43
44
|
require_relative "moxml/context"
|
|
44
45
|
require_relative "moxml/adapter"
|
|
46
|
+
require_relative "moxml/xpath"
|
|
47
|
+
require_relative "moxml/sax"
|
data/moxml.gemspec
CHANGED
|
@@ -10,7 +10,9 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
|
|
11
11
|
spec.summary = "Unified XML manipulation library"
|
|
12
12
|
spec.description = <<~DESCRIPTION
|
|
13
|
-
Moxml is a unified XML manipulation library that provides a common API
|
|
13
|
+
Moxml is a unified XML manipulation library that provides a common API
|
|
14
|
+
for XML node navigation, manipulation, building and XPath querying
|
|
15
|
+
interface covering multiple parser implementations including Nokogiri, Oga, REXML, Ox, LibXML.
|
|
14
16
|
DESCRIPTION
|
|
15
17
|
|
|
16
18
|
spec.homepage = "https://github.com/lutaml/moxml"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# CustomizedLibxml wrapper classes are tested via:
|
|
2
|
+
# spec/moxml/adapter/libxml_spec.rb
|
|
3
|
+
#
|
|
4
|
+
# Individual wrapper spec files removed to avoid redundant pending tests.
|
|
5
|
+
# The wrapper classes (Element, Text, Cdata, Comment, etc.) are thoroughly
|
|
6
|
+
# tested through the main LibXML adapter test suite.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Consistency Tests
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This directory contains cross-adapter consistency tests that verify all adapters produce equivalent results for the same operations. These tests act as a quality gate to ensure adapter parity.
|
|
6
|
+
|
|
7
|
+
## What Should Be Placed Here
|
|
8
|
+
|
|
9
|
+
- ✅ Tests verifying all adapters produce equivalent XML output
|
|
10
|
+
- ✅ Tests ensuring API parity across adapters
|
|
11
|
+
- ✅ Tests catching adapter-specific quirks
|
|
12
|
+
- ✅ Tests for serialization consistency
|
|
13
|
+
- ✅ Tests for parsing equivalence
|
|
14
|
+
|
|
15
|
+
## What Should NOT Be Placed Here
|
|
16
|
+
|
|
17
|
+
- ❌ Adapter-specific implementation tests (use adapter/ instead)
|
|
18
|
+
- ❌ Unit tests (use unit/ instead)
|
|
19
|
+
- ❌ Performance benchmarks (use performance/ instead)
|
|
20
|
+
- ❌ Documentation examples (use examples/ instead)
|
|
21
|
+
|
|
22
|
+
## How to Run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Run all consistency tests
|
|
26
|
+
bundle exec rake spec:consistency
|
|
27
|
+
|
|
28
|
+
# Run specific consistency test
|
|
29
|
+
bundle exec rspec spec/consistency/adapter_parity_spec.rb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Directory Structure
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
consistency/
|
|
36
|
+
└── adapter_parity_spec.rb # Ensures all adapters produce equivalent results
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Writing Consistency Tests
|
|
40
|
+
|
|
41
|
+
Consistency tests should compare output across all adapters:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
RSpec.describe "Adapter Parity" do
|
|
45
|
+
describe "Serialization consistency" do
|
|
46
|
+
it "produces equivalent XML across adapters" do
|
|
47
|
+
results = {}
|
|
48
|
+
|
|
49
|
+
Moxml::Adapter::AVALIABLE_ADAPTERS.each do |adapter|
|
|
50
|
+
Moxml.with_config(adapter) do
|
|
51
|
+
doc = Moxml.parse("<root><child>text</child></root>")
|
|
52
|
+
results[adapter] = doc.to_xml
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# All results should be equivalent (allowing for formatting differences)
|
|
57
|
+
expect(results.values.uniq.length).to eq(1)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Purpose
|
|
64
|
+
|
|
65
|
+
These tests ensure that:
|
|
66
|
+
1. Users can switch adapters without changing behavior
|
|
67
|
+
2. All adapters implement the same API surface
|
|
68
|
+
3. No adapter-specific bugs leak into user code
|
|
69
|
+
4. Documentation examples work with all adapters
|
|
70
|
+
|
|
71
|
+
## CI Integration
|
|
72
|
+
|
|
73
|
+
Consistency tests should run:
|
|
74
|
+
- On every pull request
|
|
75
|
+
- Before releases
|
|
76
|
+
- As a quality gate
|
|
77
|
+
- To catch regressions
|
|
@@ -18,10 +18,10 @@ RSpec.describe "Adapter Examples" do
|
|
|
18
18
|
|
|
19
19
|
def normalize_xml(xml)
|
|
20
20
|
xml.gsub(/>\s+</, "><")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
.gsub(/\s+/, " ")
|
|
22
|
+
.gsub(" >", ">")
|
|
23
|
+
.gsub("?><", "?>\n<")
|
|
24
|
+
.strip
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Example Tests
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This directory contains executable documentation examples. These tests verify that code examples from documentation and README work correctly and serve as living documentation.
|
|
6
|
+
|
|
7
|
+
## What Should Be Placed Here
|
|
8
|
+
|
|
9
|
+
- ✅ Tests for documentation examples
|
|
10
|
+
- ✅ Tests for README code snippets
|
|
11
|
+
- ✅ Examples that demonstrate API usage
|
|
12
|
+
- ✅ Tutorial-style example tests
|
|
13
|
+
- ✅ Tests that can be extracted for documentation
|
|
14
|
+
|
|
15
|
+
## What Should NOT Be Placed Here
|
|
16
|
+
|
|
17
|
+
- ❌ Comprehensive test coverage (use unit/ or integration/ instead)
|
|
18
|
+
- ❌ Edge case testing (use integration/ instead)
|
|
19
|
+
- ❌ Performance benchmarks (use performance/ instead)
|
|
20
|
+
- ❌ Adapter-specific tests (use adapter/ instead)
|
|
21
|
+
|
|
22
|
+
## How to Run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Run all example tests
|
|
26
|
+
bundle exec rake spec:examples
|
|
27
|
+
|
|
28
|
+
# Run specific example test
|
|
29
|
+
bundle exec rspec spec/examples/basic_usage_spec.rb
|
|
30
|
+
|
|
31
|
+
# Examples can be skipped in CI
|
|
32
|
+
bundle exec rspec --tag ~examples
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Directory Structure
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
examples/
|
|
39
|
+
├── basic_usage_spec.rb # README basic usage examples
|
|
40
|
+
├── namespace_examples_spec.rb # Namespace handling examples
|
|
41
|
+
├── xpath_examples_spec.rb # XPath query examples
|
|
42
|
+
└── attribute_examples_spec.rb # Attribute manipulation examples
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Writing Example Tests
|
|
46
|
+
|
|
47
|
+
Example tests should:
|
|
48
|
+
1. Be simple and easy to understand
|
|
49
|
+
2. Demonstrate real-world usage patterns
|
|
50
|
+
3. Run with all adapters to ensure portability
|
|
51
|
+
4. Be suitable for extraction into documentation
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
RSpec.describe "Basic Usage Examples" do
|
|
55
|
+
Moxml::Adapter::AVALIABLE_ADAPTERS.each do |adapter_name|
|
|
56
|
+
context "with #{adapter_name} adapter" do
|
|
57
|
+
let(:context) { Moxml.new(adapter_name) }
|
|
58
|
+
|
|
59
|
+
it "parses XML" do
|
|
60
|
+
doc = context.parse("<root>Text</root>")
|
|
61
|
+
expect(doc.root.text).to eq("Text")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Tagging
|
|
69
|
+
|
|
70
|
+
Example tests are tagged with `:examples` and can be excluded:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
RSpec.describe "Example", :examples do
|
|
74
|
+
# ...
|
|
75
|
+
end
|