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/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,107 @@
|
|
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
|
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
|
72
46
|
end
|
47
|
+
alias add_namespace_definition add_namespace
|
73
48
|
|
74
|
-
|
75
|
-
|
49
|
+
# it's NOT the same as namespaces.first
|
50
|
+
def namespace
|
51
|
+
ns = adapter.namespace(@native)
|
52
|
+
ns && Namespace.new(ns, context)
|
76
53
|
end
|
77
54
|
|
78
|
-
|
79
|
-
|
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
|
80
65
|
end
|
81
66
|
|
82
|
-
def
|
83
|
-
|
67
|
+
def namespaces
|
68
|
+
adapter.namespace_definitions(@native).map do |ns|
|
69
|
+
Namespace.new(ns, context)
|
70
|
+
end
|
84
71
|
end
|
72
|
+
alias namespace_definitions namespaces
|
85
73
|
|
86
|
-
def
|
87
|
-
|
74
|
+
def text
|
75
|
+
adapter.text_content(@native)
|
88
76
|
end
|
89
77
|
|
90
|
-
def
|
91
|
-
|
78
|
+
def text=(content)
|
79
|
+
adapter.set_text_content(@native, normalize_xml_value(content))
|
92
80
|
end
|
93
81
|
|
94
|
-
def
|
95
|
-
|
82
|
+
def inner_html
|
83
|
+
adapter.inner_html(@native)
|
96
84
|
end
|
97
85
|
|
98
|
-
def
|
99
|
-
|
86
|
+
def inner_html=(html)
|
87
|
+
doc = context.parse("<root>#{html}</root>")
|
88
|
+
adapter.replace_children(@native, doc.root.children.map(&:native))
|
100
89
|
end
|
101
90
|
|
102
|
-
|
103
|
-
|
91
|
+
# Fluent interface methods
|
92
|
+
def with_attribute(name, value)
|
93
|
+
self[name] = value
|
104
94
|
self
|
105
95
|
end
|
106
96
|
|
107
|
-
def
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
alias has_attribute? key?
|
112
|
-
|
113
|
-
def classes
|
114
|
-
(self["class"] || "").split(/\s+/)
|
115
|
-
end
|
116
|
-
|
117
|
-
def add_class(*names)
|
118
|
-
self["class"] = (classes + names).uniq.join(" ")
|
97
|
+
def with_namespace(prefix, uri)
|
98
|
+
add_namespace(prefix, uri)
|
119
99
|
self
|
120
100
|
end
|
121
101
|
|
122
|
-
def
|
123
|
-
self
|
102
|
+
def with_text(content)
|
103
|
+
self.text = content
|
124
104
|
self
|
125
105
|
end
|
126
|
-
|
127
|
-
def toggle_class(name)
|
128
|
-
if classes.include?(name)
|
129
|
-
remove_class(name)
|
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, "")
|
143
|
-
end
|
144
106
|
end
|
145
107
|
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
|