moxml 0.1.22 → 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/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +680 -110
- data/Rakefile +12 -9
- data/lib/compat/opal/moxml_boot.rb +53 -0
- data/lib/compat/opal/rexml/namespace.rb +8 -5
- data/lib/compat/opal/rexml/parsers/baseparser.rb +276 -212
- data/lib/compat/opal/rexml/source.rb +28 -27
- data/lib/compat/opal/rexml/text.rb +112 -104
- data/lib/compat/opal/rexml/xmltokens.rb +8 -8
- data/lib/compat/opal/rexml_compat.rb +12 -11
- 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_oga/xml_declaration.rb +8 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +7 -8
- data/lib/moxml/adapter/headed_ox.rb +1 -5
- data/lib/moxml/adapter/libxml/entity_ref_registry.rb +4 -2
- data/lib/moxml/adapter/libxml/entity_restorer.rb +3 -1
- data/lib/moxml/adapter/libxml.rb +29 -14
- data/lib/moxml/adapter/nokogiri.rb +21 -17
- data/lib/moxml/adapter/oga.rb +48 -67
- data/lib/moxml/adapter/ox.rb +61 -31
- data/lib/moxml/adapter/rexml.rb +5 -6
- data/lib/moxml/adapter.rb +13 -5
- data/lib/moxml/attribute.rb +5 -2
- data/lib/moxml/config.rb +16 -2
- data/lib/moxml/context.rb +8 -8
- data/lib/moxml/declaration.rb +2 -7
- data/lib/moxml/document.rb +2 -19
- data/lib/moxml/document_builder.rb +12 -8
- data/lib/moxml/element.rb +4 -4
- data/lib/moxml/entity_registry.rb +8 -4
- data/lib/moxml/entity_registry_opal_data.rb +3 -2
- data/lib/moxml/node.rb +45 -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 +2 -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 +74 -20
- data/spec/integration/all_adapters_spec.rb +1 -0
- data/spec/integration/shared_examples/line_ending_behavior.rb +56 -0
- data/spec/moxml/adapter/libxml_internals_spec.rb +4 -2
- data/spec/moxml/adapter/platform_spec.rb +2 -1
- data/spec/moxml/attribute_spec.rb +16 -0
- data/spec/moxml/config_spec.rb +33 -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 +4 -2
|
@@ -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
|
|
@@ -82,6 +80,7 @@ module Moxml
|
|
|
82
80
|
else
|
|
83
81
|
# Accept relative references and bare paths
|
|
84
82
|
return unless uri.match?(/[\x00-\x08\x0B\x0C\x0E-\x1F]/)
|
|
83
|
+
|
|
85
84
|
raise ValidationError, "Invalid URI: #{uri}"
|
|
86
85
|
end
|
|
87
86
|
rescue URI::InvalidURIError
|
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,27 +40,64 @@ 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
|
-
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def preprocess_entities(xml)
|
|
60
|
+
Adapter::Base.preprocess_entities(xml)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def restore_entities(text)
|
|
64
|
+
Adapter::Base.restore_entities(text)
|
|
34
65
|
end
|
|
35
66
|
end
|
|
36
|
-
end
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.shared_examples "Moxml Line Ending" do
|
|
4
|
+
describe "Line ending configuration" do
|
|
5
|
+
let(:context) { Moxml.new }
|
|
6
|
+
let(:xml) { "<root><child>text</child></root>" }
|
|
7
|
+
|
|
8
|
+
it "produces no CRLF with LF default" do
|
|
9
|
+
doc = context.parse(xml)
|
|
10
|
+
expect(doc.to_xml).not_to include("\r\n")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "produces no bare LF with CRLF configured" do
|
|
14
|
+
context.config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
15
|
+
doc = context.parse(xml)
|
|
16
|
+
expect(doc.to_xml).not_to match(/(?<!\r)\n/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "allows per-call CRLF override producing no bare LF" do
|
|
20
|
+
doc = context.parse(xml)
|
|
21
|
+
output = doc.to_xml(line_ending: Moxml::Config::LINE_ENDING_CRLF)
|
|
22
|
+
expect(output).not_to match(/(?<!\r)\n/)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "per-call LF override wins over config CRLF" do
|
|
26
|
+
context.config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
27
|
+
doc = context.parse(xml)
|
|
28
|
+
expect(doc.to_xml(line_ending: Moxml::Config::LINE_ENDING_LF))
|
|
29
|
+
.not_to include("\r\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "produces identical bytes on re-serialization with CRLF" do
|
|
33
|
+
context.config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
34
|
+
doc = context.parse(xml)
|
|
35
|
+
first = doc.to_xml
|
|
36
|
+
|
|
37
|
+
ctx2 = Moxml.new
|
|
38
|
+
ctx2.config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
39
|
+
result = ctx2.parse(first)
|
|
40
|
+
expect(result.to_xml).to eq(first)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "preserves element structure through CRLF round-trip" do
|
|
44
|
+
doc = context.parse("<root><a>text</a><b>more</b></root>")
|
|
45
|
+
context.config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
46
|
+
crlf_output = doc.to_xml
|
|
47
|
+
|
|
48
|
+
ctx2 = Moxml.new
|
|
49
|
+
result = ctx2.parse(crlf_output)
|
|
50
|
+
elements = result.root.children.select(&:element?)
|
|
51
|
+
expect(elements.map(&:name)).to eq(%w[a b])
|
|
52
|
+
expect(elements[0].children.first.content).to eq("text")
|
|
53
|
+
expect(elements[1].children.first.content).to eq("more")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -105,7 +105,8 @@ RSpec.describe Moxml::Adapter::Libxml do
|
|
|
105
105
|
|
|
106
106
|
it "returns [nil, nil] when the document has no entity-ref attachments" do
|
|
107
107
|
root = libxml_native(context.parse("<root><a/></root>").root)
|
|
108
|
-
expect(adapter.send(:lookup_entity_ref_serialization,
|
|
108
|
+
expect(adapter.send(:lookup_entity_ref_serialization,
|
|
109
|
+
root)).to eq([nil, nil])
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
it "returns [nil, nil] for an element with no entity refs even when the doc has erefs elsewhere" do
|
|
@@ -129,7 +130,8 @@ RSpec.describe Moxml::Adapter::Libxml do
|
|
|
129
130
|
)
|
|
130
131
|
a.add_child(eref)
|
|
131
132
|
|
|
132
|
-
refs, seq = adapter.send(:lookup_entity_ref_serialization,
|
|
133
|
+
refs, seq = adapter.send(:lookup_entity_ref_serialization,
|
|
134
|
+
libxml_native(a))
|
|
133
135
|
expect(refs).to be_an(Array).and(satisfy { |r| !r.empty? })
|
|
134
136
|
expect(seq).to be_an(Array).and(include(:eref))
|
|
135
137
|
end
|
|
@@ -4,7 +4,8 @@ require "spec_helper"
|
|
|
4
4
|
|
|
5
5
|
RSpec.describe Moxml::Adapter, ".platform_adapters" do
|
|
6
6
|
it "includes all known adapters under MRI" do
|
|
7
|
-
expect(described_class.platform_adapters).to include(:nokogiri, :oga,
|
|
7
|
+
expect(described_class.platform_adapters).to include(:nokogiri, :oga,
|
|
8
|
+
:rexml, :ox)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
it "uses the AVAILABLE_ADAPTERS constant under MRI" do
|
|
@@ -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
|
data/spec/moxml/config_spec.rb
CHANGED
|
@@ -10,6 +10,7 @@ RSpec.describe Moxml::Config do
|
|
|
10
10
|
expect(config.strict_parsing).to be true
|
|
11
11
|
expect(config.default_encoding).to eq("UTF-8")
|
|
12
12
|
expect(config.default_indent).to eq(2)
|
|
13
|
+
expect(config.default_line_ending).to eq("\n")
|
|
13
14
|
expect(config.entity_encoding).to eq(:basic)
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -89,6 +90,38 @@ RSpec.describe Moxml::Config do
|
|
|
89
90
|
end
|
|
90
91
|
end
|
|
91
92
|
|
|
93
|
+
describe "#default_line_ending=" do
|
|
94
|
+
it "accepts LINE_ENDING_LF" do
|
|
95
|
+
config.default_line_ending = Moxml::Config::LINE_ENDING_LF
|
|
96
|
+
expect(config.default_line_ending).to eq("\n")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "accepts LINE_ENDING_CRLF" do
|
|
100
|
+
config.default_line_ending = Moxml::Config::LINE_ENDING_CRLF
|
|
101
|
+
expect(config.default_line_ending).to eq("\r\n")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "rejects nil" do
|
|
105
|
+
expect { config.default_line_ending = nil }
|
|
106
|
+
.to raise_error(ArgumentError, /Invalid line_ending/)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "rejects arbitrary strings" do
|
|
110
|
+
expect { config.default_line_ending = "BAD" }
|
|
111
|
+
.to raise_error(ArgumentError, /Invalid line_ending/)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "rejects bare CR" do
|
|
115
|
+
expect { config.default_line_ending = "\r" }
|
|
116
|
+
.to raise_error(ArgumentError, /Invalid line_ending/)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "rejects empty string" do
|
|
120
|
+
expect { config.default_line_ending = "" }
|
|
121
|
+
.to raise_error(ArgumentError, /Invalid line_ending/)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
92
125
|
describe "#adapter=" do
|
|
93
126
|
it "sets valid adapter" do
|
|
94
127
|
config.adapter = :ox
|
data/spec/moxml/context_spec.rb
CHANGED
|
@@ -25,4 +25,18 @@ RSpec.describe Moxml::Context do
|
|
|
25
25
|
expect(context.config.adapter.ancestors).to include(Moxml::Adapter::Base)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
describe "#build" do
|
|
30
|
+
it "creates a document via builder DSL" do
|
|
31
|
+
doc = context.build do
|
|
32
|
+
element("root") do
|
|
33
|
+
element("child") do
|
|
34
|
+
text("hello")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
expect(doc).to be_a(Moxml::Document)
|
|
39
|
+
expect(doc.root.name).to eq("root")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
28
42
|
end
|
data/spec/moxml/moxml_spec.rb
CHANGED
|
@@ -52,4 +52,17 @@ RSpec.describe Moxml do
|
|
|
52
52
|
expect(Moxml::Config::VALID_ADAPTERS).to include(Moxml::Config.runtime_default_adapter)
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
describe ".parse" do
|
|
57
|
+
it "parses XML with shorthand" do
|
|
58
|
+
doc = described_class.parse("<root>hello</root>")
|
|
59
|
+
expect(doc).to be_a(Moxml::Document)
|
|
60
|
+
expect(doc.root.text).to eq("hello")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "accepts adapter option" do
|
|
64
|
+
doc = described_class.parse("<root/>", adapter: :rexml)
|
|
65
|
+
expect(doc).to be_a(Moxml::Document)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
55
68
|
end
|