moxml 0.1.4 → 0.1.6
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/.gitignore +0 -1
- data/.rubocop_todo.yml +2 -2
- data/Gemfile +2 -0
- data/README.adoc +71 -21
- data/lib/moxml/adapter/base.rb +8 -2
- data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
- data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
- data/lib/moxml/adapter/customized_ox/text.rb +12 -0
- data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -3
- data/lib/moxml/adapter/nokogiri.rb +3 -1
- data/lib/moxml/adapter/oga.rb +4 -2
- data/lib/moxml/adapter/ox.rb +238 -92
- data/lib/moxml/adapter/rexml.rb +10 -6
- data/lib/moxml/adapter.rb +1 -1
- data/lib/moxml/cdata.rb +0 -4
- data/lib/moxml/comment.rb +0 -4
- data/lib/moxml/context.rb +2 -2
- data/lib/moxml/doctype.rb +1 -5
- data/lib/moxml/document.rb +1 -1
- data/lib/moxml/document_builder.rb +1 -1
- data/lib/moxml/element.rb +2 -1
- data/lib/moxml/namespace.rb +4 -0
- data/lib/moxml/node.rb +17 -2
- data/lib/moxml/node_set.rb +8 -1
- data/lib/moxml/processing_instruction.rb +0 -4
- data/lib/moxml/text.rb +0 -4
- data/lib/moxml/version.rb +1 -1
- data/lib/ox/node.rb +9 -0
- data/spec/fixtures/small.xml +1 -0
- data/spec/moxml/all_with_adapters_spec.rb +2 -1
- data/spec/support/shared_examples/builder.rb +19 -2
- data/spec/support/shared_examples/cdata.rb +7 -5
- data/spec/support/shared_examples/declaration.rb +16 -4
- data/spec/support/shared_examples/doctype.rb +2 -1
- data/spec/support/shared_examples/document.rb +10 -0
- data/spec/support/shared_examples/edge_cases.rb +6 -0
- data/spec/support/shared_examples/element.rb +4 -0
- data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
- data/spec/support/shared_examples/examples/memory.rb +30 -17
- data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
- data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
- data/spec/support/shared_examples/examples/xpath.rb +34 -3
- data/spec/support/shared_examples/integration.rb +4 -0
- data/spec/support/shared_examples/namespace.rb +16 -0
- data/spec/support/shared_examples/node.rb +4 -0
- data/spec/support/shared_examples/node_set.rb +20 -0
- data/spec/support/shared_examples/processing_instruction.rb +1 -1
- data/spec/support/shared_examples/text.rb +2 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90a6a09c55ff15198efd4687fa31e58bc4b17fa6a72b46c0e9cb88d5474f8899
|
4
|
+
data.tar.gz: 27b376ff3bfa49b2bcb7c768451af8e39f491a7e2467c6965e85cee3cedc43f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3895bfc0612eddea337e083e6cdd66dbfda811d7d819c858d05a7e30b433c226ddaa8e7995690c65f142b329ac9454c8712c1cb9f6d95372c8cbbc0b0c8e7846
|
7
|
+
data.tar.gz: 52bf943812996dcd501d65dfe2d6f9b06578429de92fe2ad74f87080c1c57cc04a8574c3e85ad72f156918d5ada2415c8e4a4c5e97ff2f10d68b5056d821c599
|
data/.gitignore
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -38,7 +38,7 @@ Metrics/BlockLength:
|
|
38
38
|
# Offense count: 5
|
39
39
|
# Configuration parameters: CountComments, CountAsOne.
|
40
40
|
Metrics/ClassLength:
|
41
|
-
Max:
|
41
|
+
Max: 373
|
42
42
|
|
43
43
|
# Offense count: 7
|
44
44
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
@@ -53,7 +53,7 @@ Metrics/MethodLength:
|
|
53
53
|
# Offense count: 3
|
54
54
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
55
55
|
Metrics/PerceivedComplexity:
|
56
|
-
Max:
|
56
|
+
Max: 16
|
57
57
|
|
58
58
|
# Offense count: 5
|
59
59
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
data/Gemfile
CHANGED
@@ -15,6 +15,8 @@ gem "rexml"
|
|
15
15
|
gem "rspec", "~> 3.0"
|
16
16
|
gem "rubocop", "~> 1.21"
|
17
17
|
gem "tempfile", "~> 0.3"
|
18
|
+
# Provides iteration per second benchmarking for Ruby
|
19
|
+
gem "benchmark-ips"
|
18
20
|
|
19
21
|
# Needed by get_process_mem on Windows
|
20
22
|
gem "sys-proctable" if Gem.win_platform?
|
data/README.adoc
CHANGED
@@ -28,10 +28,10 @@ Key features:
|
|
28
28
|
|
29
29
|
Moxml supports the following XML libraries:
|
30
30
|
|
31
|
-
REXML::
|
31
|
+
REXML:: https://github.com/ruby/rexml[REXML], a pure Ruby XML parser
|
32
32
|
distributed with standard Ruby. Not the fastest, but always available.
|
33
33
|
|
34
|
-
Nokogiri:: (
|
34
|
+
Nokogiri:: (default) https://github.com/sparklemotion/nokogiri[Nokogiri], a
|
35
35
|
widely used implementation which wraps around the performant
|
36
36
|
https://github.com/GNOME/libxml2[libxml2] C library.
|
37
37
|
|
@@ -39,8 +39,53 @@ Oga:: https://github.com/yorickpeterse/oga[Oga], a pure Ruby XML parser.
|
|
39
39
|
Recommended when you need a pure Ruby solution say for
|
40
40
|
https://github.com/opal/opal[Opal].
|
41
41
|
|
42
|
-
Ox:: https://github.com/ohler55/ox[Ox], a fast XML parser
|
42
|
+
Ox:: https://github.com/ohler55/ox[Ox], a fast XML parser.
|
43
43
|
|
44
|
+
=== Feature table
|
45
|
+
|
46
|
+
Moxml exercises its best effort to provide a consistent interface across basic
|
47
|
+
XML features, various XML libraries have different features and capabilities.
|
48
|
+
|
49
|
+
The following table summarizes the features supported by each library.
|
50
|
+
|
51
|
+
NOTE: The checkmarks indicate support for the feature, while the footnotes
|
52
|
+
provide additional context for specific features.
|
53
|
+
|
54
|
+
[cols="1,1,1,1,3"]
|
55
|
+
|===
|
56
|
+
|Feature |Nokogiri |Oga |REXML |Ox
|
57
|
+
|
58
|
+
|Parsing, serializing
|
59
|
+
| ✅
|
60
|
+
| ✅
|
61
|
+
| ✅
|
62
|
+
| ✅
|
63
|
+
|
64
|
+
|Node manipulation
|
65
|
+
| ✅
|
66
|
+
| ✅
|
67
|
+
| ✅
|
68
|
+
| ✅ See NOTE 1.
|
69
|
+
|
70
|
+
|Basic XPath
|
71
|
+
| ✅
|
72
|
+
| ✅
|
73
|
+
| ✅
|
74
|
+
a|
|
75
|
+
Uses `locate`. See NOTE 2.
|
76
|
+
|
77
|
+
|XPath with namespaces
|
78
|
+
| ✅
|
79
|
+
| ✅
|
80
|
+
| ❌
|
81
|
+
a|
|
82
|
+
Uses `locate`. See NOTE 2.
|
83
|
+
|
84
|
+
|===
|
85
|
+
|
86
|
+
NOTE 1: Ox's node manipulation may have issues, especially when manipulating Text nodes.
|
87
|
+
|
88
|
+
NOTE 2: The native Ox method `locate` is similar to XPath but has a different syntax.
|
44
89
|
|
45
90
|
== Getting started
|
46
91
|
|
@@ -52,7 +97,7 @@ Install the gem and at least one supported XML library:
|
|
52
97
|
----
|
53
98
|
# In your Gemfile
|
54
99
|
gem 'moxml'
|
55
|
-
gem 'nokogiri' # Or 'oga', 'rexml', or 'ox'
|
100
|
+
gem 'nokogiri' # Or 'oga', 'rexml', or 'ox'
|
56
101
|
----
|
57
102
|
|
58
103
|
=== Basic document creation
|
@@ -248,7 +293,7 @@ text.text? # Returns true
|
|
248
293
|
|
249
294
|
# Structure
|
250
295
|
text.parent # Get parent node
|
251
|
-
text.remove
|
296
|
+
text.remove # Remove from document
|
252
297
|
text.replace(node) # Replace with another node
|
253
298
|
----
|
254
299
|
|
@@ -270,7 +315,7 @@ cdata.cdata? # Returns true
|
|
270
315
|
|
271
316
|
# Structure
|
272
317
|
cdata.parent # Get parent node
|
273
|
-
cdata.remove
|
318
|
+
cdata.remove # Remove from document
|
274
319
|
cdata.replace(node) # Replace with another node
|
275
320
|
----
|
276
321
|
|
@@ -309,7 +354,7 @@ pi = doc.create_processing_instruction("xml-stylesheet",
|
|
309
354
|
# PI properties
|
310
355
|
pi.target # Get PI target
|
311
356
|
pi.target = "new" # Set PI target
|
312
|
-
pi.content
|
357
|
+
pi.content # Get PI content
|
313
358
|
pi.content = "new" # Set PI content
|
314
359
|
|
315
360
|
# Node type checking
|
@@ -317,7 +362,7 @@ pi.processing_instruction? # Returns true
|
|
317
362
|
|
318
363
|
# Structure
|
319
364
|
pi.parent # Get parent node
|
320
|
-
pi.remove
|
365
|
+
pi.remove # Remove from document
|
321
366
|
pi.replace(node) # Replace with another node
|
322
367
|
----
|
323
368
|
|
@@ -364,15 +409,13 @@ Each node type provides methods for traversing the document structure:
|
|
364
409
|
|
365
410
|
[source,ruby]
|
366
411
|
----
|
367
|
-
node.parent
|
412
|
+
node.parent # Get parent node
|
368
413
|
node.children # Get child nodes
|
369
414
|
node.next_sibling # Get next sibling
|
370
415
|
node.previous_sibling # Get previous sibling
|
371
|
-
node.ancestors # Get all ancestor nodes
|
372
|
-
node.descendants # Get all descendant nodes
|
373
416
|
|
374
417
|
# Type checking
|
375
|
-
node.element?
|
418
|
+
node.element? # Is it an element?
|
376
419
|
node.text? # Is it a text node?
|
377
420
|
node.cdata? # Is it a CDATA section?
|
378
421
|
node.comment? # Is it a comment?
|
@@ -382,14 +425,14 @@ node.namespace? # Is it a namespace?
|
|
382
425
|
|
383
426
|
# Node information
|
384
427
|
node.document # Get owning document
|
385
|
-
node.path # Get XPath to node
|
386
|
-
node.line_number # Get source line number (if available)
|
387
428
|
----
|
388
429
|
|
389
430
|
== Advanced features
|
390
431
|
|
391
432
|
=== XPath querying and node mapping
|
392
433
|
|
434
|
+
==== Nokogiri, Oga, REXML
|
435
|
+
|
393
436
|
Moxml provides efficient XPath querying by leveraging the native XML library's
|
394
437
|
implementation while maintaining consistent node mapping:
|
395
438
|
|
@@ -414,6 +457,12 @@ doc.xpath('//book').each do |book|
|
|
414
457
|
end
|
415
458
|
----
|
416
459
|
|
460
|
+
==== Ox
|
461
|
+
|
462
|
+
The native Ox's query method
|
463
|
+
https://www.ohler.com/ox/Ox/Element.html#method-i-locate[`locate`] resembles
|
464
|
+
XPath but has a different syntax.
|
465
|
+
|
417
466
|
=== Namespace handling
|
418
467
|
|
419
468
|
[source,ruby]
|
@@ -508,13 +557,13 @@ Moxml::Config.default_adapter = <adapter-symbol>
|
|
508
557
|
|
509
558
|
Where, `<adapter-symbol>` is one of the following:
|
510
559
|
|
511
|
-
`:rexml`:: REXML
|
560
|
+
`:rexml`:: REXML
|
512
561
|
|
513
|
-
`:nokogiri`:: Nokogiri
|
562
|
+
`:nokogiri`:: Nokogiri (default)
|
514
563
|
|
515
564
|
`:oga`:: Oga
|
516
565
|
|
517
|
-
`:ox`:: Ox
|
566
|
+
`:ox`:: Ox
|
518
567
|
|
519
568
|
|
520
569
|
== Thread safety
|
@@ -567,7 +616,7 @@ doc.xpath('//book/title')
|
|
567
616
|
doc.xpath('//title')
|
568
617
|
|
569
618
|
# Most efficient - direct child access
|
570
|
-
root.xpath('
|
619
|
+
root.xpath('./*/title')
|
571
620
|
----
|
572
621
|
|
573
622
|
== Best practices
|
@@ -577,7 +626,7 @@ root.xpath('./title')
|
|
577
626
|
[source,ruby]
|
578
627
|
----
|
579
628
|
# Preferred - using builder pattern
|
580
|
-
doc = Moxml.new.build do
|
629
|
+
doc = Moxml::Builder.new(Moxml.new).build do
|
581
630
|
declaration version: "1.0", encoding: "UTF-8"
|
582
631
|
element 'root' do
|
583
632
|
element 'child' do
|
@@ -588,7 +637,7 @@ end
|
|
588
637
|
|
589
638
|
# Alternative - direct manipulation
|
590
639
|
doc = Moxml.new.create_document
|
591
|
-
doc.
|
640
|
+
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
592
641
|
root = doc.create_element('root')
|
593
642
|
doc.add_child(root)
|
594
643
|
----
|
@@ -604,6 +653,7 @@ element
|
|
604
653
|
|
605
654
|
# Preferred - clear node type checking
|
606
655
|
if node.element?
|
656
|
+
node.add_namespace('dc', 'http://purl.org/dc/elements/1.1/')
|
607
657
|
node.add_child(doc.create_text('content'))
|
608
658
|
end
|
609
659
|
----
|
@@ -618,7 +668,7 @@ end
|
|
618
668
|
|
619
669
|
== License
|
620
670
|
|
621
|
-
Copyright
|
671
|
+
Copyright Ribose.
|
622
672
|
|
623
673
|
This project is licensed under the BSD-2-Clause License. See the
|
624
674
|
link:LICENSE.md[] file for details.
|
data/lib/moxml/adapter/base.rb
CHANGED
@@ -19,7 +19,7 @@ module Moxml
|
|
19
19
|
raise NotImplementedError
|
20
20
|
end
|
21
21
|
|
22
|
-
def create_document
|
22
|
+
def create_document(native_doc = nil)
|
23
23
|
raise NotImplementedError
|
24
24
|
end
|
25
25
|
|
@@ -29,7 +29,8 @@ module Moxml
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_text(content)
|
32
|
-
|
32
|
+
# Ox freezes the content, so we need to dup it
|
33
|
+
create_native_text(normalize_xml_value(content).dup)
|
33
34
|
end
|
34
35
|
|
35
36
|
def create_cdata(content)
|
@@ -75,6 +76,11 @@ module Moxml
|
|
75
76
|
node.dup
|
76
77
|
end
|
77
78
|
|
79
|
+
def patch_node(node, _parent = nil)
|
80
|
+
# monkey-patch the native node if necessary
|
81
|
+
node
|
82
|
+
end
|
83
|
+
|
78
84
|
protected
|
79
85
|
|
80
86
|
def create_native_element(name)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../ox/node"
|
4
|
+
|
5
|
+
module Moxml
|
6
|
+
module Adapter
|
7
|
+
module CustomizedOx
|
8
|
+
class Attribute < ::Ox::Node
|
9
|
+
attr_reader :name, :prefix
|
10
|
+
|
11
|
+
def initialize(attr_name, value, parent = nil)
|
12
|
+
self.name = attr_name
|
13
|
+
@parent = parent
|
14
|
+
super(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def name=(new_name)
|
18
|
+
@prefix, new_name = new_name.to_s.split(":", 2) if new_name.to_s.include?(":")
|
19
|
+
|
20
|
+
@name = new_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def expanded_name
|
24
|
+
[prefix, name].compact.join(":")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../ox/node"
|
4
|
+
|
5
|
+
module Moxml
|
6
|
+
module Adapter
|
7
|
+
module CustomizedOx
|
8
|
+
class Namespace
|
9
|
+
attr_accessor :uri, :parent
|
10
|
+
attr_writer :prefix
|
11
|
+
|
12
|
+
def initialize(prefix, uri, parent = nil)
|
13
|
+
@prefix = prefix
|
14
|
+
@uri = uri
|
15
|
+
@parent = parent
|
16
|
+
end
|
17
|
+
|
18
|
+
def prefix
|
19
|
+
return if @prefix == "xmlns" || @prefix.nil?
|
20
|
+
|
21
|
+
@prefix.to_s.delete_prefix("xmlns:")
|
22
|
+
end
|
23
|
+
|
24
|
+
def expanded_prefix
|
25
|
+
["xmlns", prefix].compact.join(":")
|
26
|
+
end
|
27
|
+
|
28
|
+
def default?
|
29
|
+
prefix.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -101,9 +101,9 @@ module Moxml
|
|
101
101
|
|
102
102
|
def write_cdata(node, output)
|
103
103
|
# output << ' ' * @level
|
104
|
-
output <<
|
105
|
-
output << node.to_s.gsub(
|
106
|
-
output <<
|
104
|
+
output << ::REXML::CData::START
|
105
|
+
output << node.to_s.gsub(::REXML::CData::STOP, "]]]]><![CDATA[>")
|
106
|
+
output << ::REXML::CData::STOP
|
107
107
|
# output << "\n"
|
108
108
|
end
|
109
109
|
|
@@ -31,7 +31,7 @@ module Moxml
|
|
31
31
|
DocumentBuilder.new(Context.new(:nokogiri)).build(native_doc)
|
32
32
|
end
|
33
33
|
|
34
|
-
def create_document
|
34
|
+
def create_document(_native_doc = nil)
|
35
35
|
::Nokogiri::XML::Document.new
|
36
36
|
end
|
37
37
|
|
@@ -120,6 +120,8 @@ module Moxml
|
|
120
120
|
when ::Nokogiri::XML::CDATA then :cdata
|
121
121
|
when ::Nokogiri::XML::Text then :text
|
122
122
|
when ::Nokogiri::XML::Comment then :comment
|
123
|
+
when ::Nokogiri::XML::Attr then :attribute
|
124
|
+
when ::Nokogiri::XML::Namespace then :namespace
|
123
125
|
when ::Nokogiri::XML::ProcessingInstruction then :processing_instruction
|
124
126
|
when ::Nokogiri::XML::Document, ::Nokogiri::XML::DocumentFragment then :document
|
125
127
|
when ::Nokogiri::XML::DTD then :doctype
|
data/lib/moxml/adapter/oga.rb
CHANGED
@@ -24,7 +24,7 @@ module Moxml
|
|
24
24
|
DocumentBuilder.new(Context.new(:oga)).build(native_doc)
|
25
25
|
end
|
26
26
|
|
27
|
-
def create_document
|
27
|
+
def create_document(_native_doc = nil)
|
28
28
|
::Oga::XML::Document.new
|
29
29
|
end
|
30
30
|
|
@@ -111,6 +111,8 @@ module Moxml
|
|
111
111
|
when ::Oga::XML::Text then :text
|
112
112
|
when ::Oga::XML::Cdata then :cdata
|
113
113
|
when ::Oga::XML::Comment then :comment
|
114
|
+
when ::Oga::XML::Attribute then :attribute
|
115
|
+
when ::Oga::XML::Namespace then :namespace
|
114
116
|
when ::Oga::XML::ProcessingInstruction then :processing_instruction
|
115
117
|
when ::Oga::XML::Document then :document
|
116
118
|
when ::Oga::XML::Doctype then :doctype
|
@@ -261,7 +263,7 @@ module Moxml
|
|
261
263
|
end
|
262
264
|
|
263
265
|
def set_text_content(node, content)
|
264
|
-
if node.respond_to?(:inner_text)
|
266
|
+
if node.respond_to?(:inner_text=)
|
265
267
|
node.inner_text = content
|
266
268
|
else
|
267
269
|
# Oga::XML::Text node for example
|