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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +15 -0
  3. data/.github/workflows/release.yml +23 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +2 -0
  6. data/.rubocop_todo.yml +65 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +10 -3
  9. data/README.adoc +400 -594
  10. data/lib/moxml/adapter/base.rb +102 -0
  11. data/lib/moxml/adapter/customized_oga/xml_declaration.rb +18 -0
  12. data/lib/moxml/adapter/customized_oga/xml_generator.rb +104 -0
  13. data/lib/moxml/adapter/nokogiri.rb +314 -0
  14. data/lib/moxml/adapter/oga.rb +309 -0
  15. data/lib/moxml/adapter/ox.rb +325 -0
  16. data/lib/moxml/adapter.rb +26 -170
  17. data/lib/moxml/attribute.rb +47 -14
  18. data/lib/moxml/builder.rb +64 -0
  19. data/lib/moxml/cdata.rb +4 -26
  20. data/lib/moxml/comment.rb +6 -22
  21. data/lib/moxml/config.rb +39 -15
  22. data/lib/moxml/context.rb +29 -0
  23. data/lib/moxml/declaration.rb +16 -26
  24. data/lib/moxml/doctype.rb +9 -0
  25. data/lib/moxml/document.rb +51 -63
  26. data/lib/moxml/document_builder.rb +87 -0
  27. data/lib/moxml/element.rb +61 -99
  28. data/lib/moxml/error.rb +20 -0
  29. data/lib/moxml/namespace.rb +12 -37
  30. data/lib/moxml/node.rb +78 -58
  31. data/lib/moxml/node_set.rb +19 -222
  32. data/lib/moxml/processing_instruction.rb +6 -25
  33. data/lib/moxml/text.rb +4 -26
  34. data/lib/moxml/version.rb +1 -1
  35. data/lib/moxml/xml_utils/encoder.rb +55 -0
  36. data/lib/moxml/xml_utils.rb +80 -0
  37. data/lib/moxml.rb +33 -33
  38. data/moxml.gemspec +1 -1
  39. data/spec/moxml/adapter/nokogiri_spec.rb +14 -0
  40. data/spec/moxml/adapter/oga_spec.rb +14 -0
  41. data/spec/moxml/adapter/ox_spec.rb +49 -0
  42. data/spec/moxml/all_with_adapters_spec.rb +46 -0
  43. data/spec/moxml/config_spec.rb +55 -0
  44. data/spec/moxml/error_spec.rb +71 -0
  45. data/spec/moxml/examples/adapter_spec.rb +27 -0
  46. data/spec/moxml_spec.rb +50 -0
  47. data/spec/spec_helper.rb +32 -0
  48. data/spec/support/shared_examples/attribute.rb +165 -0
  49. data/spec/support/shared_examples/builder.rb +25 -0
  50. data/spec/support/shared_examples/cdata.rb +70 -0
  51. data/spec/support/shared_examples/comment.rb +65 -0
  52. data/spec/support/shared_examples/context.rb +35 -0
  53. data/spec/support/shared_examples/declaration.rb +93 -0
  54. data/spec/support/shared_examples/doctype.rb +25 -0
  55. data/spec/support/shared_examples/document.rb +110 -0
  56. data/spec/support/shared_examples/document_builder.rb +43 -0
  57. data/spec/support/shared_examples/edge_cases.rb +185 -0
  58. data/spec/support/shared_examples/element.rb +110 -0
  59. data/spec/support/shared_examples/examples/attribute.rb +42 -0
  60. data/spec/support/shared_examples/examples/basic_usage.rb +67 -0
  61. data/spec/support/shared_examples/examples/memory.rb +54 -0
  62. data/spec/support/shared_examples/examples/namespace.rb +65 -0
  63. data/spec/support/shared_examples/examples/readme_examples.rb +100 -0
  64. data/spec/support/shared_examples/examples/thread_safety.rb +43 -0
  65. data/spec/support/shared_examples/examples/xpath.rb +39 -0
  66. data/spec/support/shared_examples/integration.rb +135 -0
  67. data/spec/support/shared_examples/namespace.rb +96 -0
  68. data/spec/support/shared_examples/node.rb +110 -0
  69. data/spec/support/shared_examples/node_set.rb +90 -0
  70. data/spec/support/shared_examples/processing_instruction.rb +88 -0
  71. data/spec/support/shared_examples/text.rb +66 -0
  72. data/spec/support/shared_examples/xml_adapter.rb +191 -0
  73. data/spec/support/xml_matchers.rb +27 -0
  74. metadata +55 -6
  75. data/.github/workflows/main.yml +0 -27
  76. data/lib/moxml/error_handler.rb +0 -77
  77. data/lib/moxml/errors.rb +0 -169
data/lib/moxml/adapter.rb CHANGED
@@ -1,175 +1,31 @@
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
1
+ # frozen_string_literal: true
55
2
 
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
3
+ require_relative "adapter/base"
67
4
 
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
- }
5
+ module Moxml
6
+ module Adapter
7
+ AVALIABLE_ADAPTERS = %i[nokogiri oga].freeze # ox to be added later
8
+
9
+ class << self
10
+ def load(name)
11
+ require_adapter(name)
12
+ const_get(name.to_s.capitalize)
13
+ rescue LoadError => e
14
+ raise LoadError, "Could not load #{name} adapter. Please ensure the #{name} gem is available: #{e.message}"
15
+ end
16
+
17
+ private
18
+
19
+ def require_adapter(name)
20
+ require "#{__dir__}/adapter/#{name}"
21
+ rescue LoadError
22
+ begin
23
+ require name.to_s
24
+ require "#{__dir__}/adapter/#{name}"
25
+ rescue LoadError => e
26
+ raise LoadError, "Failed to load #{name} adapter: #{e.message}"
27
+ end
28
+ end
173
29
  end
174
30
  end
175
31
  end
@@ -1,30 +1,63 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moxml
2
4
  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
5
+ def name
6
+ @native.name
10
7
  end
11
8
 
12
- def name
13
- adapter.attribute_name(native)
9
+ def name=(new_name)
10
+ @native.name = new_name
14
11
  end
15
12
 
16
13
  def value
17
- adapter.attribute_value(native)
14
+ @native.value
18
15
  end
19
16
 
20
17
  def value=(new_value)
21
- adapter.set_attribute_value(native, new_value)
18
+ @native.value = normalize_xml_value(new_value)
19
+ end
20
+
21
+ def namespace
22
+ ns = adapter.namespace(@native)
23
+ ns && Namespace.new(ns, context)
24
+ end
25
+
26
+ def namespace=(ns)
27
+ adapter.set_namespace(@native, ns&.native)
28
+ end
29
+
30
+ def element
31
+ adapter.attribute_element(@native)
32
+ end
33
+
34
+ def remove
35
+ adapter.remove_attribute(element, name)
36
+ self
37
+ end
38
+
39
+ def ==(other)
40
+ return false unless other.is_a?(Attribute)
41
+
42
+ name == other.name && value == other.value && namespace == other.namespace
43
+ end
44
+
45
+ def to_s
46
+ if namespace&.prefix
47
+ "#{namespace.prefix}:#{name}=\"#{value}\""
48
+ else
49
+ "#{name}=\"#{value}\""
50
+ end
51
+ end
52
+
53
+ def attribute?
54
+ true
22
55
  end
23
56
 
24
- private
57
+ protected
25
58
 
26
- def create_native_node
27
- adapter.create_attribute(nil, "", "")
59
+ def adapter
60
+ context.config.adapter
28
61
  end
29
62
  end
30
63
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ class Builder
5
+ def initialize(context)
6
+ @context = context
7
+ @current = @document = context.create_document
8
+ @namespaces = {}
9
+ end
10
+
11
+ def build(&block)
12
+ instance_eval(&block)
13
+ @document
14
+ end
15
+
16
+ def declaration(version: "1.0", encoding: "UTF-8", standalone: nil)
17
+ @current.add_child(
18
+ @document.create_declaration(version, encoding, standalone)
19
+ )
20
+ end
21
+
22
+ def element(name, attributes = {}, &block)
23
+ el = @document.create_element(name)
24
+
25
+ attributes.each do |key, value|
26
+ el[key] = value
27
+ end
28
+
29
+ @current.add_child(el)
30
+
31
+ if block_given?
32
+ previous = @current
33
+ @current = el
34
+ instance_eval(&block)
35
+ @current = previous
36
+ end
37
+
38
+ el
39
+ end
40
+
41
+ def text(content)
42
+ @current.add_child(@document.create_text(content))
43
+ end
44
+
45
+ def cdata(content)
46
+ @current.add_child(@document.create_cdata(content))
47
+ end
48
+
49
+ def comment(content)
50
+ @current.add_child(@document.create_comment(content))
51
+ end
52
+
53
+ def processing_instruction(target, content)
54
+ @current.add_child(
55
+ @document.create_processing_instruction(target, content)
56
+ )
57
+ end
58
+
59
+ def namespace(prefix, uri)
60
+ @current.add_namespace(prefix, uri)
61
+ @namespaces[prefix] = uri
62
+ end
63
+ end
64
+ end
data/lib/moxml/cdata.rb CHANGED
@@ -1,39 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moxml
2
4
  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
5
  def content
13
- adapter.cdata_content(native)
6
+ adapter.cdata_content(@native)
14
7
  end
15
8
 
16
9
  def content=(text)
17
- adapter.set_cdata_content(native, text)
18
- self
19
- end
20
-
21
- def blank?
22
- content.strip.empty?
10
+ adapter.set_cdata_content(@native, normalize_xml_value(text))
23
11
  end
24
12
 
25
13
  def cdata?
26
14
  true
27
15
  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
16
  end
39
17
  end
data/lib/moxml/comment.rb CHANGED
@@ -1,35 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moxml
2
4
  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
5
  def content
13
- adapter.comment_content(native)
6
+ adapter.comment_content(@native)
14
7
  end
15
8
 
16
9
  def content=(text)
17
- adapter.set_comment_content(native, text)
18
- self
19
- end
20
-
21
- def blank?
22
- content.strip.empty?
10
+ text = normalize_xml_value(text)
11
+ adapter.validate_comment_content(text)
12
+ adapter.set_comment_content(@native, text)
23
13
  end
24
14
 
25
15
  def comment?
26
16
  true
27
17
  end
28
-
29
- private
30
-
31
- def create_native_node
32
- adapter.create_comment(nil, "")
33
- end
34
18
  end
35
19
  end
data/lib/moxml/config.rb CHANGED
@@ -1,23 +1,47 @@
1
- # lib/moxml/config.rb
1
+ # frozen_string_literal: true
2
+
2
3
  module Moxml
3
4
  class Config
4
- attr_accessor :backend, :huge_document,
5
+ VALID_ADAPTERS = %i[nokogiri oga ox].freeze
6
+ DEFAULT_ADAPTER = VALID_ADAPTERS.first
7
+
8
+ class << self
9
+ def default
10
+ @default ||= new(DEFAULT_ADAPTER, true, "UTF-8")
11
+ end
12
+ end
13
+
14
+ attr_reader :adapter_name
15
+ attr_accessor :strict_parsing,
5
16
  :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"
17
+ :entity_encoding,
18
+ :default_indent
19
+
20
+ def initialize(adapter_name = nil, strict_parsing = nil, default_encoding = nil)
21
+ self.adapter = adapter_name || Config.default.adapter_name
22
+ @strict_parsing = strict_parsing || Config.default.strict_parsing
23
+ @default_encoding = default_encoding || Config.default.default_encoding
24
+ # reserved for future use
16
25
  @default_indent = 2
17
- @cdata_sections = true
18
- @cdata_patterns = ["script", "style"]
19
- @strict_parsing = true
20
26
  @entity_encoding = :basic
21
27
  end
28
+
29
+ def adapter=(name)
30
+ name = name.to_sym
31
+ @adapter = nil
32
+
33
+ unless VALID_ADAPTERS.include?(name)
34
+ raise ArgumentError, "Invalid adapter: #{name}. Valid adapters are: #{VALID_ADAPTERS.join(", ")}"
35
+ end
36
+
37
+ @adapter_name = name
38
+ adapter
39
+ end
40
+
41
+ alias default_adapter= adapter=
42
+
43
+ def adapter
44
+ @adapter ||= Adapter.load(@adapter_name)
45
+ end
22
46
  end
23
47
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ class Context
5
+ attr_reader :config
6
+
7
+ def initialize(adapter = nil)
8
+ @config = Config.new(adapter)
9
+ end
10
+
11
+ def create_document
12
+ Document.new(config.adapter.create_document, self)
13
+ end
14
+
15
+ def parse(xml, options = {})
16
+ config.adapter.parse(xml, default_options.merge(options))
17
+ end
18
+
19
+ private
20
+
21
+ def default_options
22
+ {
23
+ encoding: config.default_encoding,
24
+ strict: config.strict_parsing,
25
+ indent: config.default_indent
26
+ }
27
+ end
28
+ end
29
+ end
@@ -1,50 +1,40 @@
1
- # lib/moxml/declaration.rb
1
+ # frozen_string_literal: true
2
+
2
3
  module Moxml
3
4
  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
5
+ ALLOWED_VERSIONS = ["1.0", "1.1"].freeze
6
+ ALLOWED_STANDALONE = %w[yes no].freeze
7
+ ALLOWED_ATTRIBUTES = %w[version encoding standalone].freeze
12
8
 
13
9
  def version
14
- adapter.declaration_version(native)
10
+ adapter.declaration_attribute(@native, "version")
15
11
  end
16
12
 
17
13
  def version=(new_version)
18
- adapter.set_declaration_version(native, new_version)
19
- self
14
+ adapter.validate_declaration_version(new_version)
15
+ adapter.set_declaration_attribute(@native, "version", new_version)
20
16
  end
21
17
 
22
18
  def encoding
23
- adapter.declaration_encoding(native)
19
+ adapter.declaration_attribute(@native, "encoding")
24
20
  end
25
21
 
26
22
  def encoding=(new_encoding)
27
- adapter.set_declaration_encoding(native, new_encoding)
28
- self
23
+ adapter.validate_declaration_encoding(new_encoding)
24
+ adapter.set_declaration_attribute(@native, "encoding", new_encoding)
29
25
  end
30
26
 
31
27
  def standalone
32
- adapter.declaration_standalone(native)
28
+ adapter.declaration_attribute(@native, "standalone")
33
29
  end
34
30
 
35
31
  def standalone=(new_standalone)
36
- adapter.set_declaration_standalone(native, new_standalone)
37
- self
32
+ adapter.validate_declaration_standalone(new_standalone)
33
+ adapter.set_declaration_attribute(@native, "standalone", new_standalone)
38
34
  end
39
35
 
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)
36
+ def declaration?
37
+ true
48
38
  end
49
39
  end
50
40
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ class Doctype < Node
5
+ def doctype?
6
+ true
7
+ end
8
+ end
9
+ end