dozuki 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CI +13 -0
- data/Gemfile.lock +21 -20
- data/{README.rdoc → README.markdown} +48 -37
- data/Rakefile +17 -0
- data/dozuki.gemspec +4 -3
- data/features/each_accessor.feature +22 -50
- data/features/exists_accessor.feature +3 -4
- data/features/float_accessor.feature +22 -28
- data/features/get_accessor.feature +17 -46
- data/features/int_accessor.feature +22 -28
- data/features/steps/collection_steps.rb +11 -0
- data/features/steps/error_steps.rb +11 -0
- data/features/steps/format_steps.rb +19 -0
- data/features/steps/xml_steps.rb +4 -53
- data/features/string_accessor.feature +16 -27
- data/lib/dozuki.rb +5 -1
- data/lib/dozuki/exceptions.rb +23 -0
- data/lib/dozuki/node.rb +53 -0
- data/lib/dozuki/node_collection.rb +25 -0
- data/lib/dozuki/parsers.rb +29 -0
- data/lib/dozuki/version.rb +1 -1
- data/lib/dozuki/xml.rb +3 -8
- data/spec/dozuki/node_collection_spec.rb +114 -0
- data/spec/dozuki/{xml/node_spec.rb → node_spec.rb} +40 -40
- data/spec/dozuki/{xml/parser_spec.rb → parsers_spec.rb} +37 -29
- data/spec/dozuki/xml_spec.rb +14 -11
- metadata +33 -33
- data/lib/dozuki/xml/exceptions.rb +0 -27
- data/lib/dozuki/xml/node.rb +0 -54
- data/lib/dozuki/xml/node_collection.rb +0 -27
- data/lib/dozuki/xml/parser.rb +0 -23
- data/spec/dozuki/xml/node_collection_spec.rb +0 -117
data/CI
ADDED
data/Gemfile.lock
CHANGED
@@ -7,34 +7,35 @@ PATH
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
9
9
|
specs:
|
10
|
-
ZenTest (4.
|
10
|
+
ZenTest (4.5.0)
|
11
11
|
archive-tar-minitar (0.5.2)
|
12
12
|
autotest (4.4.6)
|
13
13
|
ZenTest (>= 4.4.1)
|
14
14
|
builder (3.0.0)
|
15
15
|
columnize (0.3.2)
|
16
|
-
cucumber (0.10.
|
16
|
+
cucumber (0.10.2)
|
17
17
|
builder (>= 2.1.2)
|
18
|
-
diff-lcs (
|
19
|
-
gherkin (
|
20
|
-
json (
|
21
|
-
term-ansicolor (
|
18
|
+
diff-lcs (>= 1.1.2)
|
19
|
+
gherkin (>= 2.3.5)
|
20
|
+
json (>= 1.4.6)
|
21
|
+
term-ansicolor (>= 1.0.5)
|
22
22
|
diff-lcs (1.1.2)
|
23
|
-
gherkin (2.3.
|
24
|
-
json (
|
25
|
-
json (1.
|
26
|
-
linecache19 (0.5.
|
23
|
+
gherkin (2.3.6)
|
24
|
+
json (>= 1.4.6)
|
25
|
+
json (1.5.1)
|
26
|
+
linecache19 (0.5.12)
|
27
27
|
ruby_core_source (>= 0.1.4)
|
28
28
|
nokogiri (1.4.4)
|
29
|
-
|
30
|
-
|
31
|
-
rspec-
|
32
|
-
rspec-
|
33
|
-
|
34
|
-
rspec-
|
29
|
+
rake (0.8.7)
|
30
|
+
rspec (2.5.0)
|
31
|
+
rspec-core (~> 2.5.0)
|
32
|
+
rspec-expectations (~> 2.5.0)
|
33
|
+
rspec-mocks (~> 2.5.0)
|
34
|
+
rspec-core (2.5.1)
|
35
|
+
rspec-expectations (2.5.0)
|
35
36
|
diff-lcs (~> 1.1.2)
|
36
|
-
rspec-mocks (2.
|
37
|
-
ruby-debug-base19 (0.11.
|
37
|
+
rspec-mocks (2.5.0)
|
38
|
+
ruby-debug-base19 (0.11.25)
|
38
39
|
columnize (>= 0.3.1)
|
39
40
|
linecache19 (>= 0.5.11)
|
40
41
|
ruby_core_source (>= 0.1.4)
|
@@ -42,7 +43,7 @@ GEM
|
|
42
43
|
columnize (>= 0.3.1)
|
43
44
|
linecache19 (>= 0.5.11)
|
44
45
|
ruby-debug-base19 (>= 0.11.19)
|
45
|
-
ruby_core_source (0.1.
|
46
|
+
ruby_core_source (0.1.5)
|
46
47
|
archive-tar-minitar (>= 0.5.2)
|
47
48
|
term-ansicolor (1.0.5)
|
48
49
|
|
@@ -53,6 +54,6 @@ DEPENDENCIES
|
|
53
54
|
autotest
|
54
55
|
cucumber
|
55
56
|
dozuki!
|
56
|
-
|
57
|
+
rake
|
57
58
|
rspec
|
58
59
|
ruby-debug19
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# Dozuki
|
2
2
|
|
3
3
|
A Nokogiri wrapper that simplifies commonly occurring tasks.
|
4
4
|
|
5
|
-
|
5
|
+
[![Build Status](http://travis-ci.org/JamesAlmond/dozuki.png)](http://travis-ci.org/JamesAlmond/dozuki)
|
6
|
+
|
7
|
+
## What does it do?
|
6
8
|
|
7
9
|
Dozuki removes the repetitive tasks from parsing XML documents with XPaths such as:
|
8
10
|
|
@@ -14,73 +16,82 @@ Dozuki removes the repetitive tasks from parsing XML documents with XPaths such
|
|
14
16
|
|
15
17
|
It's mainly sugar for reducing the amount of chaining on calls like:
|
16
18
|
|
17
|
-
|
19
|
+
doc.xpath('/my/xpath').first.to_i
|
20
|
+
|
21
|
+
to:
|
18
22
|
|
19
|
-
|
23
|
+
doc.int('/my/xpath')
|
20
24
|
|
21
|
-
|
25
|
+
## How do I use it?
|
22
26
|
|
23
|
-
|
27
|
+
The parse method supports all input parameters supported by [Nokogiri's
|
28
|
+
parse method](http://nokogiri.org/Nokogiri/XML/Document.html#parse):
|
29
|
+
|
30
|
+
# simple usage
|
31
|
+
doc = Dozuki::XML.parse(string_or_io)
|
32
|
+
|
33
|
+
# or the full parameter list
|
34
|
+
doc = Dozuki::XML.parse(string_or_io, url = nil, encoding = nil, options = ParseOptions::DEFAULT_XML)
|
24
35
|
|
25
36
|
This documents supports the Dozuki extensions for:
|
26
37
|
|
27
|
-
|
38
|
+
### Extracting a single node
|
28
39
|
|
29
40
|
The get methods takes an xpath and returns the first node that matches the xpath:
|
30
41
|
|
31
|
-
|
42
|
+
doc.get('/my/xpath')
|
32
43
|
|
33
44
|
If the node can't be found then an exception is raised.
|
34
45
|
|
35
|
-
|
46
|
+
### Extracting a single node of a certain type
|
36
47
|
|
37
48
|
The following methods take the first node that matches the xpath and returns the formatted result:
|
38
49
|
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
doc.string('/my/xpath') # surrounding whitespace stripped
|
51
|
+
doc.float('/my/xpath')
|
52
|
+
doc.int('/my/xpath')
|
42
53
|
|
43
54
|
These functions are to replace calls using plain Nokogiri such as:
|
44
55
|
|
45
56
|
doc.xpath('/my/xpath').first.to_i
|
46
57
|
|
47
|
-
|
58
|
+
### Checking whether an element exists
|
48
59
|
|
49
|
-
|
60
|
+
doc.exists?('/my/xpath')
|
50
61
|
|
51
|
-
|
62
|
+
### Iterating through nodes
|
52
63
|
|
53
64
|
Dozuki also provides a slightly more succinct way to 'each' an xpath:
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
doc.each('/my/xpath') do |node|
|
67
|
+
# do something
|
68
|
+
end
|
58
69
|
|
59
|
-
|
70
|
+
## Iterating through node text and parsing
|
60
71
|
|
61
72
|
There are also simple ways to extract formatted text of a series of nodes with an each
|
62
73
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
doc.each('/my/xpath').as_string do |node|
|
75
|
+
# string with surrounding whitespace stripped
|
76
|
+
end
|
77
|
+
|
78
|
+
doc.each('/my/xpath').as_int do |node|
|
79
|
+
# int
|
80
|
+
end
|
81
|
+
|
82
|
+
doc.each('/my/xpath').as_float do |node|
|
83
|
+
# float
|
84
|
+
end
|
74
85
|
|
75
|
-
|
86
|
+
## Playing nicely with Nokogiri
|
76
87
|
|
77
88
|
Dozuki will proxy any calls not recognised onto the underlying Nokogiri structure, including responds_to?, allowing you to treat it like any other Nokogiri document.
|
78
89
|
|
79
|
-
|
90
|
+
## More documentation
|
80
91
|
|
81
|
-
More features are described in the..
|
92
|
+
More features are described in the.. [features](https://github.com/jamesalmond/dozuki/tree/master/features)
|
82
93
|
|
83
|
-
|
94
|
+
## Contributing to Dozuki
|
84
95
|
|
85
96
|
* Fork the project.
|
86
97
|
* Add tests that cover the new feature or bug fix.
|
@@ -88,13 +99,13 @@ More features are described in the.. {features}[https://github.com/jamesalmond/d
|
|
88
99
|
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
|
89
100
|
* Send me a pull request. Bonus points for topic branches.
|
90
101
|
|
91
|
-
|
102
|
+
## LICENSE:
|
92
103
|
|
93
104
|
(The MIT License)
|
94
105
|
|
95
106
|
Copyright (c) 2010:
|
96
107
|
|
97
|
-
*
|
108
|
+
* [James Almond](http://jamesalmond.com)
|
98
109
|
|
99
110
|
Permission is hereby granted, free of charge, to any person obtaining
|
100
111
|
a copy of this software and associated documentation files (the
|
@@ -113,4 +124,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
113
124
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
114
125
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
115
126
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
116
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
127
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,2 +1,19 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'cucumber'
|
7
|
+
require 'cucumber/rake/task'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
desc "Run specs"
|
12
|
+
RSpec::Core::RakeTask.new :spec
|
13
|
+
|
14
|
+
desc "Run integrations"
|
15
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
16
|
+
t.cucumber_opts = "features --format pretty"
|
17
|
+
end
|
18
|
+
task :default => [:spec, :features]
|
19
|
+
|
data/dozuki.gemspec
CHANGED
@@ -18,12 +18,13 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- {spec,features}/* .autotest`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
-
|
21
|
+
|
22
22
|
s.add_dependency("nokogiri")
|
23
|
-
|
23
|
+
|
24
24
|
s.add_development_dependency("rspec")
|
25
25
|
s.add_development_dependency("cucumber")
|
26
26
|
s.add_development_dependency("ruby-debug19")
|
27
27
|
s.add_development_dependency("autotest")
|
28
|
-
|
28
|
+
s.add_development_dependency("rake")
|
29
|
+
|
29
30
|
end
|
@@ -2,71 +2,43 @@ Feature: Iterating through nodes
|
|
2
2
|
In order to provide simpler way of accessing groups of nods
|
3
3
|
As a traverser
|
4
4
|
I want to access nodes using the each method and an xpath
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I have parsed the XML:
|
8
8
|
"""
|
9
9
|
<root>
|
10
10
|
<name>St. George's Arms</name>
|
11
|
-
<average_price>20.32</average_price>
|
12
|
-
<number_of_beers>2</number_of_beers>
|
13
11
|
<rooms>
|
14
12
|
<room>SINGLE</room>
|
15
13
|
<room>Double</room>
|
16
14
|
</rooms>
|
15
|
+
<room_numbers>
|
16
|
+
<number>5</number>
|
17
|
+
<number>7</number>
|
18
|
+
</room_numbers>
|
19
|
+
<prices>
|
20
|
+
<price>53.50</price>
|
21
|
+
<price>799.78</price>
|
22
|
+
</prices>
|
17
23
|
</root>
|
18
24
|
"""
|
19
|
-
|
25
|
+
|
26
|
+
Scenario: using each to traverse a document
|
27
|
+
When I call "each('/root/rooms/room')" on the document and collect the results
|
20
28
|
Then the results should contain a node with the text "SINGLE"
|
21
29
|
And the results should contain a node with the text "Double"
|
22
|
-
|
30
|
+
|
23
31
|
Scenario: using each to traverse a document and getting the string elements
|
24
|
-
When I
|
25
|
-
"""
|
26
|
-
<root>
|
27
|
-
<name>St. George's Arms</name>
|
28
|
-
<average_price>20.32</average_price>
|
29
|
-
<number_of_beers>2</number_of_beers>
|
30
|
-
<rooms>
|
31
|
-
<room>SINGLE</room>
|
32
|
-
<room>Double</room>
|
33
|
-
</rooms>
|
34
|
-
</root>
|
35
|
-
"""
|
36
|
-
And I call "each('/root/rooms/room').as_string" on the document and collect the results
|
32
|
+
When I call "each('/root/rooms/room').as_string" on the document and collect the results
|
37
33
|
Then the results should contain "SINGLE"
|
38
34
|
And the results should contain "Double"
|
39
|
-
|
35
|
+
|
40
36
|
Scenario: using each to traverse a document and getting the integer elements
|
41
|
-
When I
|
42
|
-
"""
|
43
|
-
<root>
|
44
|
-
<name>St. George's Arms</name>
|
45
|
-
<average_price>20.32</average_price>
|
46
|
-
<number_of_beers>2</number_of_beers>
|
47
|
-
<rooms>
|
48
|
-
<room>5</room>
|
49
|
-
<room>7</room>
|
50
|
-
</rooms>
|
51
|
-
</root>
|
52
|
-
"""
|
53
|
-
And I call "each('/root/rooms/room').as_int" on the document and collect the results
|
37
|
+
When I call "each('/root/room_numbers/number').as_int" on the document and collect the results
|
54
38
|
Then the results should contain 5
|
55
|
-
And the results should contain
|
56
|
-
|
39
|
+
And the results should contain 7
|
40
|
+
|
57
41
|
Scenario: using each to traverse a document and getting the float elements
|
58
|
-
When I
|
59
|
-
"""
|
60
|
-
<root>
|
61
|
-
<name>St. George's Arms</name>
|
62
|
-
<average_price>20.32</average_price>
|
63
|
-
<number_of_beers>2</number_of_beers>
|
64
|
-
<rooms>
|
65
|
-
<room>53.50</room>
|
66
|
-
<room>799.78</room>
|
67
|
-
</rooms>
|
68
|
-
</root>
|
69
|
-
"""
|
70
|
-
And I call "each('/root/rooms/room').as_float" on the document and collect the results
|
42
|
+
When I call "each('/root/prices/price').as_float" on the document and collect the results
|
71
43
|
Then the results should contain 53.50
|
72
|
-
And the results should contain 799.78
|
44
|
+
And the results should contain 799.78
|
@@ -2,8 +2,7 @@ Feature: Exists accessor
|
|
2
2
|
In order to easily determine whether a node exists in a document
|
3
3
|
As a traverser
|
4
4
|
I want to check whether a node exists
|
5
|
-
|
6
|
-
|
5
|
+
|
7
6
|
Scenario: the node exists
|
8
7
|
When I parse the XML:
|
9
8
|
"""
|
@@ -15,7 +14,7 @@ Feature: Exists accessor
|
|
15
14
|
"""
|
16
15
|
And I call "exists?('/root/number_of_beers')" on the document
|
17
16
|
Then the result should be true
|
18
|
-
|
17
|
+
|
19
18
|
Scenario: the node doesn't exist
|
20
19
|
When I parse the XML:
|
21
20
|
"""
|
@@ -26,4 +25,4 @@ Feature: Exists accessor
|
|
26
25
|
</root>
|
27
26
|
"""
|
28
27
|
And I call "exists?('/root/food')" on the document
|
29
|
-
Then the result should be false
|
28
|
+
Then the result should be false
|
@@ -2,43 +2,37 @@ Feature: Getting floats from the document
|
|
2
2
|
In order to provide simpler way of getting floats from a node
|
3
3
|
As a traverser
|
4
4
|
I want to access nodes using the float method and an xpath
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I have parsed the XML:
|
8
8
|
"""
|
9
9
|
<root>
|
10
10
|
<name>St. George's Arms</name>
|
11
11
|
<average_price>20.32</average_price>
|
12
|
+
<highest_price>
|
13
|
+
30.33
|
14
|
+
</highest_price>
|
12
15
|
<number_of_beers>2</number_of_beers>
|
13
16
|
</root>
|
14
17
|
"""
|
15
|
-
|
18
|
+
|
19
|
+
Scenario: getting the float of a single node
|
20
|
+
When I call "float('/root/average_price')" on the document
|
16
21
|
Then the result should be 20.32
|
17
|
-
|
22
|
+
|
18
23
|
Scenario: getting the float of a single node with whitespace
|
19
|
-
When I
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"""
|
29
|
-
And I call "float('/root/average_price')" on the document
|
30
|
-
Then the result should be 20.32
|
31
|
-
|
24
|
+
When I call "float('/root/highest_price')" on the document
|
25
|
+
Then the result should be 30.33
|
26
|
+
|
27
|
+
Scenario: getting the int of a node that doesn't contain a float
|
28
|
+
When I call "float('/root/name')" on the document
|
29
|
+
Then it should raise an "InvalidFormat" error
|
30
|
+
And the error should have the value "St. George's Arms"
|
31
|
+
And the error should have the format "float"
|
32
|
+
|
32
33
|
Scenario: getting a non-existent node
|
33
|
-
When I
|
34
|
-
|
35
|
-
<root>
|
36
|
-
<name>St. George's Arms</name>
|
37
|
-
<average_price>20.32</average_price>
|
38
|
-
<number_of_beers>2</number_of_beers>
|
39
|
-
</root>
|
40
|
-
"""
|
41
|
-
Then calling "float('//something/missing')" on the document should raise a "NotFound" error
|
34
|
+
When I call "float('//something/missing')" on the document
|
35
|
+
Then it should raise a "NotFound" error
|
42
36
|
And the error should have the xpath "//something/missing"
|
43
37
|
And the error should have a stored node
|
44
|
-
|
38
|
+
|