moxml 0.1.0 → 0.1.2
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 +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
|