rxerces 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGES.md +9 -0
- data/Gemfile +7 -0
- data/README.md +6 -2
- data/Rakefile +43 -0
- data/certs/djberg96_pub.pem +26 -0
- data/examples/basic_usage.rb +75 -0
- data/examples/simple_example.rb +34 -0
- data/examples/xpath_example.rb +108 -0
- data/lib/rxerces/nokogiri.rb +33 -0
- data/lib/rxerces/version.rb +1 -1
- data/lib/rxerces.rb +0 -31
- data/rxerces.gemspec +33 -0
- data/spec/document_spec.rb +66 -0
- data/spec/element_spec.rb +25 -0
- data/spec/node_spec.rb +111 -0
- data/spec/nodeset_spec.rb +92 -0
- data/spec/nokogiri_compatibility_spec.rb +98 -0
- data/spec/rxerces_shared.rb +10 -0
- data/spec/rxerces_spec.rb +23 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/xpath_spec.rb +164 -0
- data.tar.gz.sig +0 -0
- metadata +68 -9
- metadata.gz.sig +2 -0
- data/lib/rxerces/rxerces.bundle +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e19a59f7418eca81e344bf69e5a60b91a510615979d4716c23eb18cd79fd954f
|
|
4
|
+
data.tar.gz: b8d5c62806c094fc61ca00ad47b4347990440d3f208d6e2678c1b26e9453b0e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2fd038f7d64bab9017b48b2a20b1abd1df859f6bc21082ae4e73e955f41a2d664eb5991d07bbc5aa1393eb8d72040e1f65358436c424987d1757261f71e086d
|
|
7
|
+
data.tar.gz: 1493d18f198efc2bd6bf9f06282d071a96b7edf91f0de4cb5960ceefad6baf394fe3257348b1f835f193a982661be2af12d0dc12b80a9dcc933794d5a521e61f
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/CHANGES.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## 0.2.0 - 13-Dec-2025
|
|
2
|
+
* The nokogiri compatibility layer is now optional.
|
|
3
|
+
* Fixed up the gemspec with real values instead of the AI generated junk.
|
|
4
|
+
* Updated the Rakefile a bit, reworked some of the tasks.
|
|
5
|
+
* Minor spec updates.
|
|
6
|
+
* Added my cert.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 - 12-Dec-2025
|
|
9
|
+
* Initial release.
|
data/Gemfile
ADDED
data/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
[](https://github.com/djberg96/rxerces/actions/workflows/ci.yml)
|
|
2
|
+
|
|
1
3
|
# RXerces
|
|
2
4
|
|
|
3
5
|
A Ruby XML library with a Nokogiri-compatible API, powered by Apache Xerces-C instead of libxml2.
|
|
@@ -73,10 +75,10 @@ puts root.name # => "root"
|
|
|
73
75
|
|
|
74
76
|
### Nokogiri Compatibility
|
|
75
77
|
|
|
76
|
-
RXerces provides
|
|
78
|
+
RXerces provides optional Nokogiri compatibility. Require `rxerces/nokogiri` to enable drop-in replacement:
|
|
77
79
|
|
|
78
80
|
```ruby
|
|
79
|
-
require 'rxerces'
|
|
81
|
+
require 'rxerces/nokogiri'
|
|
80
82
|
|
|
81
83
|
# Use Nokogiri syntax
|
|
82
84
|
doc = Nokogiri.XML('<root><child>text</child></root>')
|
|
@@ -86,6 +88,8 @@ puts doc.root.name # => "root"
|
|
|
86
88
|
Nokogiri::XML::Document == RXerces::XML::Document # => true
|
|
87
89
|
```
|
|
88
90
|
|
|
91
|
+
**Note:** If you don't need Nokogiri compatibility, just `require 'rxerces'` and use the `RXerces` module directly.
|
|
92
|
+
|
|
89
93
|
### Working with Nodes
|
|
90
94
|
|
|
91
95
|
```ruby
|
data/Rakefile
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "rake/extensiontask"
|
|
2
|
+
require "rspec/core/rake_task"
|
|
3
|
+
require "rake/clean"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
include RbConfig
|
|
6
|
+
|
|
7
|
+
CLEAN.include(
|
|
8
|
+
'**/*.gem', # Gem files
|
|
9
|
+
'**/*.rbc', # Rubinius
|
|
10
|
+
'**/*.o', # C object file
|
|
11
|
+
'**/*.log', # Ruby extension build log
|
|
12
|
+
'**/*.lock', # Gemfile.lock
|
|
13
|
+
'**/Makefile', # C Makefile
|
|
14
|
+
'**/conftest.dSYM', # OS X build directory
|
|
15
|
+
"**/*.#{CONFIG['DLEXT']}" # C shared object
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
namespace :gem do
|
|
19
|
+
desc "Create the sys-uname gem"
|
|
20
|
+
task :create => [:clean] do
|
|
21
|
+
require 'rubygems/package'
|
|
22
|
+
spec = Gem::Specification.load('rxerces.gemspec')
|
|
23
|
+
spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
|
|
24
|
+
Gem::Package.build(spec)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc "Install the sys-uname gem"
|
|
28
|
+
task :install => [:create] do
|
|
29
|
+
file = Dir["*.gem"].first
|
|
30
|
+
sh "gem install #{file}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Rake::ExtensionTask.new("rxerces") do |ext|
|
|
35
|
+
ext.lib_dir = "lib/rxerces"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
39
|
+
t.verbose = false
|
|
40
|
+
t.rspec_opts = '-f documentation -w'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
task default: [:compile, :spec]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
|
|
3
|
+
cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
|
|
4
|
+
MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
|
|
5
|
+
ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
|
|
6
|
+
bTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h
|
|
7
|
+
A+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj
|
|
8
|
+
u95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph
|
|
9
|
+
75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO
|
|
10
|
+
6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL
|
|
11
|
+
iLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3
|
|
12
|
+
ctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq
|
|
13
|
+
74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq
|
|
14
|
+
058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV
|
|
15
|
+
HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e
|
|
16
|
+
AzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq
|
|
17
|
+
YmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq
|
|
18
|
+
/3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ
|
|
19
|
+
h9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY
|
|
20
|
+
6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH
|
|
21
|
+
ks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE
|
|
22
|
+
1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf
|
|
23
|
+
DYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR
|
|
24
|
+
ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
|
|
25
|
+
WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
|
|
26
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'rxerces'
|
|
2
|
+
require 'rxerces/nokogiri'
|
|
3
|
+
|
|
4
|
+
puts "=== RXerces Basic Usage Example ===\n\n"
|
|
5
|
+
|
|
6
|
+
# Parse XML
|
|
7
|
+
xml = <<-XML
|
|
8
|
+
<library name="City Library">
|
|
9
|
+
<book id="1" isbn="978-0451524935">
|
|
10
|
+
<title>1984</title>
|
|
11
|
+
<author>George Orwell</author>
|
|
12
|
+
<year>1949</year>
|
|
13
|
+
</book>
|
|
14
|
+
<book id="2" isbn="978-0060850524">
|
|
15
|
+
<title>Brave New World</title>
|
|
16
|
+
<author>Aldous Huxley</author>
|
|
17
|
+
<year>1932</year>
|
|
18
|
+
</book>
|
|
19
|
+
</library>
|
|
20
|
+
XML
|
|
21
|
+
|
|
22
|
+
puts "1. Parsing XML document..."
|
|
23
|
+
doc = RXerces.XML(xml)
|
|
24
|
+
puts " ✓ Document parsed successfully\n\n"
|
|
25
|
+
|
|
26
|
+
# Access root element
|
|
27
|
+
puts "2. Accessing root element..."
|
|
28
|
+
root = doc.root
|
|
29
|
+
puts " Root element name: #{root.name}"
|
|
30
|
+
puts " Library name: #{root['name']}\n\n"
|
|
31
|
+
|
|
32
|
+
# Navigate children
|
|
33
|
+
puts "3. Navigating child elements..."
|
|
34
|
+
books = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
|
|
35
|
+
puts " Found #{books.length} books:\n\n"
|
|
36
|
+
|
|
37
|
+
books.each do |book|
|
|
38
|
+
title = book.children.find { |n| n.name == 'title' }
|
|
39
|
+
author = book.children.find { |n| n.name == 'author' }
|
|
40
|
+
year = book.children.find { |n| n.name == 'year' }
|
|
41
|
+
|
|
42
|
+
puts " Book ##{book['id']}:"
|
|
43
|
+
puts " Title: #{title.text.strip}"
|
|
44
|
+
puts " Author: #{author.text.strip}"
|
|
45
|
+
puts " Year: #{year.text.strip}"
|
|
46
|
+
puts " ISBN: #{book['isbn']}"
|
|
47
|
+
puts
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Modify document
|
|
51
|
+
puts "4. Modifying the document..."
|
|
52
|
+
first_book = books.first
|
|
53
|
+
first_book['rating'] = '5 stars'
|
|
54
|
+
title = first_book.children.find { |n| n.name == 'title' }
|
|
55
|
+
puts " Added rating to first book: #{first_book['rating']}"
|
|
56
|
+
puts " First book title: #{title.text.strip}\n\n"
|
|
57
|
+
|
|
58
|
+
# Serialize back to XML
|
|
59
|
+
puts "5. Serializing to XML..."
|
|
60
|
+
xml_output = doc.to_xml
|
|
61
|
+
puts " ✓ Document serialized successfully"
|
|
62
|
+
puts "\n Output preview:"
|
|
63
|
+
puts " " + xml_output.lines.first(3).join(" ")
|
|
64
|
+
puts " ...\n\n"
|
|
65
|
+
|
|
66
|
+
# Nokogiri compatibility
|
|
67
|
+
puts "6. Testing Nokogiri compatibility..."
|
|
68
|
+
nokogiri_doc = Nokogiri.XML('<test><item>Hello World</item></test>')
|
|
69
|
+
puts " Parsed with Nokogiri.XML: #{nokogiri_doc.root.name}"
|
|
70
|
+
item = nokogiri_doc.root.children.find { |n| n.is_a?(Nokogiri::XML::Element) }
|
|
71
|
+
puts " Item text: #{item.text}"
|
|
72
|
+
puts " Document class: #{nokogiri_doc.class}"
|
|
73
|
+
puts " ✓ Nokogiri compatibility confirmed\n\n"
|
|
74
|
+
|
|
75
|
+
puts "=== Example Complete ==="
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
require 'rxerces'
|
|
4
|
+
|
|
5
|
+
puts "=== RXerces Simple Example (No Nokogiri) ===\n\n"
|
|
6
|
+
|
|
7
|
+
xml = <<-XML
|
|
8
|
+
<bookstore>
|
|
9
|
+
<book>
|
|
10
|
+
<title>1984</title>
|
|
11
|
+
<author>George Orwell</author>
|
|
12
|
+
</book>
|
|
13
|
+
</bookstore>
|
|
14
|
+
XML
|
|
15
|
+
|
|
16
|
+
# Parse using RXerces directly
|
|
17
|
+
doc = RXerces.XML(xml)
|
|
18
|
+
|
|
19
|
+
puts "Document parsed successfully!"
|
|
20
|
+
puts "Root element: #{doc.root.name}"
|
|
21
|
+
puts
|
|
22
|
+
|
|
23
|
+
# Find the book
|
|
24
|
+
book = doc.root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
25
|
+
title = book.children.find { |n| n.name == 'title' }
|
|
26
|
+
author = book.children.find { |n| n.name == 'author' }
|
|
27
|
+
|
|
28
|
+
puts "Book found:"
|
|
29
|
+
puts " Title: #{title.text.strip}"
|
|
30
|
+
puts " Author: #{author.text.strip}"
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
puts "=== Example Complete ===\n"
|
|
34
|
+
puts "Note: This example uses RXerces directly without Nokogiri compatibility."
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'rxerces'
|
|
2
|
+
require 'rxerces/nokogiri'
|
|
3
|
+
|
|
4
|
+
puts "=== RXerces XPath Example ===\n\n"
|
|
5
|
+
|
|
6
|
+
xml = <<-XML
|
|
7
|
+
<bookstore>
|
|
8
|
+
<book category="fiction">
|
|
9
|
+
<title>1984</title>
|
|
10
|
+
<author>George Orwell</author>
|
|
11
|
+
<year>1949</year>
|
|
12
|
+
<price>15.99</price>
|
|
13
|
+
</book>
|
|
14
|
+
<book category="fiction">
|
|
15
|
+
<title>Brave New World</title>
|
|
16
|
+
<author>Aldous Huxley</author>
|
|
17
|
+
<year>1932</year>
|
|
18
|
+
<price>14.99</price>
|
|
19
|
+
</book>
|
|
20
|
+
<book category="non-fiction">
|
|
21
|
+
<title>Sapiens</title>
|
|
22
|
+
<author>Yuval Noah Harari</author>
|
|
23
|
+
<year>2011</year>
|
|
24
|
+
<price>18.99</price>
|
|
25
|
+
</book>
|
|
26
|
+
</bookstore>
|
|
27
|
+
XML
|
|
28
|
+
|
|
29
|
+
doc = RXerces.XML(xml)
|
|
30
|
+
|
|
31
|
+
puts "1. Finding all books:"
|
|
32
|
+
books = doc.xpath('//book')
|
|
33
|
+
puts " Found #{books.length} books"
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
puts "2. Finding all titles:"
|
|
37
|
+
titles = doc.xpath('//title')
|
|
38
|
+
titles.each do |title|
|
|
39
|
+
puts " - #{title.text.strip}"
|
|
40
|
+
end
|
|
41
|
+
puts
|
|
42
|
+
|
|
43
|
+
puts "3. Finding all authors:"
|
|
44
|
+
authors = doc.xpath('//author')
|
|
45
|
+
authors.each do |author|
|
|
46
|
+
puts " - #{author.text.strip}"
|
|
47
|
+
end
|
|
48
|
+
puts
|
|
49
|
+
|
|
50
|
+
puts "4. Using absolute paths:"
|
|
51
|
+
bookstore_books = doc.xpath('/bookstore/book')
|
|
52
|
+
puts " Found #{bookstore_books.length} books via absolute path"
|
|
53
|
+
puts
|
|
54
|
+
|
|
55
|
+
puts "5. Querying from a specific node:"
|
|
56
|
+
first_book = books[0]
|
|
57
|
+
title = first_book.xpath('.//title').first
|
|
58
|
+
author = first_book.xpath('.//author').first
|
|
59
|
+
puts " First book: #{title.text.strip} by #{author.text.strip}"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
puts "6. Combining XPath with Ruby methods:"
|
|
63
|
+
puts " All books with their details:"
|
|
64
|
+
books.each_with_index do |book, i|
|
|
65
|
+
title_node = book.xpath('.//title').first
|
|
66
|
+
author_node = book.xpath('.//author').first
|
|
67
|
+
year_node = book.xpath('.//year').first
|
|
68
|
+
price_node = book.xpath('.//price').first
|
|
69
|
+
|
|
70
|
+
puts " Book #{i + 1}:"
|
|
71
|
+
puts " Title: #{title_node.text.strip}"
|
|
72
|
+
puts " Author: #{author_node.text.strip}"
|
|
73
|
+
puts " Year: #{year_node.text.strip}"
|
|
74
|
+
puts " Price: $#{price_node.text.strip}"
|
|
75
|
+
puts " Category: #{book['category']}"
|
|
76
|
+
puts
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts "7. Filtering with Ruby:"
|
|
80
|
+
puts " Fiction books only:"
|
|
81
|
+
fiction_books = books.select { |book| book['category'] == 'fiction' }
|
|
82
|
+
fiction_books.each do |book|
|
|
83
|
+
title = book.xpath('.//title').first
|
|
84
|
+
puts " - #{title.text.strip}"
|
|
85
|
+
end
|
|
86
|
+
puts
|
|
87
|
+
|
|
88
|
+
puts "8. Finding nested elements:"
|
|
89
|
+
all_prices = doc.xpath('//book/price')
|
|
90
|
+
puts " Found #{all_prices.length} prices:"
|
|
91
|
+
total = 0
|
|
92
|
+
all_prices.each do |price|
|
|
93
|
+
amount = price.text.strip.to_f
|
|
94
|
+
total += amount
|
|
95
|
+
puts " - $#{amount}"
|
|
96
|
+
end
|
|
97
|
+
puts " Total: $#{'%.2f' % total}"
|
|
98
|
+
puts
|
|
99
|
+
|
|
100
|
+
puts "9. Nokogiri compatibility:"
|
|
101
|
+
nokogiri_doc = Nokogiri.XML(xml)
|
|
102
|
+
nokogiri_books = nokogiri_doc.xpath('//book')
|
|
103
|
+
puts " Parsed with Nokogiri: #{nokogiri_books.length} books found"
|
|
104
|
+
puts
|
|
105
|
+
|
|
106
|
+
puts "=== Example Complete ==="
|
|
107
|
+
puts "\nNote: Xerces-C supports the XML Schema XPath subset."
|
|
108
|
+
puts "For advanced filtering, combine basic XPath with Ruby methods."
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'rxerces'
|
|
2
|
+
|
|
3
|
+
# Nokogiri compatibility module
|
|
4
|
+
# Provides drop-in replacement for Nokogiri XML parsing using RXerces
|
|
5
|
+
module Nokogiri
|
|
6
|
+
# Nokogiri-compatible XML module
|
|
7
|
+
module XML
|
|
8
|
+
# Parse XML from a string - delegates to RXerces
|
|
9
|
+
# @param string [String] XML string to parse
|
|
10
|
+
# @return [RXerces::XML::Document] parsed document
|
|
11
|
+
def self.parse(string)
|
|
12
|
+
RXerces::XML::Document.parse(string)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Alias Document class for compatibility
|
|
16
|
+
Document = RXerces::XML::Document
|
|
17
|
+
Node = RXerces::XML::Node
|
|
18
|
+
Element = RXerces::XML::Element
|
|
19
|
+
Text = RXerces::XML::Text
|
|
20
|
+
NodeSet = RXerces::XML::NodeSet
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Top-level parse method for compatibility
|
|
24
|
+
# @param string [String] XML string to parse
|
|
25
|
+
# @return [RXerces::XML::Document] parsed document
|
|
26
|
+
def self.XML(string)
|
|
27
|
+
RXerces::XML::Document.parse(string)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
alias_method :parse, :XML
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/rxerces/version.rb
CHANGED
data/lib/rxerces.rb
CHANGED
|
@@ -15,34 +15,3 @@ module RXerces
|
|
|
15
15
|
alias_method :parse, :XML
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
# Nokogiri compatibility module
|
|
20
|
-
module Nokogiri
|
|
21
|
-
# Nokogiri-compatible XML module
|
|
22
|
-
module XML
|
|
23
|
-
# Parse XML from a string - delegates to RXerces
|
|
24
|
-
# @param string [String] XML string to parse
|
|
25
|
-
# @return [RXerces::XML::Document] parsed document
|
|
26
|
-
def self.parse(string)
|
|
27
|
-
RXerces::XML::Document.parse(string)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Alias Document class for compatibility
|
|
31
|
-
Document = RXerces::XML::Document
|
|
32
|
-
Node = RXerces::XML::Node
|
|
33
|
-
Element = RXerces::XML::Element
|
|
34
|
-
Text = RXerces::XML::Text
|
|
35
|
-
NodeSet = RXerces::XML::NodeSet
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Top-level parse method for compatibility
|
|
39
|
-
# @param string [String] XML string to parse
|
|
40
|
-
# @return [RXerces::XML::Document] parsed document
|
|
41
|
-
def self.XML(string)
|
|
42
|
-
RXerces::XML::Document.parse(string)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
class << self
|
|
46
|
-
alias_method :parse, :XML
|
|
47
|
-
end
|
|
48
|
-
end
|
data/rxerces.gemspec
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Gem::Specification.new do |spec|
|
|
2
|
+
spec.name = "rxerces"
|
|
3
|
+
spec.version = "0.2.0"
|
|
4
|
+
spec.author = "Daniel J. Berger"
|
|
5
|
+
spec.email = "djberg96@gmail.com"
|
|
6
|
+
spec.cert_chain = ["certs/djberg96_pub.pem"]
|
|
7
|
+
spec.homepage = "http://github.com/djberg96/rxerces"
|
|
8
|
+
spec.summary = "Nokogiri-compatible XML library using Xerces-C"
|
|
9
|
+
spec.license = "MIT"
|
|
10
|
+
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
|
11
|
+
spec.test_files = Dir['spec/*_spec.rb']
|
|
12
|
+
spec.extensions = ["ext/rxerces/extconf.rb"]
|
|
13
|
+
|
|
14
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
15
|
+
|
|
16
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
17
|
+
spec.add_development_dependency "rake-compiler", "~> 1.2"
|
|
18
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
|
19
|
+
|
|
20
|
+
spec.description = "A Ruby XML library with Nokogiri-compatible API, powered by Xerces-C instead of libxml2"
|
|
21
|
+
|
|
22
|
+
spec.metadata = {
|
|
23
|
+
'homepage_uri' => 'https://github.com/djberg96/rxerces',
|
|
24
|
+
'bug_tracker_uri' => 'https://github.com/djberg96/rxerces/issues',
|
|
25
|
+
'changelog_uri' => 'https://github.com/djberg96/rxerces/blob/main/CHANGES.md',
|
|
26
|
+
'documentation_uri' => 'https://github.com/djberg96/rxerces/wiki',
|
|
27
|
+
'source_code_uri' => 'https://github.com/djberg96/rxerces',
|
|
28
|
+
'wiki_uri' => 'https://github.com/djberg96/rxerces/wiki',
|
|
29
|
+
'rubygems_mfa_required' => 'true',
|
|
30
|
+
'github_repo' => 'https://github.com/djberg96/rxerces',
|
|
31
|
+
'funding_uri' => 'https://github.com/sponsors/djberg96'
|
|
32
|
+
}
|
|
33
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces::XML::Document do
|
|
4
|
+
let(:simple_xml) { '<root><child>Hello</child></root>' }
|
|
5
|
+
let(:complex_xml) do
|
|
6
|
+
<<-XML
|
|
7
|
+
<root>
|
|
8
|
+
<person id="1" name="Alice">
|
|
9
|
+
<age>30</age>
|
|
10
|
+
<city>New York</city>
|
|
11
|
+
</person>
|
|
12
|
+
<person id="2" name="Bob">
|
|
13
|
+
<age>25</age>
|
|
14
|
+
<city>London</city>
|
|
15
|
+
</person>
|
|
16
|
+
</root>
|
|
17
|
+
XML
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe ".parse" do
|
|
21
|
+
it "parses simple XML" do
|
|
22
|
+
doc = RXerces::XML::Document.parse(simple_xml)
|
|
23
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "parses complex XML" do
|
|
27
|
+
doc = RXerces::XML::Document.parse(complex_xml)
|
|
28
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#root" do
|
|
33
|
+
it "returns the root element" do
|
|
34
|
+
doc = RXerces::XML::Document.parse(simple_xml)
|
|
35
|
+
root = doc.root
|
|
36
|
+
expect(root).to be_a(RXerces::XML::Element)
|
|
37
|
+
expect(root.name).to eq('root')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#to_s" do
|
|
42
|
+
it "serializes document to string" do
|
|
43
|
+
doc = RXerces::XML::Document.parse(simple_xml)
|
|
44
|
+
xml_string = doc.to_s
|
|
45
|
+
expect(xml_string).to be_a(String)
|
|
46
|
+
expect(xml_string).to include('<root>')
|
|
47
|
+
expect(xml_string).to include('<child>')
|
|
48
|
+
expect(xml_string).to include('Hello')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "#to_xml" do
|
|
53
|
+
it "is an alias for to_s" do
|
|
54
|
+
doc = RXerces::XML::Document.parse(simple_xml)
|
|
55
|
+
expect(doc.to_xml).to eq(doc.to_s)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "#xpath" do
|
|
60
|
+
it "returns a NodeSet" do
|
|
61
|
+
doc = RXerces::XML::Document.parse(simple_xml)
|
|
62
|
+
result = doc.xpath('//child')
|
|
63
|
+
expect(result).to be_a(RXerces::XML::NodeSet)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces::XML::Element do
|
|
4
|
+
let(:xml) { '<root><child id="1">Text</child></root>' }
|
|
5
|
+
let(:doc) { RXerces::XML::Document.parse(xml) }
|
|
6
|
+
let(:element) { doc.root }
|
|
7
|
+
|
|
8
|
+
it "is a subclass of Node" do
|
|
9
|
+
expect(element).to be_a(RXerces::XML::Node)
|
|
10
|
+
expect(element).to be_a(RXerces::XML::Element)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "has a name" do
|
|
14
|
+
expect(element.name).to eq('root')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "can have attributes" do
|
|
18
|
+
child = element.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
19
|
+
expect(child['id']).to eq('1')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "can have children" do
|
|
23
|
+
expect(element.children).not_to be_empty
|
|
24
|
+
end
|
|
25
|
+
end
|
data/spec/node_spec.rb
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces::XML::Node do
|
|
4
|
+
let(:xml) do
|
|
5
|
+
<<-XML
|
|
6
|
+
<root>
|
|
7
|
+
<person id="1" name="Alice">
|
|
8
|
+
<age>30</age>
|
|
9
|
+
<city>New York</city>
|
|
10
|
+
</person>
|
|
11
|
+
<person id="2" name="Bob">
|
|
12
|
+
<age>25</age>
|
|
13
|
+
</person>
|
|
14
|
+
</root>
|
|
15
|
+
XML
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
let(:doc) { RXerces::XML::Document.parse(xml) }
|
|
19
|
+
let(:root) { doc.root }
|
|
20
|
+
|
|
21
|
+
describe "#name" do
|
|
22
|
+
it "returns the node name" do
|
|
23
|
+
expect(root.name).to eq('root')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "returns child element names" do
|
|
27
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
28
|
+
expect(person.name).to eq('person')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#text" do
|
|
33
|
+
it "returns text content" do
|
|
34
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
35
|
+
age = person.children.find { |n| n.name == 'age' }
|
|
36
|
+
expect(age.text.strip).to eq('30')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "returns empty string for nodes without text" do
|
|
40
|
+
expect(root.text).to be_a(String)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#content" do
|
|
45
|
+
it "is an alias for text" do
|
|
46
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
47
|
+
age = person.children.find { |n| n.name == 'age' }
|
|
48
|
+
expect(age.content).to eq(age.text)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "#text=" do
|
|
53
|
+
it "sets text content" do
|
|
54
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
55
|
+
age = person.children.find { |n| n.name == 'age' }
|
|
56
|
+
age.text = '35'
|
|
57
|
+
expect(age.text.strip).to eq('35')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#[]" do
|
|
62
|
+
it "gets attribute value" do
|
|
63
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
64
|
+
expect(person['id']).to eq('1')
|
|
65
|
+
expect(person['name']).to eq('Alice')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "returns nil for non-existent attribute" do
|
|
69
|
+
expect(root['nonexistent']).to be_nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "#[]=" do
|
|
74
|
+
it "sets attribute value" do
|
|
75
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
76
|
+
person['id'] = '100'
|
|
77
|
+
expect(person['id']).to eq('100')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "creates new attribute" do
|
|
81
|
+
person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
82
|
+
person['email'] = 'alice@example.com'
|
|
83
|
+
expect(person['email']).to eq('alice@example.com')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "#children" do
|
|
88
|
+
it "returns an array of child nodes" do
|
|
89
|
+
children = root.children
|
|
90
|
+
expect(children).to be_an(Array)
|
|
91
|
+
expect(children.length).to be > 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "includes element nodes" do
|
|
95
|
+
person_nodes = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
|
|
96
|
+
expect(person_nodes.length).to eq(2)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "includes text nodes" do
|
|
100
|
+
text_nodes = root.children.select { |n| n.is_a?(RXerces::XML::Text) }
|
|
101
|
+
expect(text_nodes.length).to be > 0
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe "#xpath" do
|
|
106
|
+
it "returns a NodeSet" do
|
|
107
|
+
result = root.xpath('.//age')
|
|
108
|
+
expect(result).to be_a(RXerces::XML::NodeSet)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces::XML::NodeSet do
|
|
4
|
+
let(:xml) do
|
|
5
|
+
<<-XML
|
|
6
|
+
<root>
|
|
7
|
+
<item>First</item>
|
|
8
|
+
<item>Second</item>
|
|
9
|
+
<item>Third</item>
|
|
10
|
+
</root>
|
|
11
|
+
XML
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:doc) { RXerces::XML::Document.parse(xml) }
|
|
15
|
+
let(:nodeset) { doc.xpath('//item') }
|
|
16
|
+
let(:empty_nodeset) { doc.xpath('//nonexistent') }
|
|
17
|
+
|
|
18
|
+
describe "#length" do
|
|
19
|
+
it "returns the number of nodes" do
|
|
20
|
+
expect(nodeset.length).to be_a(Integer)
|
|
21
|
+
expect(nodeset.length).to eq(3)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns 0 for empty nodeset" do
|
|
25
|
+
expect(empty_nodeset.length).to eq(0)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#size" do
|
|
30
|
+
it "is an alias for length" do
|
|
31
|
+
expect(nodeset.size).to eq(nodeset.length)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "#[]" do
|
|
36
|
+
it "returns node at index" do
|
|
37
|
+
item = nodeset[0]
|
|
38
|
+
expect(item).to be_a(RXerces::XML::Element)
|
|
39
|
+
expect(item.text.strip).to eq('First')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns nil for out of bounds index" do
|
|
43
|
+
expect(nodeset[999]).to be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "returns nil for empty nodeset" do
|
|
47
|
+
expect(empty_nodeset[0]).to be_nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "#each" do
|
|
52
|
+
it "is enumerable" do
|
|
53
|
+
expect(nodeset).to respond_to(:each)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns enumerator when no block given" do
|
|
57
|
+
expect(nodeset.each).to be_a(Enumerator)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "yields all items in nodeset" do
|
|
61
|
+
count = 0
|
|
62
|
+
nodeset.each { count += 1 }
|
|
63
|
+
expect(count).to eq(3)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "yields nothing for empty nodeset" do
|
|
67
|
+
count = 0
|
|
68
|
+
empty_nodeset.each { count += 1 }
|
|
69
|
+
expect(count).to eq(0)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "can iterate and access node properties" do
|
|
73
|
+
texts = []
|
|
74
|
+
nodeset.each do |item|
|
|
75
|
+
texts << item.text.strip
|
|
76
|
+
end
|
|
77
|
+
expect(texts).to eq(['First', 'Second', 'Third'])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "#to_a" do
|
|
82
|
+
it "converts to array" do
|
|
83
|
+
result = nodeset.to_a
|
|
84
|
+
expect(result).to be_an(Array)
|
|
85
|
+
expect(result.length).to eq(3)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "includes Enumerable" do
|
|
90
|
+
expect(RXerces::XML::NodeSet.ancestors).to include(Enumerable)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rxerces/nokogiri'
|
|
3
|
+
|
|
4
|
+
RSpec.describe "Nokogiri compatibility" do
|
|
5
|
+
let(:simple_xml) { '<root><child>Hello</child></root>' }
|
|
6
|
+
|
|
7
|
+
describe "Nokogiri module" do
|
|
8
|
+
it "exists" do
|
|
9
|
+
expect(defined?(Nokogiri)).to eq('constant')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".XML" do
|
|
13
|
+
it "parses XML" do
|
|
14
|
+
doc = Nokogiri.XML(simple_xml)
|
|
15
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe ".parse" do
|
|
20
|
+
it "is an alias for .XML" do
|
|
21
|
+
doc = Nokogiri.parse(simple_xml)
|
|
22
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "Nokogiri::XML" do
|
|
28
|
+
it "exists" do
|
|
29
|
+
expect(defined?(Nokogiri::XML)).to eq('constant')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe ".parse" do
|
|
33
|
+
it "parses XML" do
|
|
34
|
+
doc = Nokogiri::XML.parse(simple_xml)
|
|
35
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "Nokogiri::XML::Document" do
|
|
41
|
+
it "is an alias for RXerces::XML::Document" do
|
|
42
|
+
expect(Nokogiri::XML::Document).to eq(RXerces::XML::Document)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "Nokogiri::XML::Node" do
|
|
47
|
+
it "is an alias for RXerces::XML::Node" do
|
|
48
|
+
expect(Nokogiri::XML::Node).to eq(RXerces::XML::Node)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "Nokogiri::XML::Element" do
|
|
53
|
+
it "is an alias for RXerces::XML::Element" do
|
|
54
|
+
expect(Nokogiri::XML::Element).to eq(RXerces::XML::Element)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "Nokogiri::XML::NodeSet" do
|
|
59
|
+
it "is an alias for RXerces::XML::NodeSet" do
|
|
60
|
+
expect(Nokogiri::XML::NodeSet).to eq(RXerces::XML::NodeSet)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "API compatibility" do
|
|
65
|
+
let(:doc) { Nokogiri.XML(simple_xml) }
|
|
66
|
+
|
|
67
|
+
it "provides root method" do
|
|
68
|
+
expect(doc.root).to be_a(Nokogiri::XML::Element)
|
|
69
|
+
expect(doc.root.name).to eq('root')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "provides to_s method" do
|
|
73
|
+
xml_string = doc.to_s
|
|
74
|
+
expect(xml_string).to be_a(String)
|
|
75
|
+
expect(xml_string).to include('<root>')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "provides to_xml method" do
|
|
79
|
+
xml_string = doc.to_xml
|
|
80
|
+
expect(xml_string).to be_a(String)
|
|
81
|
+
expect(xml_string).to include('<root>')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "provides node name method" do
|
|
85
|
+
expect(doc.root.name).to eq('root')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "provides node text method" do
|
|
89
|
+
child = doc.root.children.find { |n| n.is_a?(Nokogiri::XML::Element) }
|
|
90
|
+
expect(child.text).to be_a(String)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "provides node children method" do
|
|
94
|
+
children = doc.root.children
|
|
95
|
+
expect(children).to be_an(Array)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces do
|
|
4
|
+
it "has a version number" do
|
|
5
|
+
expect(RXerces::VERSION).not_to be nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe ".XML" do
|
|
9
|
+
it "parses XML string" do
|
|
10
|
+
xml = '<root><child>text</child></root>'
|
|
11
|
+
doc = RXerces.XML(xml)
|
|
12
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe ".parse" do
|
|
17
|
+
it "is an alias for .XML" do
|
|
18
|
+
xml = '<root><child>text</child></root>'
|
|
19
|
+
doc = RXerces.parse(xml)
|
|
20
|
+
expect(doc).to be_a(RXerces::XML::Document)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
require 'rxerces'
|
|
3
|
+
require 'rxerces_shared'
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |config|
|
|
6
|
+
# Enable flags like --only-failures and --next-failure
|
|
7
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
|
8
|
+
|
|
9
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
10
|
+
config.disable_monkey_patching!
|
|
11
|
+
|
|
12
|
+
config.expect_with :rspec do |c|
|
|
13
|
+
c.syntax = :expect
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
config.include_context(RXerces)
|
|
17
|
+
end
|
data/spec/xpath_spec.rb
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rxerces/nokogiri'
|
|
3
|
+
|
|
4
|
+
RSpec.describe "XPath support" do
|
|
5
|
+
let(:xml) do
|
|
6
|
+
<<-XML
|
|
7
|
+
<library>
|
|
8
|
+
<book id="1" category="fiction">
|
|
9
|
+
<title>1984</title>
|
|
10
|
+
<author>George Orwell</author>
|
|
11
|
+
<year>1949</year>
|
|
12
|
+
<price>15.99</price>
|
|
13
|
+
</book>
|
|
14
|
+
<book id="2" category="fiction">
|
|
15
|
+
<title>Brave New World</title>
|
|
16
|
+
<author>Aldous Huxley</author>
|
|
17
|
+
<year>1932</year>
|
|
18
|
+
<price>14.99</price>
|
|
19
|
+
</book>
|
|
20
|
+
<book id="3" category="non-fiction">
|
|
21
|
+
<title>Sapiens</title>
|
|
22
|
+
<author>Yuval Noah Harari</author>
|
|
23
|
+
<year>2011</year>
|
|
24
|
+
<price>18.99</price>
|
|
25
|
+
</book>
|
|
26
|
+
</library>
|
|
27
|
+
XML
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:doc) { RXerces::XML::Document.parse(xml) }
|
|
31
|
+
|
|
32
|
+
describe "Document XPath queries" do
|
|
33
|
+
it "finds all book elements" do
|
|
34
|
+
books = doc.xpath('//book')
|
|
35
|
+
expect(books).to be_a(RXerces::XML::NodeSet)
|
|
36
|
+
expect(books.length).to eq(3)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "finds all title elements" do
|
|
40
|
+
titles = doc.xpath('//title')
|
|
41
|
+
expect(titles.length).to eq(3)
|
|
42
|
+
expect(titles[0].text.strip).to eq('1984')
|
|
43
|
+
expect(titles[1].text.strip).to eq('Brave New World')
|
|
44
|
+
expect(titles[2].text.strip).to eq('Sapiens')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "finds elements by path" do
|
|
48
|
+
authors = doc.xpath('/library/book/author')
|
|
49
|
+
expect(authors.length).to eq(3)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "finds descendant elements" do
|
|
53
|
+
years = doc.xpath('//year')
|
|
54
|
+
expect(years.length).to eq(3)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "finds price elements" do
|
|
58
|
+
prices = doc.xpath('//price')
|
|
59
|
+
expect(prices.length).to eq(3)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns empty nodeset for non-matching xpath" do
|
|
63
|
+
result = doc.xpath('//nonexistent')
|
|
64
|
+
expect(result).to be_a(RXerces::XML::NodeSet)
|
|
65
|
+
expect(result.length).to eq(0)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "can find nested elements" do
|
|
69
|
+
library_books = doc.xpath('/library/book')
|
|
70
|
+
expect(library_books.length).to eq(3)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "Node XPath queries" do
|
|
75
|
+
let(:root) { doc.root }
|
|
76
|
+
|
|
77
|
+
it "finds children from root element" do
|
|
78
|
+
books = root.xpath('.//book')
|
|
79
|
+
expect(books.length).to eq(3)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "finds specific child elements" do
|
|
83
|
+
first_book = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
84
|
+
titles = first_book.xpath('.//title')
|
|
85
|
+
expect(titles.length).to eq(1)
|
|
86
|
+
expect(titles[0].text.strip).to eq('1984')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "finds descendant elements from node" do
|
|
90
|
+
first_book = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
|
|
91
|
+
author = first_book.xpath('.//author')
|
|
92
|
+
expect(author.length).to eq(1)
|
|
93
|
+
expect(author[0].text.strip).to eq('George Orwell')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "can use relative paths" do
|
|
97
|
+
books = root.xpath('book')
|
|
98
|
+
expect(books.length).to eq(3)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "finds all descendants" do
|
|
102
|
+
all_titles = root.xpath('.//title')
|
|
103
|
+
expect(all_titles.length).to eq(3)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe "Error handling" do
|
|
108
|
+
it "raises error for invalid XPath" do
|
|
109
|
+
expect {
|
|
110
|
+
doc.xpath('//[invalid')
|
|
111
|
+
}.to raise_error(RuntimeError, /XPath error/)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "Nokogiri compatibility" do
|
|
116
|
+
it "works with Nokogiri syntax" do
|
|
117
|
+
nokogiri_doc = Nokogiri::XML(xml)
|
|
118
|
+
books = nokogiri_doc.xpath('//book')
|
|
119
|
+
expect(books).to be_a(Nokogiri::XML::NodeSet)
|
|
120
|
+
expect(books.length).to eq(3)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "can chain xpath on results" do
|
|
124
|
+
books = doc.xpath('//book')
|
|
125
|
+
expect(books.length).to eq(3)
|
|
126
|
+
|
|
127
|
+
# Access first book's title
|
|
128
|
+
first_book = books[0]
|
|
129
|
+
titles = first_book.xpath('.//title')
|
|
130
|
+
expect(titles.length).to eq(1)
|
|
131
|
+
expect(titles[0].text.strip).to eq('1984')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "can iterate over xpath results" do
|
|
135
|
+
authors = doc.xpath('//author')
|
|
136
|
+
author_names = []
|
|
137
|
+
authors.each do |author|
|
|
138
|
+
author_names << author.text.strip
|
|
139
|
+
end
|
|
140
|
+
expect(author_names).to include('George Orwell', 'Aldous Huxley', 'Yuval Noah Harari')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "XPath limitations" do
|
|
145
|
+
it "notes that Xerces-C uses XML Schema XPath subset" do
|
|
146
|
+
# Xerces-C implements the XML Schema XPath subset, not full XPath 1.0
|
|
147
|
+
# This means the following are NOT supported:
|
|
148
|
+
# - Attribute predicates like [@id="1"]
|
|
149
|
+
# - Functions like last(), position(), text()
|
|
150
|
+
# - Comparison operators in predicates
|
|
151
|
+
#
|
|
152
|
+
# However, basic path expressions work well:
|
|
153
|
+
# - // (descendant-or-self)
|
|
154
|
+
# - / (child)
|
|
155
|
+
# - . (self)
|
|
156
|
+
# - .. (parent)
|
|
157
|
+
|
|
158
|
+
# Basic paths work
|
|
159
|
+
expect(doc.xpath('//book').length).to eq(3)
|
|
160
|
+
expect(doc.xpath('/library/book').length).to eq(3)
|
|
161
|
+
expect(doc.xpath('//title').length).to eq(3)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data.tar.gz.sig
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rxerces
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Daniel J. Berger
|
|
8
8
|
bindir: bin
|
|
9
|
-
cert_chain:
|
|
9
|
+
cert_chain:
|
|
10
|
+
- |
|
|
11
|
+
-----BEGIN CERTIFICATE-----
|
|
12
|
+
MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
|
|
13
|
+
cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
|
|
14
|
+
MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
|
|
15
|
+
ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
|
|
16
|
+
bTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h
|
|
17
|
+
A+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj
|
|
18
|
+
u95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph
|
|
19
|
+
75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO
|
|
20
|
+
6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL
|
|
21
|
+
iLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3
|
|
22
|
+
ctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq
|
|
23
|
+
74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq
|
|
24
|
+
058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV
|
|
25
|
+
HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e
|
|
26
|
+
AzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq
|
|
27
|
+
YmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq
|
|
28
|
+
/3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ
|
|
29
|
+
h9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY
|
|
30
|
+
6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH
|
|
31
|
+
ks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE
|
|
32
|
+
1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf
|
|
33
|
+
DYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR
|
|
34
|
+
ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
|
|
35
|
+
WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
|
|
36
|
+
-----END CERTIFICATE-----
|
|
10
37
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
38
|
dependencies:
|
|
12
39
|
- !ruby/object:Gem::Dependency
|
|
@@ -53,25 +80,50 @@ dependencies:
|
|
|
53
80
|
version: '3.12'
|
|
54
81
|
description: A Ruby XML library with Nokogiri-compatible API, powered by Xerces-C
|
|
55
82
|
instead of libxml2
|
|
56
|
-
email:
|
|
57
|
-
- contributors@example.com
|
|
83
|
+
email: djberg96@gmail.com
|
|
58
84
|
executables: []
|
|
59
85
|
extensions:
|
|
60
86
|
- ext/rxerces/extconf.rb
|
|
61
87
|
extra_rdoc_files: []
|
|
62
88
|
files:
|
|
89
|
+
- CHANGES.md
|
|
90
|
+
- Gemfile
|
|
63
91
|
- LICENSE
|
|
64
92
|
- README.md
|
|
93
|
+
- Rakefile
|
|
94
|
+
- certs/djberg96_pub.pem
|
|
95
|
+
- examples/basic_usage.rb
|
|
96
|
+
- examples/simple_example.rb
|
|
97
|
+
- examples/xpath_example.rb
|
|
65
98
|
- ext/rxerces/extconf.rb
|
|
66
99
|
- ext/rxerces/rxerces.cpp
|
|
67
100
|
- ext/rxerces/rxerces.h
|
|
68
101
|
- lib/rxerces.rb
|
|
69
|
-
- lib/rxerces/
|
|
102
|
+
- lib/rxerces/nokogiri.rb
|
|
70
103
|
- lib/rxerces/version.rb
|
|
71
|
-
|
|
104
|
+
- rxerces.gemspec
|
|
105
|
+
- spec/document_spec.rb
|
|
106
|
+
- spec/element_spec.rb
|
|
107
|
+
- spec/node_spec.rb
|
|
108
|
+
- spec/nodeset_spec.rb
|
|
109
|
+
- spec/nokogiri_compatibility_spec.rb
|
|
110
|
+
- spec/rxerces_shared.rb
|
|
111
|
+
- spec/rxerces_spec.rb
|
|
112
|
+
- spec/spec_helper.rb
|
|
113
|
+
- spec/xpath_spec.rb
|
|
114
|
+
homepage: http://github.com/djberg96/rxerces
|
|
72
115
|
licenses:
|
|
73
116
|
- MIT
|
|
74
|
-
metadata:
|
|
117
|
+
metadata:
|
|
118
|
+
homepage_uri: https://github.com/djberg96/rxerces
|
|
119
|
+
bug_tracker_uri: https://github.com/djberg96/rxerces/issues
|
|
120
|
+
changelog_uri: https://github.com/djberg96/rxerces/blob/main/CHANGES.md
|
|
121
|
+
documentation_uri: https://github.com/djberg96/rxerces/wiki
|
|
122
|
+
source_code_uri: https://github.com/djberg96/rxerces
|
|
123
|
+
wiki_uri: https://github.com/djberg96/rxerces/wiki
|
|
124
|
+
rubygems_mfa_required: 'true'
|
|
125
|
+
github_repo: https://github.com/djberg96/rxerces
|
|
126
|
+
funding_uri: https://github.com/sponsors/djberg96
|
|
75
127
|
rdoc_options: []
|
|
76
128
|
require_paths:
|
|
77
129
|
- lib
|
|
@@ -89,4 +141,11 @@ requirements: []
|
|
|
89
141
|
rubygems_version: 3.6.9
|
|
90
142
|
specification_version: 4
|
|
91
143
|
summary: Nokogiri-compatible XML library using Xerces-C
|
|
92
|
-
test_files:
|
|
144
|
+
test_files:
|
|
145
|
+
- spec/document_spec.rb
|
|
146
|
+
- spec/element_spec.rb
|
|
147
|
+
- spec/node_spec.rb
|
|
148
|
+
- spec/nodeset_spec.rb
|
|
149
|
+
- spec/nokogiri_compatibility_spec.rb
|
|
150
|
+
- spec/rxerces_spec.rb
|
|
151
|
+
- spec/xpath_spec.rb
|
metadata.gz.sig
ADDED
data/lib/rxerces/rxerces.bundle
DELETED
|
Binary file
|