moxml 0.1.0

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.
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