moxml 0.1.0 → 0.1.1
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/rake.yml +15 -0
- data/.github/workflows/release.yml +23 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +65 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -3
- data/README.adoc +400 -594
- data/lib/moxml/adapter/base.rb +102 -0
- data/lib/moxml/adapter/customized_oga/xml_declaration.rb +18 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +104 -0
- data/lib/moxml/adapter/nokogiri.rb +314 -0
- data/lib/moxml/adapter/oga.rb +309 -0
- data/lib/moxml/adapter/ox.rb +325 -0
- data/lib/moxml/adapter.rb +26 -170
- data/lib/moxml/attribute.rb +47 -14
- data/lib/moxml/builder.rb +64 -0
- data/lib/moxml/cdata.rb +4 -26
- data/lib/moxml/comment.rb +6 -22
- data/lib/moxml/config.rb +39 -15
- data/lib/moxml/context.rb +29 -0
- data/lib/moxml/declaration.rb +16 -26
- data/lib/moxml/doctype.rb +9 -0
- data/lib/moxml/document.rb +51 -63
- data/lib/moxml/document_builder.rb +87 -0
- data/lib/moxml/element.rb +61 -99
- data/lib/moxml/error.rb +20 -0
- data/lib/moxml/namespace.rb +12 -37
- data/lib/moxml/node.rb +78 -58
- data/lib/moxml/node_set.rb +19 -222
- data/lib/moxml/processing_instruction.rb +6 -25
- data/lib/moxml/text.rb +4 -26
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +55 -0
- data/lib/moxml/xml_utils.rb +80 -0
- data/lib/moxml.rb +33 -33
- data/moxml.gemspec +1 -1
- data/spec/moxml/adapter/nokogiri_spec.rb +14 -0
- data/spec/moxml/adapter/oga_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +49 -0
- data/spec/moxml/all_with_adapters_spec.rb +46 -0
- data/spec/moxml/config_spec.rb +55 -0
- data/spec/moxml/error_spec.rb +71 -0
- data/spec/moxml/examples/adapter_spec.rb +27 -0
- data/spec/moxml_spec.rb +50 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/shared_examples/attribute.rb +165 -0
- data/spec/support/shared_examples/builder.rb +25 -0
- data/spec/support/shared_examples/cdata.rb +70 -0
- data/spec/support/shared_examples/comment.rb +65 -0
- data/spec/support/shared_examples/context.rb +35 -0
- data/spec/support/shared_examples/declaration.rb +93 -0
- data/spec/support/shared_examples/doctype.rb +25 -0
- data/spec/support/shared_examples/document.rb +110 -0
- data/spec/support/shared_examples/document_builder.rb +43 -0
- data/spec/support/shared_examples/edge_cases.rb +185 -0
- data/spec/support/shared_examples/element.rb +110 -0
- data/spec/support/shared_examples/examples/attribute.rb +42 -0
- data/spec/support/shared_examples/examples/basic_usage.rb +67 -0
- data/spec/support/shared_examples/examples/memory.rb +54 -0
- data/spec/support/shared_examples/examples/namespace.rb +65 -0
- data/spec/support/shared_examples/examples/readme_examples.rb +100 -0
- data/spec/support/shared_examples/examples/thread_safety.rb +43 -0
- data/spec/support/shared_examples/examples/xpath.rb +39 -0
- data/spec/support/shared_examples/integration.rb +135 -0
- data/spec/support/shared_examples/namespace.rb +96 -0
- data/spec/support/shared_examples/node.rb +110 -0
- data/spec/support/shared_examples/node_set.rb +90 -0
- data/spec/support/shared_examples/processing_instruction.rb +88 -0
- data/spec/support/shared_examples/text.rb +66 -0
- data/spec/support/shared_examples/xml_adapter.rb +191 -0
- data/spec/support/xml_matchers.rb +27 -0
- metadata +55 -6
- data/.github/workflows/main.yml +0 -27
- data/lib/moxml/error_handler.rb +0 -77
- data/lib/moxml/errors.rb +0 -169
data/lib/moxml/node.rb
CHANGED
@@ -1,113 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "xml_utils"
|
4
|
+
require_relative "node_set"
|
5
|
+
|
1
6
|
module Moxml
|
2
7
|
class Node
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(native_node = nil)
|
6
|
-
@native = native_node || create_native_node
|
7
|
-
end
|
8
|
+
include XmlUtils
|
8
9
|
|
9
|
-
|
10
|
-
return nil if native_node.nil?
|
10
|
+
attr_reader :native, :context
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
when :comment then Comment
|
17
|
-
when :processing_instruction then ProcessingInstruction
|
18
|
-
when :document then Document
|
19
|
-
when :attribute then Attribute
|
20
|
-
when :namespace then Namespace
|
21
|
-
else
|
22
|
-
raise Error, "Unknown node type: #{native_node.class}"
|
23
|
-
end
|
12
|
+
def initialize(native, context)
|
13
|
+
@native = native
|
14
|
+
@context = context
|
15
|
+
end
|
24
16
|
|
25
|
-
|
17
|
+
def document
|
18
|
+
Document.wrap(adapter.document(@native), context)
|
26
19
|
end
|
27
20
|
|
28
21
|
def parent
|
29
|
-
|
22
|
+
Node.wrap(adapter.parent(@native), context)
|
30
23
|
end
|
31
24
|
|
32
25
|
def children
|
33
|
-
NodeSet.new(adapter.children(native))
|
26
|
+
NodeSet.new(adapter.children(@native), context)
|
34
27
|
end
|
35
28
|
|
36
29
|
def next_sibling
|
37
|
-
|
30
|
+
Node.wrap(adapter.next_sibling(@native), context)
|
38
31
|
end
|
39
32
|
|
40
33
|
def previous_sibling
|
41
|
-
|
34
|
+
Node.wrap(adapter.previous_sibling(@native), context)
|
42
35
|
end
|
43
36
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def replace(node)
|
50
|
-
adapter.replace(native, node.native)
|
37
|
+
def add_child(node)
|
38
|
+
node = prepare_node(node)
|
39
|
+
adapter.add_child(@native, node.native)
|
51
40
|
self
|
52
41
|
end
|
53
42
|
|
54
43
|
def add_previous_sibling(node)
|
55
|
-
|
44
|
+
node = prepare_node(node)
|
45
|
+
adapter.add_previous_sibling(@native, node.native)
|
56
46
|
self
|
57
47
|
end
|
58
48
|
|
59
49
|
def add_next_sibling(node)
|
60
|
-
|
50
|
+
node = prepare_node(node)
|
51
|
+
adapter.add_next_sibling(@native, node.native)
|
61
52
|
self
|
62
53
|
end
|
63
54
|
|
64
|
-
def
|
65
|
-
adapter.
|
55
|
+
def remove
|
56
|
+
adapter.remove(@native)
|
57
|
+
self
|
66
58
|
end
|
67
59
|
|
68
|
-
def
|
69
|
-
|
60
|
+
def replace(node)
|
61
|
+
node = prepare_node(node)
|
62
|
+
adapter.replace(@native, node.native)
|
70
63
|
self
|
71
64
|
end
|
72
65
|
|
73
|
-
def
|
74
|
-
adapter.
|
66
|
+
def to_xml(options = {})
|
67
|
+
adapter.serialize(@native, default_options.merge(options))
|
75
68
|
end
|
76
69
|
|
77
|
-
def
|
78
|
-
adapter.
|
70
|
+
def xpath(expression, namespaces = {})
|
71
|
+
NodeSet.new(adapter.xpath(@native, expression, namespaces), context)
|
79
72
|
end
|
80
73
|
|
81
|
-
def
|
82
|
-
adapter.
|
74
|
+
def at_xpath(expression, namespaces = {})
|
75
|
+
Node.wrap(adapter.at_xpath(@native, expression, namespaces), context)
|
83
76
|
end
|
84
77
|
|
85
|
-
def
|
86
|
-
|
78
|
+
def ==(other)
|
79
|
+
self.class == other.class && @native == other.native
|
87
80
|
end
|
88
81
|
|
89
|
-
def
|
90
|
-
|
91
|
-
end
|
82
|
+
def self.wrap(node, context)
|
83
|
+
return nil if node.nil?
|
92
84
|
|
93
|
-
|
94
|
-
|
85
|
+
klass = case adapter(context).node_type(node)
|
86
|
+
when :element then Element
|
87
|
+
when :text then Text
|
88
|
+
when :cdata then Cdata
|
89
|
+
when :comment then Comment
|
90
|
+
when :processing_instruction then ProcessingInstruction
|
91
|
+
when :document then Document
|
92
|
+
when :declaration then Declaration
|
93
|
+
when :doctype then Doctype
|
94
|
+
else self
|
95
|
+
end
|
96
|
+
|
97
|
+
klass.new(node, context)
|
95
98
|
end
|
96
99
|
|
97
100
|
protected
|
98
101
|
|
99
|
-
def
|
100
|
-
|
102
|
+
def adapter
|
103
|
+
context.config.adapter
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.adapter(context)
|
107
|
+
context.config.adapter
|
101
108
|
end
|
102
109
|
|
103
110
|
private
|
104
111
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
112
|
+
def prepare_node(node)
|
113
|
+
case node
|
114
|
+
when String then Text.new(adapter.create_text(node), context)
|
115
|
+
when Node then node
|
116
|
+
else
|
117
|
+
raise ArgumentError, "Invalid node type: #{node.class}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def default_options
|
122
|
+
{
|
123
|
+
encoding: context.config.default_encoding,
|
124
|
+
indent: context.config.default_indent,
|
125
|
+
# The short format of empty tags in Oga and Nokogiri isn't configurable
|
126
|
+
# Oga: <empty /> (with a space)
|
127
|
+
# Nokogiri: <empty/> (without a space)
|
128
|
+
# The expanded format is enforced to avoid this conflict
|
129
|
+
expand_empty: true
|
130
|
+
}
|
111
131
|
end
|
112
132
|
end
|
113
133
|
end
|
data/lib/moxml/node_set.rb
CHANGED
@@ -1,43 +1,46 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Moxml
|
3
4
|
class NodeSet
|
4
5
|
include Enumerable
|
5
6
|
|
6
|
-
attr_reader :
|
7
|
+
attr_reader :nodes, :context
|
7
8
|
|
8
|
-
def initialize(
|
9
|
-
@
|
9
|
+
def initialize(nodes, context)
|
10
|
+
@nodes = Array(nodes)
|
11
|
+
@context = context
|
10
12
|
end
|
11
13
|
|
12
14
|
def each
|
13
|
-
return
|
14
|
-
|
15
|
+
return to_enum(:each) unless block_given?
|
16
|
+
|
17
|
+
nodes.each { |node| yield Node.wrap(node, context) }
|
15
18
|
self
|
16
19
|
end
|
17
20
|
|
18
21
|
def [](index)
|
19
22
|
case index
|
20
23
|
when Integer
|
21
|
-
Node.wrap(
|
24
|
+
Node.wrap(nodes[index], context)
|
22
25
|
when Range
|
23
|
-
NodeSet.new(
|
26
|
+
NodeSet.new(nodes[index], context)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
27
30
|
def first
|
28
|
-
Node.wrap(
|
31
|
+
Node.wrap(nodes.first, context)
|
29
32
|
end
|
30
33
|
|
31
34
|
def last
|
32
|
-
Node.wrap(
|
35
|
+
Node.wrap(nodes.last, context)
|
33
36
|
end
|
34
37
|
|
35
38
|
def empty?
|
36
|
-
|
39
|
+
nodes.empty?
|
37
40
|
end
|
38
41
|
|
39
42
|
def size
|
40
|
-
|
43
|
+
nodes.size
|
41
44
|
end
|
42
45
|
|
43
46
|
alias length size
|
@@ -46,223 +49,17 @@ module Moxml
|
|
46
49
|
map { |node| node }
|
47
50
|
end
|
48
51
|
|
49
|
-
def
|
50
|
-
|
51
|
-
native_nodes.select { |node| Moxml.adapter.matches?(node, selector) }
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
|
-
def remove
|
56
|
-
each(&:remove)
|
57
|
-
self
|
52
|
+
def +(other)
|
53
|
+
self.class.new(nodes + other.nodes, context)
|
58
54
|
end
|
59
55
|
|
60
56
|
def text
|
61
57
|
map(&:text).join
|
62
58
|
end
|
63
59
|
|
64
|
-
def
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
def wrap(html_or_element)
|
69
|
-
each do |node|
|
70
|
-
wrapper = case html_or_element
|
71
|
-
when String
|
72
|
-
Document.parse("<div>#{html_or_element}</div>").root.children.first
|
73
|
-
when Element
|
74
|
-
html_or_element.dup
|
75
|
-
else
|
76
|
-
raise ArgumentError, "Expected String or Element"
|
77
|
-
end
|
78
|
-
|
79
|
-
node.add_previous_sibling(wrapper)
|
80
|
-
wrapper.add_child(node)
|
81
|
-
end
|
82
|
-
self
|
83
|
-
end
|
84
|
-
|
85
|
-
def add_class(names)
|
86
|
-
each do |node|
|
87
|
-
next unless node.is_a?(Element)
|
88
|
-
current = (node["class"] || "").split(/\s+/)
|
89
|
-
new_classes = names.is_a?(Array) ? names : names.split(/\s+/)
|
90
|
-
node["class"] = (current + new_classes).uniq.join(" ")
|
91
|
-
end
|
92
|
-
self
|
93
|
-
end
|
94
|
-
|
95
|
-
def remove_class(names)
|
96
|
-
each do |node|
|
97
|
-
next unless node.is_a?(Element)
|
98
|
-
current = (node["class"] || "").split(/\s+/)
|
99
|
-
remove_classes = names.is_a?(Array) ? names : names.split(/\s+/)
|
100
|
-
node["class"] = (current - remove_classes).join(" ")
|
101
|
-
end
|
102
|
-
self
|
103
|
-
end
|
104
|
-
|
105
|
-
def attr(name, value = nil)
|
106
|
-
if value.nil?
|
107
|
-
first&.[](name)
|
108
|
-
else
|
109
|
-
each { |node| node[name] = value if node.is_a?(Element) }
|
110
|
-
self
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Collection operations
|
115
|
-
def +(other)
|
116
|
-
NodeSet.new(native_nodes + other.native_nodes)
|
117
|
-
end
|
118
|
-
|
119
|
-
def -(other)
|
120
|
-
NodeSet.new(native_nodes - other.native_nodes)
|
121
|
-
end
|
122
|
-
|
123
|
-
def &(other)
|
124
|
-
NodeSet.new(native_nodes & other.native_nodes)
|
125
|
-
end
|
126
|
-
|
127
|
-
def |(other)
|
128
|
-
NodeSet.new(native_nodes | other.native_nodes)
|
129
|
-
end
|
130
|
-
|
131
|
-
def uniq
|
132
|
-
NodeSet.new(native_nodes.uniq)
|
133
|
-
end
|
134
|
-
|
135
|
-
def reverse
|
136
|
-
NodeSet.new(native_nodes.reverse)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Search and filtering
|
140
|
-
def find_by_id(id)
|
141
|
-
detect { |node| node.is_a?(Element) && node["id"] == id }
|
142
|
-
end
|
143
|
-
|
144
|
-
def find_by_class(class_name)
|
145
|
-
select { |node| node.is_a?(Element) && (node["class"] || "").split(/\s+/).include?(class_name) }
|
146
|
-
end
|
147
|
-
|
148
|
-
def find_by_attribute(name, value = nil)
|
149
|
-
select do |node|
|
150
|
-
next unless node.is_a?(Element)
|
151
|
-
if value.nil?
|
152
|
-
node.attributes.key?(name)
|
153
|
-
else
|
154
|
-
node[name] == value
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def of_type(type)
|
160
|
-
select { |node| node.is_a?(type) }
|
161
|
-
end
|
162
|
-
|
163
|
-
# DOM Manipulation
|
164
|
-
def before(node_or_nodes)
|
165
|
-
each { |node| node.add_previous_sibling(node_or_nodes) }
|
166
|
-
self
|
167
|
-
end
|
168
|
-
|
169
|
-
def after(node_or_nodes)
|
170
|
-
each { |node| node.add_next_sibling(node_or_nodes) }
|
171
|
-
self
|
172
|
-
end
|
173
|
-
|
174
|
-
def replace_with(node_or_nodes)
|
175
|
-
each { |node| node.replace(node_or_nodes) }
|
176
|
-
self
|
177
|
-
end
|
178
|
-
|
179
|
-
def wrap_all(wrapper)
|
180
|
-
return self if empty?
|
181
|
-
|
182
|
-
wrapper_node = case wrapper
|
183
|
-
when String
|
184
|
-
Document.parse(wrapper).root
|
185
|
-
when Element
|
186
|
-
wrapper
|
187
|
-
else
|
188
|
-
raise ArgumentError, "Expected String or Element"
|
189
|
-
end
|
190
|
-
|
191
|
-
first.add_previous_sibling(wrapper_node)
|
192
|
-
wrapper_node.add_child(self)
|
193
|
-
self
|
194
|
-
end
|
195
|
-
|
196
|
-
# Content manipulation
|
197
|
-
def inner_text=(text)
|
198
|
-
each { |node| node.inner_text = text }
|
199
|
-
self
|
200
|
-
end
|
201
|
-
|
202
|
-
def inner_html=(html)
|
203
|
-
each { |node| node.inner_html = html }
|
204
|
-
self
|
205
|
-
end
|
206
|
-
|
207
|
-
# Attribute operations
|
208
|
-
def toggle_class(names)
|
209
|
-
names = names.split(/\s+/) if names.is_a?(String)
|
210
|
-
each do |node|
|
211
|
-
next unless node.is_a?(Element)
|
212
|
-
current = (node["class"] || "").split(/\s+/)
|
213
|
-
names.each do |name|
|
214
|
-
if current.include?(name)
|
215
|
-
current.delete(name)
|
216
|
-
else
|
217
|
-
current << name
|
218
|
-
end
|
219
|
-
end
|
220
|
-
node["class"] = current.uniq.join(" ")
|
221
|
-
end
|
222
|
-
self
|
223
|
-
end
|
224
|
-
|
225
|
-
def has_class?(name)
|
226
|
-
any? { |node| node.is_a?(Element) && (node["class"] || "").split(/\s+/).include?(name) }
|
227
|
-
end
|
228
|
-
|
229
|
-
def remove_attr(*attrs)
|
230
|
-
each do |node|
|
231
|
-
next unless node.is_a?(Element)
|
232
|
-
attrs.each { |attr| node.remove_attribute(attr) }
|
233
|
-
end
|
60
|
+
def remove
|
61
|
+
each(&:remove)
|
234
62
|
self
|
235
63
|
end
|
236
|
-
|
237
|
-
# Position and hierarchy
|
238
|
-
def parents
|
239
|
-
NodeSet.new(
|
240
|
-
map { |node| node.parent }.compact.uniq
|
241
|
-
)
|
242
|
-
end
|
243
|
-
|
244
|
-
def children
|
245
|
-
NodeSet.new(
|
246
|
-
flat_map { |node| node.children.to_a }
|
247
|
-
)
|
248
|
-
end
|
249
|
-
|
250
|
-
def siblings
|
251
|
-
NodeSet.new(
|
252
|
-
flat_map { |node| node.parent ? node.parent.children.reject { |sibling| sibling == node } : [] }
|
253
|
-
).uniq
|
254
|
-
end
|
255
|
-
|
256
|
-
def next
|
257
|
-
NodeSet.new(
|
258
|
-
map { |node| node.next_sibling }.compact
|
259
|
-
)
|
260
|
-
end
|
261
|
-
|
262
|
-
def previous
|
263
|
-
NodeSet.new(
|
264
|
-
map { |node| node.previous_sibling }.compact
|
265
|
-
)
|
266
|
-
end
|
267
64
|
end
|
268
65
|
end
|
@@ -1,44 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Moxml
|
2
4
|
class ProcessingInstruction < Node
|
3
|
-
def initialize(target_or_native = nil, content = nil)
|
4
|
-
case target_or_native
|
5
|
-
when String
|
6
|
-
super(adapter.create_processing_instruction(nil, target_or_native, content))
|
7
|
-
else
|
8
|
-
super(target_or_native)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
5
|
def target
|
13
|
-
adapter.processing_instruction_target(native)
|
6
|
+
adapter.processing_instruction_target(@native)
|
14
7
|
end
|
15
8
|
|
16
9
|
def target=(new_target)
|
17
|
-
adapter.
|
18
|
-
self
|
10
|
+
adapter.set_node_name(@native, new_target.to_s)
|
19
11
|
end
|
20
12
|
|
21
13
|
def content
|
22
|
-
adapter.processing_instruction_content(native)
|
14
|
+
adapter.processing_instruction_content(@native)
|
23
15
|
end
|
24
16
|
|
25
17
|
def content=(new_content)
|
26
|
-
adapter.set_processing_instruction_content(native, new_content)
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
|
-
def blank?
|
31
|
-
content.strip.empty?
|
18
|
+
adapter.set_processing_instruction_content(@native, new_content.to_s)
|
32
19
|
end
|
33
20
|
|
34
21
|
def processing_instruction?
|
35
22
|
true
|
36
23
|
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def create_native_node
|
41
|
-
adapter.create_processing_instruction(nil, "", "")
|
42
|
-
end
|
43
24
|
end
|
44
25
|
end
|
data/lib/moxml/text.rb
CHANGED
@@ -1,39 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Moxml
|
2
4
|
class Text < Node
|
3
|
-
def initialize(content_or_native = nil)
|
4
|
-
case content_or_native
|
5
|
-
when String
|
6
|
-
super(adapter.create_text(nil, content_or_native))
|
7
|
-
else
|
8
|
-
super(content_or_native)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
5
|
def content
|
13
|
-
adapter.text_content(native)
|
6
|
+
adapter.text_content(@native)
|
14
7
|
end
|
15
8
|
|
16
9
|
def content=(text)
|
17
|
-
adapter.set_text_content(native, text)
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
def blank?
|
22
|
-
content.strip.empty?
|
23
|
-
end
|
24
|
-
|
25
|
-
def cdata?
|
26
|
-
false
|
10
|
+
adapter.set_text_content(@native, normalize_xml_value(text))
|
27
11
|
end
|
28
12
|
|
29
13
|
def text?
|
30
14
|
true
|
31
15
|
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def create_native_node
|
36
|
-
adapter.create_text(nil, "")
|
37
|
-
end
|
38
16
|
end
|
39
17
|
end
|
data/lib/moxml/version.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moxml
|
4
|
+
module XmlUtils
|
5
|
+
class Encoder
|
6
|
+
attr_reader :mode
|
7
|
+
|
8
|
+
MAPPINGS = {
|
9
|
+
none: {},
|
10
|
+
basic: {
|
11
|
+
"<" => "<",
|
12
|
+
">" => ">",
|
13
|
+
"&" => "&"
|
14
|
+
},
|
15
|
+
quotes: {
|
16
|
+
"'" => "'",
|
17
|
+
'"' => """
|
18
|
+
},
|
19
|
+
full: {
|
20
|
+
"<" => "<",
|
21
|
+
">" => ">",
|
22
|
+
"'" => "'",
|
23
|
+
'"' => """,
|
24
|
+
"&" => "&"
|
25
|
+
}
|
26
|
+
}.freeze
|
27
|
+
MODES = MAPPINGS.keys.freeze
|
28
|
+
|
29
|
+
def initialize(text, mode = nil)
|
30
|
+
@text = text
|
31
|
+
@mode = valid_mode(mode)
|
32
|
+
end
|
33
|
+
|
34
|
+
def call
|
35
|
+
return @text if mode == :none
|
36
|
+
|
37
|
+
@text.to_s.gsub(/[#{mapping.keys.join}]/) do |match|
|
38
|
+
mapping[match]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def valid_mode(raw_mode)
|
45
|
+
mode_sym = raw_mode.to_s.to_sym
|
46
|
+
|
47
|
+
MODES.include?(mode_sym) ? mode_sym : MODES.first
|
48
|
+
end
|
49
|
+
|
50
|
+
def mapping
|
51
|
+
MAPPINGS[mode] || {}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|