moxml 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 +401 -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 +319 -0
- data/lib/moxml/adapter/oga.rb +318 -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 +63 -97
- 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 +130 -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/document.rb
CHANGED
@@ -1,99 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
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
|
+
|
1
13
|
module Moxml
|
2
14
|
class Document < Node
|
3
|
-
def
|
4
|
-
|
15
|
+
def root=(element)
|
16
|
+
adapter.set_root(@native, element.native)
|
5
17
|
end
|
6
18
|
|
7
19
|
def root
|
8
|
-
|
20
|
+
root_element = adapter.root(@native)
|
21
|
+
root_element ? Element.wrap(root_element, context) : nil
|
9
22
|
end
|
10
23
|
|
11
24
|
def create_element(name)
|
12
|
-
Element.new(adapter.create_element(
|
25
|
+
Element.new(adapter.create_element(name), context)
|
13
26
|
end
|
14
27
|
|
15
28
|
def create_text(content)
|
16
|
-
Text.new(adapter.create_text(
|
29
|
+
Text.new(adapter.create_text(content), context)
|
17
30
|
end
|
18
31
|
|
19
32
|
def create_cdata(content)
|
20
|
-
Cdata.new(adapter.create_cdata(
|
33
|
+
Cdata.new(adapter.create_cdata(content), context)
|
21
34
|
end
|
22
35
|
|
23
36
|
def create_comment(content)
|
24
|
-
Comment.new(adapter.create_comment(
|
37
|
+
Comment.new(adapter.create_comment(content), context)
|
25
38
|
end
|
26
39
|
|
27
|
-
def
|
28
|
-
|
29
|
-
adapter.
|
40
|
+
def create_doctype(name, external_id, system_id)
|
41
|
+
Doctype.new(
|
42
|
+
adapter.create_doctype(name, external_id, system_id),
|
43
|
+
context
|
30
44
|
)
|
31
45
|
end
|
32
46
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
declaration&.encoding
|
39
|
-
end
|
40
|
-
|
41
|
-
def encoding=(encoding)
|
42
|
-
(declaration || add_declaration).encoding = encoding
|
43
|
-
end
|
44
|
-
|
45
|
-
def version
|
46
|
-
declaration&.version
|
47
|
-
end
|
48
|
-
|
49
|
-
def version=(version)
|
50
|
-
(declaration || add_declaration).version = version
|
51
|
-
end
|
52
|
-
|
53
|
-
def standalone
|
54
|
-
declaration&.standalone
|
47
|
+
def create_processing_instruction(target, content)
|
48
|
+
ProcessingInstruction.new(
|
49
|
+
adapter.create_processing_instruction(target, content),
|
50
|
+
context
|
51
|
+
)
|
55
52
|
end
|
56
53
|
|
57
|
-
def standalone=
|
58
|
-
|
54
|
+
def create_declaration(version = "1.0", encoding = "UTF-8", standalone = nil)
|
55
|
+
decl = adapter.create_declaration(version, encoding, standalone)
|
56
|
+
Declaration.new(decl, context)
|
59
57
|
end
|
60
58
|
|
61
|
-
def
|
62
|
-
|
63
|
-
end
|
59
|
+
def add_child(node)
|
60
|
+
node = prepare_node(node)
|
64
61
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
if node.is_a?(Declaration)
|
63
|
+
if children.empty?
|
64
|
+
adapter.add_child(@native, node.native)
|
65
|
+
else
|
66
|
+
adapter.add_previous_sibling(children.first.native, node.native)
|
67
|
+
end
|
68
|
+
elsif root && !node.is_a?(ProcessingInstruction) && !node.is_a?(Comment)
|
69
|
+
raise Error, "Document already has a root element"
|
69
70
|
else
|
70
|
-
add_child(
|
71
|
+
adapter.add_child(@native, node.native)
|
71
72
|
end
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
def css(selector)
|
76
|
-
NodeSet.new(adapter.css(native, selector))
|
73
|
+
self
|
77
74
|
end
|
78
75
|
|
79
|
-
def xpath(expression, namespaces =
|
80
|
-
|
76
|
+
def xpath(expression, namespaces = nil)
|
77
|
+
native_nodes = adapter.xpath(@native, expression, namespaces)
|
78
|
+
NodeSet.new(native_nodes, context)
|
81
79
|
end
|
82
80
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
def at_xpath(expression, namespaces = {})
|
89
|
-
node = adapter.at_xpath(native, expression, namespaces)
|
90
|
-
node.nil? ? nil : wrap_node(node)
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
def create_native_node
|
96
|
-
adapter.create_document
|
81
|
+
def at_xpath(expression, namespaces = nil)
|
82
|
+
if (native_node = adapter.at_xpath(@native, expression, namespaces))
|
83
|
+
Node.wrap(native_node, context)
|
84
|
+
end
|
97
85
|
end
|
98
86
|
end
|
99
87
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moxml
|
4
|
+
class DocumentBuilder
|
5
|
+
attr_reader :context
|
6
|
+
|
7
|
+
def initialize(context)
|
8
|
+
@context = context
|
9
|
+
@node_stack = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(native_doc)
|
13
|
+
@current_doc = context.create_document
|
14
|
+
visit_node(native_doc)
|
15
|
+
@current_doc
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def visit_node(node)
|
21
|
+
node.respond_to?(:name) ? node.name : node
|
22
|
+
method_name = "visit_#{node_type(node)}"
|
23
|
+
return unless respond_to?(method_name, true)
|
24
|
+
|
25
|
+
send(method_name, node)
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_document(doc)
|
29
|
+
@node_stack.push(@current_doc)
|
30
|
+
visit_children(doc)
|
31
|
+
@node_stack.pop
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_element(node)
|
35
|
+
element = Element.new(node, context)
|
36
|
+
if @node_stack.empty?
|
37
|
+
# For root element, we need to set it directly
|
38
|
+
adapter.set_root(@current_doc.native, element.native)
|
39
|
+
else
|
40
|
+
@node_stack.last.add_child(element)
|
41
|
+
end
|
42
|
+
@node_stack.push(element)
|
43
|
+
visit_children(node)
|
44
|
+
@node_stack.pop
|
45
|
+
element
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit_text(node)
|
49
|
+
@node_stack.last.add_child(Text.new(node, context)) if @node_stack.any?
|
50
|
+
end
|
51
|
+
|
52
|
+
def visit_cdata(node)
|
53
|
+
@node_stack.last.add_child(Cdata.new(node, context)) if @node_stack.any?
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_comment(node)
|
57
|
+
@node_stack.last.add_child(Comment.new(node, context)) if @node_stack.any?
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_processing_instruction(node)
|
61
|
+
@node_stack.last.add_child(ProcessingInstruction.new(node, context)) if @node_stack.any?
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_doctype(node)
|
65
|
+
@node_stack.last.add_child(Doctype.new(node, context)) if @node_stack.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit_children(node)
|
69
|
+
node_children = children(node).dup
|
70
|
+
node_children.each do |child|
|
71
|
+
visit_node(child)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def node_type(node)
|
76
|
+
context.config.adapter.node_type(node)
|
77
|
+
end
|
78
|
+
|
79
|
+
def children(node)
|
80
|
+
context.config.adapter.children(node)
|
81
|
+
end
|
82
|
+
|
83
|
+
def adapter
|
84
|
+
context.config.adapter
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/moxml/element.rb
CHANGED
@@ -1,145 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "attribute"
|
4
|
+
require_relative "namespace"
|
5
|
+
|
1
6
|
module Moxml
|
2
7
|
class Element < Node
|
3
|
-
def initialize(name_or_native = nil)
|
4
|
-
case name_or_native
|
5
|
-
when String
|
6
|
-
super(adapter.create_element(nil, name_or_native))
|
7
|
-
else
|
8
|
-
super(name_or_native)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
8
|
def name
|
13
|
-
adapter.node_name(native)
|
9
|
+
adapter.node_name(@native)
|
14
10
|
end
|
15
11
|
|
16
|
-
def
|
17
|
-
adapter.
|
12
|
+
def name=(value)
|
13
|
+
adapter.set_node_name(@native, value)
|
18
14
|
end
|
19
15
|
|
20
16
|
def []=(name, value)
|
21
|
-
adapter.set_attribute(native, name, value)
|
17
|
+
adapter.set_attribute(@native, name, normalize_xml_value(value))
|
22
18
|
end
|
23
19
|
|
24
20
|
def [](name)
|
25
|
-
|
26
|
-
attr.nil? ? nil : Attribute.new(attr)
|
21
|
+
adapter.get_attribute_value(@native, name)
|
27
22
|
end
|
28
23
|
|
29
|
-
def
|
30
|
-
adapter.
|
31
|
-
|
24
|
+
def attribute(name)
|
25
|
+
native_attr = adapter.get_attribute(@native, name)
|
26
|
+
native_attr && Attribute.new(native_attr, context)
|
32
27
|
end
|
33
28
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
29
|
+
def attributes
|
30
|
+
adapter.attributes(@native).map do |attr|
|
31
|
+
Attribute.new(attr, context)
|
32
|
+
end
|
37
33
|
end
|
38
34
|
|
39
|
-
def
|
40
|
-
adapter.
|
35
|
+
def remove_attribute(name)
|
36
|
+
adapter.remove_attribute(@native, name)
|
41
37
|
self
|
42
38
|
end
|
43
39
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def xpath(expression, namespaces = {})
|
53
|
-
NodeSet.new(adapter.xpath(native, expression, namespaces))
|
54
|
-
end
|
55
|
-
|
56
|
-
def at_css(selector)
|
57
|
-
node = adapter.at_css(native, selector)
|
58
|
-
node.nil? ? nil : wrap_node(node)
|
59
|
-
end
|
60
|
-
|
61
|
-
def at_xpath(expression, namespaces = {})
|
62
|
-
node = adapter.at_xpath(native, expression, namespaces)
|
63
|
-
node.nil? ? nil : wrap_node(node)
|
64
|
-
end
|
65
|
-
|
66
|
-
def blank?
|
67
|
-
text.strip.empty? && children.empty?
|
68
|
-
end
|
69
|
-
|
70
|
-
def value
|
71
|
-
text.strip
|
72
|
-
end
|
73
|
-
|
74
|
-
def value=(val)
|
75
|
-
self.text = val.to_s
|
40
|
+
def add_namespace(prefix, uri)
|
41
|
+
validate_uri(uri)
|
42
|
+
adapter.create_native_namespace(@native, prefix, uri)
|
43
|
+
self
|
44
|
+
rescue ValidationError => e
|
45
|
+
raise Moxml::NamespaceError, e.message
|
76
46
|
end
|
47
|
+
alias add_namespace_definition add_namespace
|
77
48
|
|
78
|
-
|
79
|
-
|
49
|
+
# it's NOT the same as namespaces.first
|
50
|
+
def namespace
|
51
|
+
ns = adapter.namespace(@native)
|
52
|
+
ns && Namespace.new(ns, context)
|
80
53
|
end
|
81
54
|
|
82
|
-
|
83
|
-
|
55
|
+
# it does NOT change the list of namespace definitions
|
56
|
+
def namespace=(ns_or_hash)
|
57
|
+
if ns_or_hash.is_a?(Hash)
|
58
|
+
adapter.set_namespace(
|
59
|
+
@native,
|
60
|
+
adapter.create_namespace(@native, *ns_or_hash.to_a.first)
|
61
|
+
)
|
62
|
+
else
|
63
|
+
adapter.set_namespace(@native, ns_or_hash&.native)
|
64
|
+
end
|
84
65
|
end
|
85
66
|
|
86
|
-
def
|
87
|
-
|
67
|
+
def namespaces
|
68
|
+
adapter.namespace_definitions(@native).map do |ns|
|
69
|
+
Namespace.new(ns, context)
|
70
|
+
end
|
88
71
|
end
|
72
|
+
alias namespace_definitions namespaces
|
89
73
|
|
90
|
-
def
|
91
|
-
|
74
|
+
def text
|
75
|
+
adapter.text_content(@native)
|
92
76
|
end
|
93
77
|
|
94
|
-
def
|
95
|
-
|
78
|
+
def text=(content)
|
79
|
+
adapter.set_text_content(@native, normalize_xml_value(content))
|
96
80
|
end
|
97
81
|
|
98
82
|
def inner_text
|
99
|
-
adapter.inner_text(native)
|
100
|
-
end
|
101
|
-
|
102
|
-
def inner_text=(text)
|
103
|
-
adapter.set_inner_text(native, text)
|
104
|
-
self
|
83
|
+
adapter.inner_text(@native)
|
105
84
|
end
|
106
85
|
|
107
|
-
def
|
108
|
-
adapter.
|
86
|
+
def inner_html
|
87
|
+
adapter.inner_html(@native)
|
109
88
|
end
|
110
89
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
(self["class"] || "").split(/\s+/)
|
90
|
+
def inner_html=(html)
|
91
|
+
doc = context.parse("<root>#{html}</root>")
|
92
|
+
adapter.replace_children(@native, doc.root.children.map(&:native))
|
115
93
|
end
|
116
94
|
|
117
|
-
|
118
|
-
|
95
|
+
# Fluent interface methods
|
96
|
+
def with_attribute(name, value)
|
97
|
+
self[name] = value
|
119
98
|
self
|
120
99
|
end
|
121
100
|
|
122
|
-
def
|
123
|
-
|
101
|
+
def with_namespace(prefix, uri)
|
102
|
+
add_namespace(prefix, uri)
|
124
103
|
self
|
125
104
|
end
|
126
105
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
add_class(name)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def has_class?(name)
|
136
|
-
classes.include?(name)
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
def create_native_node
|
142
|
-
adapter.create_element(nil, "")
|
106
|
+
def with_text(content)
|
107
|
+
self.text = content
|
108
|
+
self
|
143
109
|
end
|
144
110
|
end
|
145
111
|
end
|
data/lib/moxml/error.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moxml
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ParseError < Error
|
7
|
+
attr_reader :line, :column
|
8
|
+
|
9
|
+
def initialize(message, line: nil, column: nil)
|
10
|
+
@line = line
|
11
|
+
@column = column
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ValidationError < Error; end
|
17
|
+
class XPathError < Error; end
|
18
|
+
class NamespaceError < Error; end
|
19
|
+
class AdapterError < Error; end
|
20
|
+
end
|
data/lib/moxml/namespace.rb
CHANGED
@@ -1,54 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Moxml
|
2
4
|
class Namespace < Node
|
3
|
-
def initialize(prefix_or_native = nil, uri = nil)
|
4
|
-
case prefix_or_native
|
5
|
-
when String
|
6
|
-
super(adapter.create_namespace(nil, prefix_or_native, uri))
|
7
|
-
else
|
8
|
-
super(prefix_or_native)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
5
|
def prefix
|
13
|
-
adapter.namespace_prefix(native)
|
14
|
-
end
|
15
|
-
|
16
|
-
def prefix=(new_prefix)
|
17
|
-
adapter.set_namespace_prefix(native, new_prefix)
|
18
|
-
self
|
6
|
+
adapter.namespace_prefix(@native)
|
19
7
|
end
|
20
8
|
|
21
9
|
def uri
|
22
|
-
adapter.namespace_uri(native)
|
23
|
-
end
|
24
|
-
|
25
|
-
def uri=(new_uri)
|
26
|
-
adapter.set_namespace_uri(native, new_uri)
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
|
-
def blank?
|
31
|
-
uri.nil? || uri.empty?
|
32
|
-
end
|
33
|
-
|
34
|
-
def namespace?
|
35
|
-
true
|
10
|
+
adapter.namespace_uri(@native)
|
36
11
|
end
|
37
12
|
|
38
13
|
def ==(other)
|
39
|
-
other.is_a?(Namespace) &&
|
40
|
-
other.prefix == prefix &&
|
41
|
-
other.uri == uri
|
14
|
+
other.is_a?(Namespace) && prefix == other.prefix && uri == other.uri
|
42
15
|
end
|
43
16
|
|
44
17
|
def to_s
|
45
|
-
|
18
|
+
if prefix
|
19
|
+
%(xmlns:#{prefix}="#{uri}")
|
20
|
+
else
|
21
|
+
%(xmlns="#{uri}")
|
22
|
+
end
|
46
23
|
end
|
47
24
|
|
48
|
-
|
49
|
-
|
50
|
-
def create_native_node
|
51
|
-
adapter.create_namespace(nil, "", "")
|
25
|
+
def namespace?
|
26
|
+
true
|
52
27
|
end
|
53
28
|
end
|
54
29
|
end
|