moxml 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aeebdab58434af19ecef2aef1d8be83f44aa4cafef5386cc01f90df9c0d74b0
4
- data.tar.gz: 365ea23544a86eb5fbe13d6c87a903c844db321ba7884521ba76c4f169558bd2
3
+ metadata.gz: 65fadaecfecbc8142d11c82c79c20ea9811e3b58e21b94f335ba822bdfbfc852
4
+ data.tar.gz: e0189194a594ff763ffec269d15e56dadf91b22369d2efb399e29e3c993f1ace
5
5
  SHA512:
6
- metadata.gz: 924f7a2e7e6a68d9e78f4f65244bd5f8cdaefa58c00dc7a3a926d2cb41d1d6ac39cc98c46cdd83f13dbdec450ad5b8abbe400c6b89a595aadf2e1c21f074ab01
7
- data.tar.gz: f125a15ea901391a648a3545b5afa79d9e3794c4e5f4009c6cca623501ad53071f27fbc3046da7fe0a964a240c8d9ae7f75410283f59daa0b6a34ac561540c68
6
+ metadata.gz: 6c20b0290ed6a6f9ea904564b7619c05f1aec9b9ebe9274a6ea042b9756caeb8792a4d6f45224af8019e2760b8d752ff20a9ef9c4f8f4acb893be069b7f95a92
7
+ data.tar.gz: 05d046559db87598410f122e51fa7a2276d975f40eabe19f32c65de6c2459238976551496937e33a2a319e3a7ff110bdc782af8ce9942bfcc24840f292d79150
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-02-07 04:56:30 UTC using RuboCop version 1.71.2.
3
+ # on 2025-05-09 11:52:41 UTC using RuboCop version 1.68.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -17,39 +17,45 @@ Lint/MissingCopEnableDirective:
17
17
  Exclude:
18
18
  - 'lib/moxml/adapter/customized_oga/xml_generator.rb'
19
19
 
20
- # Offense count: 1
20
+ # Offense count: 2
21
21
  # Configuration parameters: AllowedParentClasses.
22
22
  Lint/MissingSuper:
23
23
  Exclude:
24
24
  - 'lib/moxml/adapter/customized_oga/xml_declaration.rb'
25
+ - 'lib/moxml/adapter/customized_rexml/formatter.rb'
25
26
 
26
- # Offense count: 5
27
+ # Offense count: 8
27
28
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
28
29
  Metrics/AbcSize:
29
- Max: 30
30
+ Max: 39
30
31
 
31
- # Offense count: 49
32
+ # Offense count: 53
32
33
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
33
34
  # AllowedMethods: refine
34
35
  Metrics/BlockLength:
35
- Max: 152
36
+ Max: 270
36
37
 
37
- # Offense count: 3
38
+ # Offense count: 5
38
39
  # Configuration parameters: CountComments, CountAsOne.
39
40
  Metrics/ClassLength:
40
- Max: 246
41
+ Max: 335
41
42
 
42
- # Offense count: 3
43
+ # Offense count: 7
43
44
  # Configuration parameters: AllowedMethods, AllowedPatterns.
44
45
  Metrics/CyclomaticComplexity:
45
- Max: 10
46
+ Max: 15
46
47
 
47
- # Offense count: 9
48
+ # Offense count: 15
48
49
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
49
50
  Metrics/MethodLength:
50
- Max: 20
51
+ Max: 23
51
52
 
52
- # Offense count: 4
53
+ # Offense count: 3
54
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
55
+ Metrics/PerceivedComplexity:
56
+ Max: 15
57
+
58
+ # Offense count: 5
53
59
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
54
60
  # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
55
61
  Naming/MethodParameterName:
@@ -57,17 +63,39 @@ Naming/MethodParameterName:
57
63
  - 'lib/moxml/adapter/customized_oga/xml_generator.rb'
58
64
  - 'lib/moxml/adapter/nokogiri.rb'
59
65
  - 'lib/moxml/adapter/ox.rb'
66
+ - 'lib/moxml/adapter/rexml.rb'
60
67
  - 'lib/moxml/attribute.rb'
61
68
 
62
- # Offense count: 27
69
+ # Offense count: 1
70
+ # This cop supports unsafe autocorrection (--autocorrect-all).
71
+ Style/CombinableLoops:
72
+ Exclude:
73
+ - 'lib/moxml/adapter/customized_rexml/formatter.rb'
74
+
75
+ # Offense count: 28
63
76
  # Configuration parameters: AllowedConstants.
64
77
  Style/Documentation:
65
78
  Enabled: false
66
79
 
67
- # Offense count: 20
68
- # This cop supports safe autocorrection (--autocorrect).
69
- # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
70
- # SupportedStyles: single_quotes, double_quotes
71
- Style/StringLiterals:
80
+ # Offense count: 1
81
+ # This cop supports unsafe autocorrection (--autocorrect-all).
82
+ # Configuration parameters: EnforcedStyle.
83
+ # SupportedStyles: always, always_true, never
84
+ Style/FrozenStringLiteralComment:
85
+ Exclude:
86
+ - 'lib/moxml/adapter/customized_rexml/formatter.rb'
87
+
88
+ # Offense count: 2
89
+ # Configuration parameters: MinBranchesCount.
90
+ Style/HashLikeCase:
91
+ Exclude:
92
+ - 'lib/moxml/adapter/customized_rexml/formatter.rb'
93
+
94
+ # Offense count: 2
95
+ # This cop supports unsafe autocorrection (--autocorrect-all).
96
+ # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
97
+ # AllowedMethods: present?, blank?, presence, try, try!
98
+ Style/SafeNavigation:
72
99
  Exclude:
73
- - 'spec/support/shared_examples/examples/readme_examples.rb'
100
+ - 'lib/moxml/adapter/customized_rexml/formatter.rb'
101
+ - 'lib/moxml/adapter/rexml.rb'
data/Gemfile CHANGED
@@ -11,6 +11,7 @@ gem "nokogiri", "~> 1.15"
11
11
  gem "oga", "~> 3.4"
12
12
  gem "ox", "~> 2.14"
13
13
  gem "rake", "~> 13"
14
+ gem "rexml"
14
15
  gem "rspec", "~> 3.0"
15
16
  gem "rubocop", "~> 1.21"
16
17
  gem "tempfile", "~> 0.3"
data/LICENSE.md ADDED
@@ -0,0 +1,33 @@
1
+ Licenses & Copyright
2
+ ====================
3
+
4
+ This license file adheres to the formatting guidelines of
5
+ [readable-licenses](https://github.com/nevir/readable-licenses).
6
+
7
+
8
+ Ribose's BSD 2-Clause License
9
+ -----------------------------
10
+
11
+ Copyright (c) 2024, [Ribose Inc](https://www.ribose.com).
12
+ All rights reserved.
13
+
14
+ Redistribution and use in source and binary forms, with or without modification,
15
+ are permitted provided that the following conditions are met:
16
+
17
+ 1. Redistributions of source code must retain the above copyright notice,
18
+ this list of conditions and the following disclaimer.
19
+
20
+ 2. Redistributions in binary form must reproduce the above copyright notice,
21
+ this list of conditions and the following disclaimer in the documentation
22
+ and/or other materials provided with the distribution.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc CHANGED
@@ -24,15 +24,35 @@ Key features:
24
24
  * Easy switching between XML processing engines
25
25
  * Clean separation between interface and implementation
26
26
 
27
+ == Supported XML libraries
28
+
29
+ Moxml supports the following XML libraries:
30
+
31
+ REXML:: (default) https://github.com/ruby/rexml[REXML], a pure Ruby XML parser
32
+ distributed with standard Ruby. Not the fastest, but always available.
33
+
34
+ Nokogiri:: (recommended) https://github.com/sparklemotion/nokogiri[Nokogiri], a
35
+ widely used implementation which wraps around the performant
36
+ https://github.com/GNOME/libxml2[libxml2] C library.
37
+
38
+ Oga:: https://github.com/yorickpeterse/oga[Oga], a pure Ruby XML parser.
39
+ Recommended when you need a pure Ruby solution say for
40
+ https://github.com/opal/opal[Opal].
41
+
42
+ Ox:: https://github.com/ohler55/ox[Ox], a fast XML parser (to be supported)
43
+
44
+
27
45
  == Getting started
28
46
 
47
+ === Installation
48
+
29
49
  Install the gem and at least one supported XML library:
30
50
 
31
51
  [source,ruby]
32
52
  ----
33
53
  # In your Gemfile
34
54
  gem 'moxml'
35
- gem 'nokogiri' # Or 'oga', or 'ox' when it is integrated
55
+ gem 'nokogiri' # Or 'oga', 'rexml', or 'ox' when it is integrated
36
56
  ----
37
57
 
38
58
  === Basic document creation
@@ -478,22 +498,23 @@ end
478
498
 
479
499
  === Default adapter selection
480
500
 
481
- To use an adapter other than `:nokogiri` as the global default, set it before
482
- processing any input using the following option.
501
+ To select a non-default adapter, set it before processing any input using the
502
+ following syntax.
483
503
 
484
504
  [source,ruby]
485
505
  ----
486
506
  Moxml::Config.default_adapter = <adapter-symbol>
487
507
  ----
488
508
 
489
- Moxml supports the following adapters:
509
+ Where, `<adapter-symbol>` is one of the following:
510
+
511
+ `:rexml`:: REXML (default)
490
512
 
491
- `:nokogiri`:: (default) https://github.com/sparklemotion/nokogiri[Nokogiri], a
492
- wrapper around the https://github.com/GNOME/libxml2[libxml2] C library
513
+ `:nokogiri`:: Nokogiri
493
514
 
494
- `:oga`:: https://github.com/yorickpeterse/oga[Oga], a pure Ruby XML parser
515
+ `:oga`:: Oga
495
516
 
496
- `:ox`:: https://github.com/ohler55/ox[Ox], a fast XML parser (not yet supported)
517
+ `:ox`:: Ox (not yet supported)
497
518
 
498
519
 
499
520
  == Thread safety
@@ -597,7 +618,8 @@ end
597
618
 
598
619
  == License
599
620
 
600
- Copyright (c) 2024 Ribose Inc.
621
+ Copyright (c) Ribose Inc.
601
622
 
602
- This project is licensed under the BSD-2-Clause License. See the LICENSE file for details.
623
+ This project is licensed under the BSD-2-Clause License. See the
624
+ link:LICENSE.md[] file for details.
603
625
 
@@ -63,6 +63,18 @@ module Moxml
63
63
  create_native_namespace(element, prefix, uri)
64
64
  end
65
65
 
66
+ def set_attribute_name(attribute, name)
67
+ attribute.name = name
68
+ end
69
+
70
+ def set_attribute_value(attribute, value)
71
+ attribute.value = value
72
+ end
73
+
74
+ def duplicate_node(node)
75
+ node.dup
76
+ end
77
+
66
78
  protected
67
79
 
68
80
  def create_native_element(name)
@@ -0,0 +1,195 @@
1
+ require "rexml/formatters/pretty"
2
+
3
+ module Moxml
4
+ module Adapter
5
+ module CustomizedRexml
6
+ # Custom REXML formatter that fixes indentation and wrapping issues
7
+ class Formatter < ::REXML::Formatters::Pretty
8
+ def initialize(indentation: 2, self_close_empty: false)
9
+ @indentation = " " * indentation
10
+ @level = 0
11
+ @compact = true
12
+ @width = -1 # Disable line wrapping
13
+ @self_close_empty = self_close_empty
14
+ end
15
+
16
+ def write(node, output)
17
+ case node
18
+ when ::REXML::XMLDecl
19
+ write_declaration(node, output)
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def write_element(node, output)
26
+ # output << ' ' * @level
27
+ output << "<#{node.expanded_name}"
28
+ write_attributes(node, output)
29
+
30
+ if node.children.empty? && @self_close_empty
31
+ output << "/>"
32
+ return
33
+ end
34
+
35
+ output << ">"
36
+
37
+ # Check for mixed content
38
+ has_text = node.children.any? { |c| c.is_a?(::REXML::Text) && !c.to_s.strip.empty? }
39
+ has_elements = node.children.any? { |c| c.is_a?(::REXML::Element) }
40
+ mixed = has_text && has_elements
41
+
42
+ # Handle children based on content type
43
+ unless node.children.empty?
44
+ @level += @indentation.length unless mixed
45
+
46
+ node.children.each_with_index do |child, _index|
47
+ # Skip insignificant whitespace
48
+ next if child.is_a?(::REXML::Text) &&
49
+ child.to_s.strip.empty? &&
50
+ !(child.next_sibling.nil? && child.previous_sibling.nil?)
51
+
52
+ # Indent non-text nodes in non-mixed content
53
+ # if !mixed && !child.is_a?(::REXML::Text)
54
+ # output << ' ' * @level
55
+ # end
56
+
57
+ write(child, output)
58
+
59
+ # Add newlines between elements in non-mixed content
60
+ # if !mixed && !child.is_a?(::REXML::Text) && index < node.children.size - 1
61
+ # output << "\n"
62
+ # end
63
+ end
64
+
65
+ # Reset indentation for closing tag in non-mixed content
66
+ unless mixed
67
+ @level -= @indentation.length
68
+ # output << ' ' * @level
69
+ end
70
+ end
71
+
72
+ output << "</#{node.expanded_name}>"
73
+ # output << "\n" unless mixed
74
+ end
75
+
76
+ def write_text(node, output)
77
+ text = node.value
78
+ return if text.empty?
79
+
80
+ output << escape_text(text)
81
+ end
82
+
83
+ def escape_text(text)
84
+ text.to_s.gsub(/[<>&]/) do |match|
85
+ case match
86
+ when "<" then "&lt;"
87
+ when ">" then "&gt;"
88
+ when "&" then "&amp;"
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def find_significant_sibling(node, direction)
96
+ method = direction == :next ? :next_sibling : :previous_sibling
97
+ sibling = node.send(method)
98
+ sibling = sibling.send(method) while sibling && sibling.is_a?(::REXML::Text) && sibling.to_s.strip.empty?
99
+ sibling
100
+ end
101
+
102
+ def write_cdata(node, output)
103
+ # output << ' ' * @level
104
+ output << "<![CDATA["
105
+ output << node.to_s.gsub("]]>", "]]]]><![CDATA[>")
106
+ output << "]]>"
107
+ # output << "\n"
108
+ end
109
+
110
+ def write_comment(node, output)
111
+ # output << ' ' * @level
112
+ output << "<!--"
113
+ output << node.to_s
114
+ output << "-->"
115
+ # output << "\n"
116
+ end
117
+
118
+ def write_instruction(node, output)
119
+ # output << ' ' * @level
120
+ output << "<?"
121
+ output << node.target
122
+ output << " "
123
+ output << node.content if node.content
124
+ output << "?>"
125
+ # output << "\n"
126
+ end
127
+
128
+ def write_document(node, output)
129
+ node.children.each do |child|
130
+ write(child, output)
131
+ # output << "\n" unless child == node.children.last
132
+ end
133
+ end
134
+
135
+ def write_doctype(node, output)
136
+ output << "<!DOCTYPE "
137
+ output << node.name
138
+ output << " "
139
+ output << node.external_id if node.external_id
140
+ output << ">"
141
+ # output << "\n"
142
+ end
143
+
144
+ def write_declaration(node, output)
145
+ output << "<?xml"
146
+ output << %( version="#{node.version}") if node.version
147
+ output << %( encoding="#{node.encoding.to_s.upcase}") if node.writeencoding
148
+ output << %( standalone="#{node.standalone}") if node.standalone
149
+ output << "?>"
150
+ # output << "\n"
151
+ end
152
+
153
+ def write_attributes(node, output)
154
+ # First write namespace declarations
155
+ node.attributes.each do |name, attr|
156
+ next unless name.to_s.start_with?("xmlns:") || name.to_s == "xmlns"
157
+
158
+ name = "xmlns" if name.to_s == "xmlns:" # convert the default namespace
159
+ value = attr.respond_to?(:value) ? attr.value : attr
160
+ output << " #{name}=\"#{value}\""
161
+ end
162
+
163
+ # Then write regular attributes
164
+ node.attributes.each do |name, attr|
165
+ next if name.to_s.start_with?("xmlns:") || name.to_s == "xmlns"
166
+
167
+ output << " "
168
+ output << if attr.respond_to?(:prefix) && attr.prefix
169
+ "#{attr.prefix}:#{attr.name}"
170
+ else
171
+ name.to_s
172
+ end
173
+
174
+ output << "=\""
175
+ value = attr.respond_to?(:value) ? attr.value : attr
176
+ output << escape_attribute_value(value.to_s)
177
+ output << "\""
178
+ end
179
+ end
180
+
181
+ def escape_attribute_value(value)
182
+ value.to_s.gsub(/[<>&"]/) do |match|
183
+ case match
184
+ when "<" then "&lt;"
185
+ when ">" then "&gt;"
186
+ when "&" then "&amp;"
187
+ when '"' then "&quot;"
188
+ # when "'" then '&apos;'
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -221,7 +221,7 @@ module Moxml
221
221
  end
222
222
 
223
223
  def text_content(node)
224
- node.content
224
+ node.text
225
225
  end
226
226
 
227
227
  def inner_text(node)
@@ -213,11 +213,25 @@ module Moxml
213
213
  end
214
214
 
215
215
  def add_previous_sibling(node, sibling)
216
- node.before(sibling)
216
+ if node.parent == sibling.parent
217
+ # Oga doesn't manipulate children of the same parent
218
+ dup_sibling = node.node_set.delete(sibling)
219
+ index = node.node_set.index(node)
220
+ node.node_set.insert(index, dup_sibling)
221
+ else
222
+ node.before(sibling)
223
+ end
217
224
  end
218
225
 
219
226
  def add_next_sibling(node, sibling)
220
- node.after(sibling)
227
+ if node.parent == sibling.parent
228
+ # Oga doesn't manipulate children of the same parent
229
+ dup_sibling = node.node_set.delete(sibling)
230
+ index = node.node_set.index(node) + 1
231
+ node.node_set.insert(index, dup_sibling)
232
+ else
233
+ node.after(sibling)
234
+ end
221
235
  end
222
236
 
223
237
  def remove(node)
@@ -229,7 +243,7 @@ module Moxml
229
243
  end
230
244
 
231
245
  def replace_children(node, new_children)
232
- node.inner_text = ""
246
+ node.children = []
233
247
  new_children.each { |child| add_child(node, child) }
234
248
  end
235
249
 
@@ -296,14 +310,14 @@ module Moxml
296
310
  node.namespaces.values
297
311
  end
298
312
 
299
- def xpath(node, expression, _namespaces = {})
300
- node.xpath(expression).to_a
313
+ def xpath(node, expression, namespaces = nil)
314
+ node.xpath(expression, {}, namespaces: namespaces&.transform_keys(&:to_s)).to_a
301
315
  rescue ::LL::ParserError => e
302
316
  raise Moxml::XPathError, e.message
303
317
  end
304
318
 
305
- def at_xpath(node, expression, _namespaces = {})
306
- node.at_xpath(expression)
319
+ def at_xpath(node, expression, namespaces = nil)
320
+ node.at_xpath(expression, namespaces: namespaces)
307
321
  rescue ::Oga::XPath::Error => e
308
322
  raise Moxml::XPathError, e.message
309
323
  end