moxml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "moxml"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,175 @@
1
+ # lib/moxml/adapter.rb
2
+ module Moxml
3
+ class Adapter
4
+ # Document operations
5
+ def parse(input, options = {})
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def serialize(node, options = {})
10
+ raise NotImplementedError
11
+ end
12
+
13
+ # Node type detection
14
+ def node_type(node)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # Node operations
19
+ def node_name(node)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def parent(node)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def children(node)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def next_sibling(node)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def previous_sibling(node)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def document(node)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def remove(node)
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def replace(node, new_node)
48
+ raise NotImplementedError
49
+ end
50
+
51
+ # Element operations
52
+ def create_element(document, name)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ def attributes(element)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def get_attribute(element, name)
61
+ raise NotImplementedError
62
+ end
63
+
64
+ def set_attribute(element, name, value)
65
+ raise NotImplementedError
66
+ end
67
+
68
+ def remove_attribute(element, name)
69
+ raise NotImplementedError
70
+ end
71
+
72
+ def add_child(element, child)
73
+ raise NotImplementedError
74
+ end
75
+
76
+ # Namespace operations
77
+ def namespaces(element)
78
+ raise NotImplementedError
79
+ end
80
+
81
+ def add_namespace(element, prefix, uri)
82
+ raise NotImplementedError
83
+ end
84
+
85
+ def namespace_prefix(namespace)
86
+ raise NotImplementedError
87
+ end
88
+
89
+ def namespace_uri(namespace)
90
+ raise NotImplementedError
91
+ end
92
+
93
+ # Attribute operations
94
+ def attribute_value(attribute)
95
+ raise NotImplementedError
96
+ end
97
+
98
+ def set_attribute_value(attribute, value)
99
+ raise NotImplementedError
100
+ end
101
+
102
+ def attribute_namespace(attribute)
103
+ raise NotImplementedError
104
+ end
105
+
106
+ # Text operations
107
+ def create_text(document, content)
108
+ raise NotImplementedError
109
+ end
110
+
111
+ def text_content(text)
112
+ raise NotImplementedError
113
+ end
114
+
115
+ def set_text_content(text, content)
116
+ raise NotImplementedError
117
+ end
118
+
119
+ # CDATA operations
120
+ def create_cdata(document, content)
121
+ raise NotImplementedError
122
+ end
123
+
124
+ # Comment operations
125
+ def create_comment(document, content)
126
+ raise NotImplementedError
127
+ end
128
+
129
+ def comment_content(comment)
130
+ raise NotImplementedError
131
+ end
132
+
133
+ def set_comment_content(comment, content)
134
+ raise NotImplementedError
135
+ end
136
+
137
+ # Processing instruction operations
138
+ def create_processing_instruction(document, target, content)
139
+ raise NotImplementedError
140
+ end
141
+
142
+ def processing_instruction_target(pi)
143
+ raise NotImplementedError
144
+ end
145
+
146
+ def processing_instruction_content(pi)
147
+ raise NotImplementedError
148
+ end
149
+
150
+ def set_processing_instruction_target(pi, target)
151
+ raise NotImplementedError
152
+ end
153
+
154
+ def set_processing_instruction_content(pi, content)
155
+ raise NotImplementedError
156
+ end
157
+
158
+ # Document specific operations
159
+ def root(document)
160
+ raise NotImplementedError
161
+ end
162
+
163
+ protected
164
+
165
+ def normalize_options(options)
166
+ {
167
+ encoding: options[:encoding] || "UTF-8",
168
+ indent: options[:indent] || 2,
169
+ xml_declaration: options.fetch(:xml_declaration, true),
170
+ pretty: options.fetch(:pretty, true),
171
+ namespace_declarations: options.fetch(:namespace_declarations, true),
172
+ }
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,30 @@
1
+ module Moxml
2
+ class Attribute < Node
3
+ def initialize(name_or_native = nil, value = nil)
4
+ case name_or_native
5
+ when String
6
+ super(adapter.create_attribute(nil, name_or_native, value))
7
+ else
8
+ super(name_or_native)
9
+ end
10
+ end
11
+
12
+ def name
13
+ adapter.attribute_name(native)
14
+ end
15
+
16
+ def value
17
+ adapter.attribute_value(native)
18
+ end
19
+
20
+ def value=(new_value)
21
+ adapter.set_attribute_value(native, new_value)
22
+ end
23
+
24
+ private
25
+
26
+ def create_native_node
27
+ adapter.create_attribute(nil, "", "")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ module Moxml
2
+ class Cdata < Node
3
+ def initialize(content_or_native = nil)
4
+ case content_or_native
5
+ when String
6
+ super(adapter.create_cdata(nil, content_or_native))
7
+ else
8
+ super(content_or_native)
9
+ end
10
+ end
11
+
12
+ def content
13
+ adapter.cdata_content(native)
14
+ end
15
+
16
+ def content=(text)
17
+ adapter.set_cdata_content(native, text)
18
+ self
19
+ end
20
+
21
+ def blank?
22
+ content.strip.empty?
23
+ end
24
+
25
+ def cdata?
26
+ true
27
+ end
28
+
29
+ def text?
30
+ false
31
+ end
32
+
33
+ private
34
+
35
+ def create_native_node
36
+ adapter.create_cdata(nil, "")
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ module Moxml
2
+ class Comment < Node
3
+ def initialize(content_or_native = nil)
4
+ case content_or_native
5
+ when String
6
+ super(adapter.create_comment(nil, content_or_native))
7
+ else
8
+ super(content_or_native)
9
+ end
10
+ end
11
+
12
+ def content
13
+ adapter.comment_content(native)
14
+ end
15
+
16
+ def content=(text)
17
+ adapter.set_comment_content(native, text)
18
+ self
19
+ end
20
+
21
+ def blank?
22
+ content.strip.empty?
23
+ end
24
+
25
+ def comment?
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ def create_native_node
32
+ adapter.create_comment(nil, "")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ # lib/moxml/config.rb
2
+ module Moxml
3
+ class Config
4
+ attr_accessor :backend, :huge_document,
5
+ :default_encoding,
6
+ :default_indent,
7
+ :cdata_sections,
8
+ :cdata_patterns,
9
+ :strict_parsing,
10
+ :entity_encoding
11
+
12
+ def initialize
13
+ @backend = :nokogiri
14
+ @huge_document = false
15
+ @default_encoding = "UTF-8"
16
+ @default_indent = 2
17
+ @cdata_sections = true
18
+ @cdata_patterns = ["script", "style"]
19
+ @strict_parsing = true
20
+ @entity_encoding = :basic
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ # lib/moxml/declaration.rb
2
+ module Moxml
3
+ class Declaration < Node
4
+ def initialize(version_or_native = "1.0", encoding = "UTF-8", standalone = nil)
5
+ case version_or_native
6
+ when String
7
+ super(adapter.create_declaration(nil, version_or_native, encoding, standalone))
8
+ else
9
+ super(version_or_native)
10
+ end
11
+ end
12
+
13
+ def version
14
+ adapter.declaration_version(native)
15
+ end
16
+
17
+ def version=(new_version)
18
+ adapter.set_declaration_version(native, new_version)
19
+ self
20
+ end
21
+
22
+ def encoding
23
+ adapter.declaration_encoding(native)
24
+ end
25
+
26
+ def encoding=(new_encoding)
27
+ adapter.set_declaration_encoding(native, new_encoding)
28
+ self
29
+ end
30
+
31
+ def standalone
32
+ adapter.declaration_standalone(native)
33
+ end
34
+
35
+ def standalone=(new_standalone)
36
+ adapter.set_declaration_standalone(native, new_standalone)
37
+ self
38
+ end
39
+
40
+ def to_xml
41
+ adapter.serialize_declaration(native)
42
+ end
43
+
44
+ private
45
+
46
+ def create_native_node
47
+ adapter.create_declaration(nil, "1.0", "UTF-8", nil)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,99 @@
1
+ module Moxml
2
+ class Document < Node
3
+ def self.parse(input, options = {})
4
+ new(Moxml.adapter.parse(input, options))
5
+ end
6
+
7
+ def root
8
+ wrap_node(adapter.root(native))
9
+ end
10
+
11
+ def create_element(name)
12
+ Element.new(adapter.create_element(native, name))
13
+ end
14
+
15
+ def create_text(content)
16
+ Text.new(adapter.create_text(native, content))
17
+ end
18
+
19
+ def create_cdata(content)
20
+ Cdata.new(adapter.create_cdata(native, content))
21
+ end
22
+
23
+ def create_comment(content)
24
+ Comment.new(adapter.create_comment(native, content))
25
+ end
26
+
27
+ def create_processing_instruction(target, content)
28
+ ProcessingInstruction.new(
29
+ adapter.create_processing_instruction(native, target, content)
30
+ )
31
+ end
32
+
33
+ def to_xml(options = {})
34
+ adapter.serialize(native, options)
35
+ end
36
+
37
+ def encoding
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
55
+ end
56
+
57
+ def standalone=(standalone)
58
+ (declaration || add_declaration).standalone = standalone
59
+ end
60
+
61
+ def declaration
62
+ children.find { |node| node.is_a?(Declaration) }
63
+ end
64
+
65
+ def add_declaration(version = "1.0", encoding = "UTF-8", standalone = nil)
66
+ decl = Declaration.new(version, encoding, standalone)
67
+ if declaration
68
+ declaration.replace(decl)
69
+ else
70
+ add_child(decl)
71
+ end
72
+ decl
73
+ end
74
+
75
+ def css(selector)
76
+ NodeSet.new(adapter.css(native, selector))
77
+ end
78
+
79
+ def xpath(expression, namespaces = {})
80
+ NodeSet.new(adapter.xpath(native, expression, namespaces))
81
+ end
82
+
83
+ def at_css(selector)
84
+ node = adapter.at_css(native, selector)
85
+ node.nil? ? nil : wrap_node(node)
86
+ end
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
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,145 @@
1
+ module Moxml
2
+ 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
+ def name
13
+ adapter.node_name(native)
14
+ end
15
+
16
+ def attributes
17
+ adapter.attributes(native).transform_values { |attr| Attribute.new(attr) }
18
+ end
19
+
20
+ def []=(name, value)
21
+ adapter.set_attribute(native, name, value)
22
+ end
23
+
24
+ def [](name)
25
+ attr = adapter.get_attribute(native, name)
26
+ attr.nil? ? nil : Attribute.new(attr)
27
+ end
28
+
29
+ def add_child(node)
30
+ adapter.add_child(native, node.native)
31
+ self
32
+ end
33
+
34
+ def namespace
35
+ ns = adapter.namespace(native)
36
+ ns.nil? ? nil : Namespace.new(ns)
37
+ end
38
+
39
+ def namespace=(ns)
40
+ adapter.set_namespace(native, ns&.native)
41
+ self
42
+ end
43
+
44
+ def namespaces
45
+ adapter.namespaces(native).transform_values { |ns| Namespace.new(ns) }
46
+ end
47
+
48
+ def css(selector)
49
+ NodeSet.new(adapter.css(native, selector))
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
76
+ end
77
+
78
+ def matches?(selector)
79
+ adapter.matches?(native, selector)
80
+ end
81
+
82
+ def ancestors
83
+ NodeSet.new(adapter.ancestors(native))
84
+ end
85
+
86
+ def descendants
87
+ NodeSet.new(adapter.descendants(native))
88
+ end
89
+
90
+ def previous_elements
91
+ NodeSet.new(adapter.previous_elements(native))
92
+ end
93
+
94
+ def next_elements
95
+ NodeSet.new(adapter.next_elements(native))
96
+ end
97
+
98
+ 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
105
+ end
106
+
107
+ def key?(name)
108
+ adapter.has_attribute?(native, name)
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(" ")
119
+ self
120
+ end
121
+
122
+ def remove_class(*names)
123
+ self["class"] = (classes - names).join(" ")
124
+ self
125
+ 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
+ end
145
+ end
@@ -0,0 +1,77 @@
1
+ # lib/moxml/error_handler.rb
2
+ module Moxml
3
+ class ErrorHandler
4
+ class << self
5
+ def handle_parse_error(error, backend)
6
+ case backend
7
+ when :nokogiri
8
+ handle_nokogiri_error(error)
9
+ when :ox
10
+ handle_ox_error(error)
11
+ when :oga
12
+ handle_oga_error(error)
13
+ else
14
+ handle_generic_error(error)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def handle_nokogiri_error(error)
21
+ case error
22
+ when ::Nokogiri::XML::SyntaxError
23
+ raise ParseError.new(
24
+ error.message,
25
+ line: error.line,
26
+ column: error.column,
27
+ source: error.source,
28
+ )
29
+ when ::Nokogiri::XML::XPath::SyntaxError
30
+ raise XPathError.new(error.message)
31
+ else
32
+ handle_generic_error(error)
33
+ end
34
+ end
35
+
36
+ def handle_ox_error(error)
37
+ case error
38
+ when ::Ox::ParseError
39
+ raise ParseError.new(
40
+ error.message,
41
+ line: error.line,
42
+ column: error.column,
43
+ )
44
+ else
45
+ handle_generic_error(error)
46
+ end
47
+ end
48
+
49
+ def handle_oga_error(error)
50
+ case error
51
+ when ::Oga::XML::ParseError
52
+ raise ParseError.new(
53
+ error.message,
54
+ line: error.line,
55
+ column: error.column,
56
+ )
57
+ when ::Oga::XML::XPath::Error
58
+ raise XPathError.new(error.message)
59
+ else
60
+ handle_generic_error(error)
61
+ end
62
+ end
63
+
64
+ def handle_generic_error(error)
65
+ case error
66
+ when NameError, NoMethodError
67
+ raise BackendError.new(
68
+ "Backend operation failed: #{error.message}",
69
+ Moxml.config.backend
70
+ )
71
+ else
72
+ raise Error, "XML operation failed: #{error.message}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end