moxml 0.1.23 → 0.1.24
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/Rakefile +1 -1
- data/lib/compat/opal/moxml_boot.rb +53 -0
- data/lib/moxml/adapter/base.rb +5 -5
- data/lib/moxml/adapter/customized_libxml/cdata.rb +0 -2
- data/lib/moxml/adapter/customized_libxml/comment.rb +0 -2
- data/lib/moxml/adapter/customized_libxml/element.rb +3 -5
- data/lib/moxml/adapter/customized_libxml/node.rb +2 -2
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +0 -2
- data/lib/moxml/adapter/customized_libxml/text.rb +0 -2
- data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -4
- data/lib/moxml/adapter/headed_ox.rb +1 -5
- data/lib/moxml/adapter/libxml.rb +12 -10
- data/lib/moxml/adapter/nokogiri.rb +4 -2
- data/lib/moxml/adapter/oga.rb +10 -10
- data/lib/moxml/adapter/ox.rb +29 -16
- data/lib/moxml/adapter/rexml.rb +5 -6
- data/lib/moxml/adapter.rb +12 -4
- data/lib/moxml/attribute.rb +5 -2
- data/lib/moxml/config.rb +1 -0
- data/lib/moxml/context.rb +8 -8
- data/lib/moxml/declaration.rb +2 -7
- data/lib/moxml/document.rb +0 -11
- data/lib/moxml/document_builder.rb +12 -8
- data/lib/moxml/element.rb +4 -4
- data/lib/moxml/node.rb +37 -14
- data/lib/moxml/node_set.rb +2 -4
- data/lib/moxml/sax/block_handler.rb +0 -2
- data/lib/moxml/sax/element_handler.rb +0 -2
- data/lib/moxml/sax/namespace_splitter.rb +5 -4
- data/lib/moxml/sax.rb +4 -25
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils.rb +1 -3
- data/lib/moxml/xpath/compiler.rb +1 -49
- data/lib/moxml/xpath/conversion.rb +7 -6
- data/lib/moxml/xpath/ruby/generator.rb +12 -19
- data/lib/moxml/xpath/ruby/node.rb +1 -9
- data/lib/moxml/xpath.rb +6 -14
- data/lib/moxml.rb +67 -20
- data/spec/moxml/attribute_spec.rb +16 -0
- data/spec/moxml/context_spec.rb +14 -0
- data/spec/moxml/moxml_spec.rb +13 -0
- data/spec/moxml/node_spec.rb +58 -0
- data/spec/moxml/xpath/ruby/node_spec.rb +3 -3
- metadata +3 -2
data/lib/moxml/document.rb
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "node"
|
|
4
|
-
require_relative "element"
|
|
5
|
-
require_relative "text"
|
|
6
|
-
require_relative "cdata"
|
|
7
|
-
require_relative "comment"
|
|
8
|
-
require_relative "processing_instruction"
|
|
9
|
-
require_relative "declaration"
|
|
10
|
-
require_relative "namespace"
|
|
11
|
-
require_relative "doctype"
|
|
12
|
-
require_relative "entity_reference"
|
|
13
|
-
|
|
14
3
|
module Moxml
|
|
15
4
|
class Document < Node
|
|
16
5
|
attr_accessor :has_xml_declaration
|
|
@@ -13,15 +13,13 @@ module Moxml
|
|
|
13
13
|
@current_doc = context.create_document(native_doc)
|
|
14
14
|
|
|
15
15
|
# Transfer has_declaration flag if present in attachments
|
|
16
|
-
if adapter.
|
|
17
|
-
adapter.attachments.key?(native_doc, :has_declaration)
|
|
16
|
+
if adapter.attachments.key?(native_doc, :has_declaration)
|
|
18
17
|
has_declaration = adapter.attachments.get(native_doc, :has_declaration)
|
|
19
18
|
@current_doc.has_xml_declaration = has_declaration
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
# Transfer DOCTYPE from parsed document if it exists in attachments
|
|
23
|
-
if adapter.
|
|
24
|
-
adapter.attachments.key?(native_doc, :doctype)
|
|
22
|
+
if adapter.attachments.key?(native_doc, :doctype)
|
|
25
23
|
doctype = adapter.attachments.get(native_doc, :doctype)
|
|
26
24
|
if doctype
|
|
27
25
|
adapter.attachments.set(@current_doc.native, :doctype, doctype)
|
|
@@ -35,10 +33,16 @@ module Moxml
|
|
|
35
33
|
private
|
|
36
34
|
|
|
37
35
|
def visit_node(node)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
case node_type(node)
|
|
37
|
+
when :document then visit_document(node)
|
|
38
|
+
when :element then visit_element(node)
|
|
39
|
+
when :text then visit_text(node)
|
|
40
|
+
when :cdata then visit_cdata(node)
|
|
41
|
+
when :comment then visit_comment(node)
|
|
42
|
+
when :processing_instruction then visit_processing_instruction(node)
|
|
43
|
+
when :doctype then visit_doctype(node)
|
|
44
|
+
when :entity_reference then visit_entity_reference(node)
|
|
45
|
+
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def visit_document(doc)
|
data/lib/moxml/element.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "attribute"
|
|
4
|
-
require_relative "namespace"
|
|
5
|
-
|
|
6
3
|
module Moxml
|
|
7
4
|
class Element < Node
|
|
8
5
|
def name
|
|
@@ -137,6 +134,8 @@ module Moxml
|
|
|
137
134
|
adapter.restore_entities(val)
|
|
138
135
|
end
|
|
139
136
|
|
|
137
|
+
alias content text
|
|
138
|
+
|
|
140
139
|
def text=(content)
|
|
141
140
|
adapter.set_text_content(@native, normalize_xml_value(content))
|
|
142
141
|
invalidate_children_cache!
|
|
@@ -158,7 +157,8 @@ module Moxml
|
|
|
158
157
|
end
|
|
159
158
|
|
|
160
159
|
def inner_xml=(xml)
|
|
161
|
-
|
|
160
|
+
wrapper = "_moxml_inner_#{Process.pid}_#{object_id}"
|
|
161
|
+
doc = context.parse("<#{wrapper}>#{xml}</#{wrapper}>")
|
|
162
162
|
adapter.replace_children(@native, doc.root.children.map(&:native))
|
|
163
163
|
invalidate_children_cache!
|
|
164
164
|
end
|
data/lib/moxml/node.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "xml_utils"
|
|
4
|
-
require_relative "node_set"
|
|
5
|
-
|
|
6
3
|
module Moxml
|
|
7
4
|
class Node
|
|
8
5
|
include XmlUtils
|
|
6
|
+
include Enumerable
|
|
9
7
|
|
|
10
8
|
TYPES = %i[
|
|
11
9
|
element text cdata comment processing_instruction document
|
|
@@ -143,13 +141,14 @@ module Moxml
|
|
|
143
141
|
""
|
|
144
142
|
end
|
|
145
143
|
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
# Returns the content/value of this node as a string.
|
|
145
|
+
# Each subclass overrides this with type-specific semantics:
|
|
146
|
+
# - Text, Comment, Cdata: raw text content
|
|
147
|
+
# - ProcessingInstruction: instruction content
|
|
148
|
+
# - Attribute: attribute value
|
|
149
|
+
# - Element: delegates to text (descendant text concatenation)
|
|
150
|
+
def content
|
|
151
|
+
""
|
|
153
152
|
end
|
|
154
153
|
|
|
155
154
|
# Returns the namespace of this node
|
|
@@ -176,15 +175,39 @@ module Moxml
|
|
|
176
175
|
def each_node(&block)
|
|
177
176
|
children.each do |child|
|
|
178
177
|
yield child
|
|
179
|
-
child.each_node(&block)
|
|
178
|
+
child.each_node(&block)
|
|
180
179
|
end
|
|
181
180
|
end
|
|
182
181
|
|
|
183
|
-
#
|
|
184
|
-
def
|
|
182
|
+
# Yield direct children, enabling Enumerable on the node.
|
|
183
|
+
def each(&block)
|
|
184
|
+
return to_enum(:each) unless block
|
|
185
|
+
|
|
186
|
+
children.each(&block)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def outer_xml
|
|
190
|
+
to_xml
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def before(node)
|
|
194
|
+
add_previous_sibling(node)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def after(node)
|
|
198
|
+
add_next_sibling(node)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def blank?
|
|
202
|
+
text.strip.empty?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Deep copy of the node (both dup and clone create deep copies for XML nodes)
|
|
206
|
+
def dup
|
|
185
207
|
Moxml::Node.wrap(adapter.dup(@native), context)
|
|
186
208
|
end
|
|
187
|
-
|
|
209
|
+
|
|
210
|
+
alias clone dup
|
|
188
211
|
|
|
189
212
|
def ==(other)
|
|
190
213
|
self.class == other.class && @native == other.native
|
data/lib/moxml/node_set.rb
CHANGED
|
@@ -68,8 +68,7 @@ module Moxml
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def <<(node)
|
|
71
|
-
|
|
72
|
-
native_node = node.respond_to?(:native) ? node.native : node
|
|
71
|
+
native_node = node.is_a?(Node) ? node.native : node
|
|
73
72
|
@nodes << native_node
|
|
74
73
|
@wrapped << nil
|
|
75
74
|
self
|
|
@@ -113,8 +112,7 @@ module Moxml
|
|
|
113
112
|
# Delete a node from the set
|
|
114
113
|
# Accepts both wrapped Moxml nodes and native nodes
|
|
115
114
|
def delete(node)
|
|
116
|
-
|
|
117
|
-
native_node = node.respond_to?(:native) ? node.native : node
|
|
115
|
+
native_node = node.is_a?(Node) ? node.native : node
|
|
118
116
|
idx = @nodes.index(native_node)
|
|
119
117
|
if idx
|
|
120
118
|
@nodes.delete_at(idx)
|
|
@@ -38,11 +38,12 @@ module Moxml
|
|
|
38
38
|
when nil
|
|
39
39
|
# nothing
|
|
40
40
|
else
|
|
41
|
-
if attributes.
|
|
41
|
+
if attributes.is_a?(Enumerable)
|
|
42
42
|
attributes.each do |item|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
case item
|
|
44
|
+
when Array
|
|
45
|
+
yield item[0], item[1] if item.size >= 2
|
|
46
|
+
else
|
|
46
47
|
yield item.name, item.value
|
|
47
48
|
end
|
|
48
49
|
end
|
data/lib/moxml/sax.rb
CHANGED
|
@@ -1,31 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "sax/handler"
|
|
4
|
-
require_relative "sax/element_handler"
|
|
5
|
-
require_relative "sax/block_handler"
|
|
6
|
-
|
|
7
3
|
module Moxml
|
|
8
|
-
# SAX (Simple API for XML) parsing interface
|
|
9
|
-
#
|
|
10
|
-
# Provides event-driven XML parsing across all Moxml adapters.
|
|
11
|
-
# SAX parsing is memory-efficient and suitable for processing large XML files.
|
|
12
|
-
#
|
|
13
|
-
# @example Class-based handler
|
|
14
|
-
# class MyHandler < Moxml::SAX::Handler
|
|
15
|
-
# def on_start_element(name, attributes = {}, namespaces = {})
|
|
16
|
-
# puts "Started element: #{name}"
|
|
17
|
-
# end
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
|
-
# context = Moxml.new
|
|
21
|
-
# context.sax_parse(xml_string, MyHandler.new)
|
|
22
|
-
#
|
|
23
|
-
# @example Block-based handler
|
|
24
|
-
# context.sax_parse(xml_string) do
|
|
25
|
-
# start_element { |name, attrs| puts "Element: #{name}" }
|
|
26
|
-
# characters { |text| puts "Text: #{text}" }
|
|
27
|
-
# end
|
|
28
|
-
#
|
|
29
4
|
module SAX
|
|
5
|
+
autoload :Handler, "moxml/sax/handler"
|
|
6
|
+
autoload :ElementHandler, "moxml/sax/element_handler"
|
|
7
|
+
autoload :BlockHandler, "moxml/sax/block_handler"
|
|
8
|
+
autoload :NamespaceSplitter, "moxml/sax/namespace_splitter"
|
|
30
9
|
end
|
|
31
10
|
end
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml/xml_utils.rb
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "xml_utils/encoder"
|
|
4
|
-
|
|
5
|
-
# Ruby 3.3+ requires the URI module to be explicitly required
|
|
6
3
|
require "uri" unless defined?(URI)
|
|
7
4
|
|
|
8
5
|
module Moxml
|
|
9
6
|
module XmlUtils
|
|
7
|
+
autoload :Encoder, "moxml/xml_utils/encoder"
|
|
10
8
|
def encode_entities(text, mode = nil)
|
|
11
9
|
Encoder.new(text, mode).call
|
|
12
10
|
end
|
data/lib/moxml/xpath/compiler.rb
CHANGED
|
@@ -62,16 +62,6 @@ module Moxml
|
|
|
62
62
|
matched = matched_literal
|
|
63
63
|
context_var = context_literal
|
|
64
64
|
|
|
65
|
-
# Enable debug output
|
|
66
|
-
debug = ENV["DEBUG_XPATH"] == "1"
|
|
67
|
-
if debug
|
|
68
|
-
puts "\n#{'=' * 60}"
|
|
69
|
-
puts "COMPILING XPath"
|
|
70
|
-
puts "=" * 60
|
|
71
|
-
puts "AST: #{ast.inspect}"
|
|
72
|
-
puts
|
|
73
|
-
end
|
|
74
|
-
|
|
75
65
|
ruby_ast = if return_nodeset?(ast)
|
|
76
66
|
process(ast, document) { |node| matched.push(node) }
|
|
77
67
|
else
|
|
@@ -103,14 +93,6 @@ module Moxml
|
|
|
103
93
|
generator = Ruby::Generator.new
|
|
104
94
|
source = generator.process(proc_ast)
|
|
105
95
|
|
|
106
|
-
if debug
|
|
107
|
-
puts "GENERATED RUBY CODE:"
|
|
108
|
-
puts "-" * 60
|
|
109
|
-
puts source
|
|
110
|
-
puts "=" * 60
|
|
111
|
-
puts
|
|
112
|
-
end
|
|
113
|
-
|
|
114
96
|
CONTEXT.evaluate(source)
|
|
115
97
|
ensure
|
|
116
98
|
@literal_id = 0
|
|
@@ -1506,7 +1488,7 @@ module Moxml
|
|
|
1506
1488
|
xml_lang.assign(string("xml:lang"))
|
|
1507
1489
|
end
|
|
1508
1490
|
.followed_by do
|
|
1509
|
-
node.
|
|
1491
|
+
node.is_a?(const_ref("Moxml", "Element")).while_true do
|
|
1510
1492
|
found.assign(node.get(xml_lang))
|
|
1511
1493
|
.followed_by do
|
|
1512
1494
|
found.if_true do
|
|
@@ -1622,36 +1604,6 @@ module Moxml
|
|
|
1622
1604
|
arg_var.assign(arg_ast).followed_by { yield arg_var }
|
|
1623
1605
|
end
|
|
1624
1606
|
|
|
1625
|
-
# Helper: Try to match first node v1
|
|
1626
|
-
def try_match_first_node_v1(ast, input, optimize_first = true)
|
|
1627
|
-
if return_nodeset?(ast) && optimize_first
|
|
1628
|
-
matched_set = unique_literal(:matched_set)
|
|
1629
|
-
first_node = unique_literal(:first_node)
|
|
1630
|
-
context_var = context_literal
|
|
1631
|
-
|
|
1632
|
-
# Create NodeSet for results
|
|
1633
|
-
nodeset_class = const_ref("Moxml", "NodeSet")
|
|
1634
|
-
empty_array = Ruby::Node.new(:array, [])
|
|
1635
|
-
nodeset_new = Ruby::Node.new(:send,
|
|
1636
|
-
[nodeset_class, "new", empty_array,
|
|
1637
|
-
context_var])
|
|
1638
|
-
|
|
1639
|
-
matched_set.assign(nodeset_new)
|
|
1640
|
-
.followed_by do
|
|
1641
|
-
# Process with block to accumulate results
|
|
1642
|
-
process(ast, input) { |node| matched_set.push(node) }
|
|
1643
|
-
end
|
|
1644
|
-
.followed_by do
|
|
1645
|
-
first_node.assign(matched_set[literal(0)])
|
|
1646
|
-
end
|
|
1647
|
-
.followed_by do
|
|
1648
|
-
first_node.if_true { first_node }.else { string("") }
|
|
1649
|
-
end
|
|
1650
|
-
else
|
|
1651
|
-
process(ast, input)
|
|
1652
|
-
end
|
|
1653
|
-
end
|
|
1654
|
-
|
|
1655
1607
|
# Helper: Create mass assignment node
|
|
1656
1608
|
def mass_assign(vars, value)
|
|
1657
1609
|
Ruby::Node.new(:massign, [vars, value])
|
|
@@ -12,11 +12,11 @@ module Moxml
|
|
|
12
12
|
# @param [Object] right
|
|
13
13
|
# @return [Array<Object, Object>]
|
|
14
14
|
def self.to_compatible_types(left, right)
|
|
15
|
-
if left.is_a?(Moxml::NodeSet) || left.
|
|
15
|
+
if left.is_a?(Moxml::NodeSet) || left.is_a?(Moxml::Node)
|
|
16
16
|
left = to_string(left)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
if right.is_a?(Moxml::NodeSet) || right.
|
|
19
|
+
if right.is_a?(Moxml::NodeSet) || right.is_a?(Moxml::Node)
|
|
20
20
|
right = to_string(right)
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -51,7 +51,7 @@ module Moxml
|
|
|
51
51
|
value = first_node_text(value)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
if value.
|
|
54
|
+
if value.is_a?(Moxml::Node)
|
|
55
55
|
value = value.text
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -67,7 +67,7 @@ module Moxml
|
|
|
67
67
|
value = first_node_text(value)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
if value.
|
|
70
|
+
if value.is_a?(Moxml::Node)
|
|
71
71
|
value = value.text
|
|
72
72
|
end
|
|
73
73
|
|
|
@@ -95,7 +95,7 @@ module Moxml
|
|
|
95
95
|
bool = !value.nan? && !value.zero?
|
|
96
96
|
elsif value.is_a?(Integer)
|
|
97
97
|
bool = !value.zero?
|
|
98
|
-
elsif value.
|
|
98
|
+
elsif value.is_a?(String) || value.is_a?(Moxml::NodeSet) || value.is_a?(Array)
|
|
99
99
|
bool = !value.empty?
|
|
100
100
|
elsif value
|
|
101
101
|
bool = true
|
|
@@ -117,7 +117,8 @@ module Moxml
|
|
|
117
117
|
# @param [Moxml::NodeSet] set
|
|
118
118
|
# @return [String]
|
|
119
119
|
def self.first_node_text(set)
|
|
120
|
-
|
|
120
|
+
first = set[0]
|
|
121
|
+
first.is_a?(Moxml::Node) ? first.text : ""
|
|
121
122
|
end
|
|
122
123
|
end
|
|
123
124
|
end
|
|
@@ -13,13 +13,7 @@ module Moxml
|
|
|
13
13
|
# @param [Moxml::XPath::Ruby::Node] ast
|
|
14
14
|
# @return [String]
|
|
15
15
|
def process(ast)
|
|
16
|
-
|
|
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)
|
|
16
|
+
send(:"on_#{ast.type}", ast)
|
|
23
17
|
end
|
|
24
18
|
|
|
25
19
|
# @param [Moxml::XPath::Ruby::Node] ast
|
|
@@ -81,6 +75,15 @@ module Moxml
|
|
|
81
75
|
"#{left_str} == #{right_str}"
|
|
82
76
|
end
|
|
83
77
|
|
|
78
|
+
def on_neq(ast)
|
|
79
|
+
left, right = *ast
|
|
80
|
+
|
|
81
|
+
left_str = process(left)
|
|
82
|
+
right_str = process(right)
|
|
83
|
+
|
|
84
|
+
"#{left_str} != #{right_str}"
|
|
85
|
+
end
|
|
86
|
+
|
|
84
87
|
# Processes a boolean "and" node.
|
|
85
88
|
#
|
|
86
89
|
# @param [Moxml::XPath::Ruby::Node] ast
|
|
@@ -167,14 +170,8 @@ module Moxml
|
|
|
167
170
|
brackets = name == "[]"
|
|
168
171
|
|
|
169
172
|
unless args.empty?
|
|
170
|
-
arg_strs =
|
|
171
|
-
|
|
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
|
|
173
|
+
arg_strs = args.map do |arg|
|
|
174
|
+
process(arg)
|
|
178
175
|
end
|
|
179
176
|
arg_str = arg_strs.join(", ")
|
|
180
177
|
call = brackets ? "[#{arg_str}]" : "#{call}(#{arg_str})"
|
|
@@ -182,10 +179,6 @@ module Moxml
|
|
|
182
179
|
|
|
183
180
|
if receiver
|
|
184
181
|
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
182
|
call = brackets ? "#{rec_str}#{call}" : "#{rec_str}.#{call}"
|
|
190
183
|
end
|
|
191
184
|
|
|
@@ -95,15 +95,7 @@ module Moxml
|
|
|
95
95
|
# @param [Class] klass
|
|
96
96
|
# @return [Moxml::XPath::Ruby::Node]
|
|
97
97
|
def is_a?(klass)
|
|
98
|
-
|
|
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])
|
|
98
|
+
Node.new(:send, [self, "is_a?", klass])
|
|
107
99
|
end
|
|
108
100
|
|
|
109
101
|
# Wraps the current node in a block.
|
data/lib/moxml/xpath.rb
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
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
4
|
module XPath
|
|
15
5
|
autoload :Engine, "moxml/xpath/engine"
|
|
16
6
|
autoload :Context, "moxml/xpath/context"
|
|
@@ -20,15 +10,17 @@ module Moxml
|
|
|
20
10
|
autoload :Parser, "moxml/xpath/parser"
|
|
21
11
|
autoload :Compiler, "moxml/xpath/compiler"
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
autoload :Error, "moxml/xpath/errors"
|
|
14
|
+
autoload :SyntaxError, "moxml/xpath/errors"
|
|
15
|
+
autoload :EvaluationError, "moxml/xpath/errors"
|
|
16
|
+
autoload :FunctionError, "moxml/xpath/errors"
|
|
17
|
+
autoload :NodeTypeError, "moxml/xpath/errors"
|
|
18
|
+
autoload :InvalidContextError, "moxml/xpath/errors"
|
|
25
19
|
|
|
26
|
-
# AST nodes for expression representation
|
|
27
20
|
module AST
|
|
28
21
|
autoload :Node, "moxml/xpath/ast/node"
|
|
29
22
|
end
|
|
30
23
|
|
|
31
|
-
# Ruby AST generation for compiling XPath
|
|
32
24
|
module Ruby
|
|
33
25
|
autoload :Node, "moxml/xpath/ruby/node"
|
|
34
26
|
autoload :Generator, "moxml/xpath/ruby/generator"
|
data/lib/moxml.rb
CHANGED
|
@@ -8,13 +8,30 @@ module Moxml
|
|
|
8
8
|
context
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def parse(xml, adapter: nil, **options)
|
|
12
|
+
Context.new(adapter).parse(xml, options)
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def configure
|
|
12
16
|
yield Config.default if block_given?
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def with_config(adapter_name = nil, strict_parsing = nil,
|
|
16
20
|
default_encoding = nil)
|
|
17
|
-
|
|
21
|
+
original = Config.default
|
|
22
|
+
saved_values = {
|
|
23
|
+
adapter: original.adapter_name,
|
|
24
|
+
strict_parsing: original.strict_parsing,
|
|
25
|
+
default_encoding: original.default_encoding,
|
|
26
|
+
default_indent: original.default_indent,
|
|
27
|
+
default_line_ending: original.default_line_ending,
|
|
28
|
+
entity_load_mode: original.entity_load_mode,
|
|
29
|
+
entity_encoding: original.entity_encoding,
|
|
30
|
+
restore_entities: original.restore_entities,
|
|
31
|
+
namespace_validation_mode: original.namespace_validation_mode,
|
|
32
|
+
entity_restoration_mode: original.entity_restoration_mode,
|
|
33
|
+
preload_entity_sets: original.preload_entity_sets.dup,
|
|
34
|
+
}
|
|
18
35
|
|
|
19
36
|
configure do |config|
|
|
20
37
|
config.adapter = adapter_name unless adapter_name.nil?
|
|
@@ -23,15 +40,22 @@ module Moxml
|
|
|
23
40
|
end
|
|
24
41
|
|
|
25
42
|
yield if block_given?
|
|
26
|
-
|
|
27
|
-
# restore the original config
|
|
43
|
+
ensure
|
|
28
44
|
configure do |config|
|
|
29
|
-
config.adapter =
|
|
30
|
-
config.strict_parsing =
|
|
31
|
-
config.default_encoding =
|
|
45
|
+
config.adapter = saved_values[:adapter]
|
|
46
|
+
config.strict_parsing = saved_values[:strict_parsing]
|
|
47
|
+
config.default_encoding = saved_values[:default_encoding]
|
|
48
|
+
config.default_indent = saved_values[:default_indent]
|
|
49
|
+
config.default_line_ending = saved_values[:default_line_ending]
|
|
50
|
+
config.entity_load_mode = saved_values[:entity_load_mode]
|
|
51
|
+
config.entity_encoding = saved_values[:entity_encoding]
|
|
52
|
+
config.restore_entities = saved_values[:restore_entities]
|
|
53
|
+
config.namespace_validation_mode = saved_values[:namespace_validation_mode]
|
|
54
|
+
config.entity_restoration_mode = saved_values[:entity_restoration_mode]
|
|
55
|
+
config.preload_entity_sets = saved_values[:preload_entity_sets]
|
|
32
56
|
end
|
|
33
|
-
original_config = nil
|
|
34
57
|
end
|
|
58
|
+
|
|
35
59
|
def preprocess_entities(xml)
|
|
36
60
|
Adapter::Base.preprocess_entities(xml)
|
|
37
61
|
end
|
|
@@ -40,17 +64,40 @@ module Moxml
|
|
|
40
64
|
Adapter::Base.restore_entities(text)
|
|
41
65
|
end
|
|
42
66
|
end
|
|
43
|
-
end
|
|
44
67
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
autoload :VERSION, "moxml/version"
|
|
69
|
+
autoload :Config, "moxml/config"
|
|
70
|
+
autoload :Context, "moxml/context"
|
|
71
|
+
autoload :Node, "moxml/node"
|
|
72
|
+
autoload :NodeSet, "moxml/node_set"
|
|
73
|
+
autoload :Document, "moxml/document"
|
|
74
|
+
autoload :Element, "moxml/element"
|
|
75
|
+
autoload :Text, "moxml/text"
|
|
76
|
+
autoload :Cdata, "moxml/cdata"
|
|
77
|
+
autoload :Comment, "moxml/comment"
|
|
78
|
+
autoload :Attribute, "moxml/attribute"
|
|
79
|
+
autoload :ProcessingInstruction, "moxml/processing_instruction"
|
|
80
|
+
autoload :Declaration, "moxml/declaration"
|
|
81
|
+
autoload :Namespace, "moxml/namespace"
|
|
82
|
+
autoload :Doctype, "moxml/doctype"
|
|
83
|
+
autoload :EntityReference, "moxml/entity_reference"
|
|
84
|
+
autoload :DocumentBuilder, "moxml/document_builder"
|
|
85
|
+
autoload :Builder, "moxml/builder"
|
|
86
|
+
autoload :EntityRegistry, "moxml/entity_registry"
|
|
87
|
+
autoload :NativeAttachment, "moxml/native_attachment"
|
|
88
|
+
autoload :XmlUtils, "moxml/xml_utils"
|
|
89
|
+
autoload :Adapter, "moxml/adapter"
|
|
90
|
+
autoload :XPath, "moxml/xpath"
|
|
91
|
+
autoload :SAX, "moxml/sax"
|
|
92
|
+
|
|
93
|
+
# Error hierarchy — each subclass autoloads from the same file
|
|
94
|
+
autoload :Error, "moxml/error"
|
|
95
|
+
autoload :NotImplementedError, "moxml/error"
|
|
96
|
+
autoload :ValidationError, "moxml/error"
|
|
97
|
+
autoload :ParseError, "moxml/error"
|
|
98
|
+
autoload :DocumentStructureError, "moxml/error"
|
|
99
|
+
autoload :NamespaceError, "moxml/error"
|
|
100
|
+
autoload :EntityDataError, "moxml/error"
|
|
101
|
+
autoload :XPathError, "moxml/error"
|
|
102
|
+
autoload :AdapterError, "moxml/error"
|
|
103
|
+
end
|
|
@@ -27,4 +27,20 @@ RSpec.describe Moxml::Attribute do
|
|
|
27
27
|
expect(attr.to_s).to match(/\w+="\w+"/)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
describe "#element" do
|
|
32
|
+
it "returns wrapped Element" do
|
|
33
|
+
attr = element.attributes.find { |a| a.name == "id" }
|
|
34
|
+
parent = attr.element
|
|
35
|
+
expect(parent).to be_a(Moxml::Element)
|
|
36
|
+
expect(parent.name).to eq("root")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#content" do
|
|
41
|
+
it "returns attribute value" do
|
|
42
|
+
attr = element.attributes.find { |a| a.name == "id" }
|
|
43
|
+
expect(attr.content).to eq("123")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
30
46
|
end
|