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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop_todo.yml +2 -2
  4. data/Gemfile +2 -0
  5. data/README.adoc +71 -21
  6. data/lib/moxml/adapter/base.rb +8 -2
  7. data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
  8. data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
  9. data/lib/moxml/adapter/customized_ox/text.rb +12 -0
  10. data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -3
  11. data/lib/moxml/adapter/nokogiri.rb +3 -1
  12. data/lib/moxml/adapter/oga.rb +4 -2
  13. data/lib/moxml/adapter/ox.rb +238 -92
  14. data/lib/moxml/adapter/rexml.rb +10 -6
  15. data/lib/moxml/adapter.rb +1 -1
  16. data/lib/moxml/cdata.rb +0 -4
  17. data/lib/moxml/comment.rb +0 -4
  18. data/lib/moxml/context.rb +2 -2
  19. data/lib/moxml/doctype.rb +1 -5
  20. data/lib/moxml/document.rb +1 -1
  21. data/lib/moxml/document_builder.rb +1 -1
  22. data/lib/moxml/element.rb +2 -1
  23. data/lib/moxml/namespace.rb +4 -0
  24. data/lib/moxml/node.rb +17 -2
  25. data/lib/moxml/node_set.rb +8 -1
  26. data/lib/moxml/processing_instruction.rb +0 -4
  27. data/lib/moxml/text.rb +0 -4
  28. data/lib/moxml/version.rb +1 -1
  29. data/lib/ox/node.rb +9 -0
  30. data/spec/fixtures/small.xml +1 -0
  31. data/spec/moxml/all_with_adapters_spec.rb +2 -1
  32. data/spec/support/shared_examples/builder.rb +19 -2
  33. data/spec/support/shared_examples/cdata.rb +7 -5
  34. data/spec/support/shared_examples/declaration.rb +16 -4
  35. data/spec/support/shared_examples/doctype.rb +2 -1
  36. data/spec/support/shared_examples/document.rb +10 -0
  37. data/spec/support/shared_examples/edge_cases.rb +6 -0
  38. data/spec/support/shared_examples/element.rb +4 -0
  39. data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
  40. data/spec/support/shared_examples/examples/memory.rb +30 -17
  41. data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
  42. data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
  43. data/spec/support/shared_examples/examples/xpath.rb +34 -3
  44. data/spec/support/shared_examples/integration.rb +4 -0
  45. data/spec/support/shared_examples/namespace.rb +16 -0
  46. data/spec/support/shared_examples/node.rb +4 -0
  47. data/spec/support/shared_examples/node_set.rb +20 -0
  48. data/spec/support/shared_examples/processing_instruction.rb +1 -1
  49. data/spec/support/shared_examples/text.rb +2 -1
  50. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65fadaecfecbc8142d11c82c79c20ea9811e3b58e21b94f335ba822bdfbfc852
4
- data.tar.gz: e0189194a594ff763ffec269d15e56dadf91b22369d2efb399e29e3c993f1ace
3
+ metadata.gz: 90a6a09c55ff15198efd4687fa31e58bc4b17fa6a72b46c0e9cb88d5474f8899
4
+ data.tar.gz: 27b376ff3bfa49b2bcb7c768451af8e39f491a7e2467c6965e85cee3cedc43f9
5
5
  SHA512:
6
- metadata.gz: 6c20b0290ed6a6f9ea904564b7619c05f1aec9b9ebe9274a6ea042b9756caeb8792a4d6f45224af8019e2760b8d752ff20a9ef9c4f8f4acb893be069b7f95a92
7
- data.tar.gz: 05d046559db87598410f122e51fa7a2276d975f40eabe19f32c65de6c2459238976551496937e33a2a319e3a7ff110bdc782af8ce9942bfcc24840f292d79150
6
+ metadata.gz: 3895bfc0612eddea337e083e6cdd66dbfda811d7d819c858d05a7e30b433c226ddaa8e7995690c65f142b329ac9454c8712c1cb9f6d95372c8cbbc0b0c8e7846
7
+ data.tar.gz: 52bf943812996dcd501d65dfe2d6f9b06578429de92fe2ad74f87080c1c57cc04a8574c3e85ad72f156918d5ada2415c8e4a4c5e97ff2f10d68b5056d821c599
data/.gitignore CHANGED
@@ -1,4 +1,3 @@
1
- /.bundle/
2
1
  /.yardoc
3
2
  /_yardoc/
4
3
  /coverage/
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: 335
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: 15
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:: (default) https://github.com/ruby/rexml[REXML], a pure Ruby XML parser
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:: (recommended) https://github.com/sparklemotion/nokogiri[Nokogiri], a
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 (to be supported)
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' when it is integrated
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 # Remove from document
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 # Remove from document
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 # Get 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 # Remove from document
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 # Get parent node
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? # Is it an 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 (default)
560
+ `:rexml`:: REXML
512
561
 
513
- `:nokogiri`:: Nokogiri
562
+ `:nokogiri`:: Nokogiri (default)
514
563
 
515
564
  `:oga`:: Oga
516
565
 
517
- `:ox`:: Ox (not yet supported)
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('./title')
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.add_declaration(version: "1.0", encoding: "UTF-8")
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 (c) Ribose Inc.
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.
@@ -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
- create_native_text(normalize_xml_value(content))
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../ox/node"
4
+
5
+ module Moxml
6
+ module Adapter
7
+ module CustomizedOx
8
+ # Ox uses Strings, but a string cannot have a parent reference
9
+ class Text < ::Ox::Node; end
10
+ end
11
+ end
12
+ end
@@ -101,9 +101,9 @@ module Moxml
101
101
 
102
102
  def write_cdata(node, output)
103
103
  # output << ' ' * @level
104
- output << "<![CDATA["
105
- output << node.to_s.gsub("]]>", "]]]]><![CDATA[>")
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
@@ -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