dozuki 0.0.1
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.
- data/.autotest +13 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +58 -0
- data/README.rdoc +116 -0
- data/Rakefile +2 -0
- data/dozuki.gemspec +29 -0
- data/features/each_accessor.feature +72 -0
- data/features/exists_accessor.feature +29 -0
- data/features/float_accessor.feature +44 -0
- data/features/get_accessor.feature +68 -0
- data/features/int_accessor.feature +43 -0
- data/features/steps/xml_steps.rb +84 -0
- data/features/string_accessor.feature +41 -0
- data/features/support/env.rb +1 -0
- data/lib/dozuki.rb +3 -0
- data/lib/dozuki/version.rb +3 -0
- data/lib/dozuki/xml.rb +12 -0
- data/lib/dozuki/xml/exceptions.rb +27 -0
- data/lib/dozuki/xml/node.rb +54 -0
- data/lib/dozuki/xml/node_collection.rb +27 -0
- data/lib/dozuki/xml/parser.rb +23 -0
- data/spec/dozuki/xml/node_collection_spec.rb +117 -0
- data/spec/dozuki/xml/node_spec.rb +257 -0
- data/spec/dozuki/xml/parser_spec.rb +78 -0
- data/spec/dozuki/xml_spec.rb +29 -0
- data/spec/spec_helper.rb +2 -0
- metadata +169 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Feature: Getting integers from the document
|
|
2
|
+
In order to provide simpler way of getting integers from a node
|
|
3
|
+
As a traverser
|
|
4
|
+
I want to access nodes using the int method and an xpath
|
|
5
|
+
|
|
6
|
+
Scenario: getting the int of a single node
|
|
7
|
+
When I parse the XML:
|
|
8
|
+
"""
|
|
9
|
+
<root>
|
|
10
|
+
<name>St. George's Arms</name>
|
|
11
|
+
<average_price>20.32</average_price>
|
|
12
|
+
<number_of_beers>2</number_of_beers>
|
|
13
|
+
</root>
|
|
14
|
+
"""
|
|
15
|
+
And I call "int('/root/number_of_beers')" on the document
|
|
16
|
+
Then the result should be 2
|
|
17
|
+
|
|
18
|
+
Scenario: getting the int of a single node with whitespace
|
|
19
|
+
When I parse the XML:
|
|
20
|
+
"""
|
|
21
|
+
<root>
|
|
22
|
+
<name>St. George's Arms</name>
|
|
23
|
+
<average_price>20.32</average_price>
|
|
24
|
+
<number_of_beers>
|
|
25
|
+
2
|
|
26
|
+
</number_of_beers>
|
|
27
|
+
</root>
|
|
28
|
+
"""
|
|
29
|
+
And I call "int('/root/number_of_beers')" on the document
|
|
30
|
+
Then the result should be 2
|
|
31
|
+
|
|
32
|
+
Scenario: getting the int of a non-existent node
|
|
33
|
+
When I parse the XML:
|
|
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 "int('//something/missing')" on the document should raise a "NotFound" error
|
|
42
|
+
And the error should have the xpath "//something/missing"
|
|
43
|
+
And the error should have a stored node
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
When /^I parse the XML:$/ do |string|
|
|
2
|
+
@doc = Dozuki::XML.parse(string)
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
Then /^the float result should be (\d+\.\d+)$/ do |float|
|
|
6
|
+
@result.should == float.to_f
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Then /^the error should have the xpath "([^"]*)"$/ do |xpath|
|
|
10
|
+
@error.xpath.should == xpath
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Then /^the error should have a stored node$/ do
|
|
14
|
+
@error.node.should_not be_nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Then /^calling "([^"]*)" on the document should raise a "([^"]*)" error$/ do |code, error|
|
|
18
|
+
begin
|
|
19
|
+
@doc.instance_eval(code)
|
|
20
|
+
fail "Expected error #{error}, nothing raised"
|
|
21
|
+
rescue Dozuki::XML.const_get(error) => e
|
|
22
|
+
@error = e
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
When /^I call "([^"]*)" on the document$/ do |code|
|
|
27
|
+
@result = @doc.instance_eval(code)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
When /^I call "([^"]*)" on the document with a block$/ do |code|
|
|
31
|
+
@result = @doc.instance_eval(code + "{|res| res}") # returns the variable passed to the block
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
When /^I call "([^"]*)" on the document and collect the results$/ do |code|
|
|
35
|
+
@results = @doc.instance_eval("results = [];" + code + "{|res| results << res}; results;")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Then /^the result should be (\d+)$/ do |int|
|
|
39
|
+
@result.should == int.to_i
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Then /^the result should be "([^"]*)"$/ do |string|
|
|
43
|
+
@result.should == string
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Then /^the result should be (\d+\.\d+)$/ do |float|
|
|
47
|
+
@result.should == float.to_f
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Then /^the (?:result|block parameter) should be a "([^"]*)"$/ do |type|
|
|
51
|
+
@result.class.to_s.should == type
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Then /^the result should be true$/ do
|
|
55
|
+
@result.should be_true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Then /^the result should be false$/ do
|
|
59
|
+
@result.should be_false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Then /^the (?:result|parameter) should have (\d+) elements$/ do |count|
|
|
63
|
+
@result.children.select{|e| e.is_a?(Nokogiri::XML::Element)}.count.should == count.to_i
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Then /^the result should have the (.*) "([^"]*)"$/ do |method, value|
|
|
67
|
+
@result.send(method).should == value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Then /^the results should contain a node with the text "([^"]*)"$/ do |text|
|
|
71
|
+
@results.any?{|n| n.text == text}.should be_true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Then /^the results should contain "([^"]*)"$/ do |string|
|
|
75
|
+
@results.should include(string)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
Then /^the results should contain (\d+)$/ do |int|
|
|
79
|
+
@results.should include(int.to_i)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
Then /^the results should contain (\d+\.\d+)$/ do |float|
|
|
83
|
+
@results.should include(float.to_f)
|
|
84
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Feature: Getting strings from the document
|
|
2
|
+
In order to provide simpler way of getting strings from a node
|
|
3
|
+
As a traverser
|
|
4
|
+
I want to access nodes using the string method and an xpath
|
|
5
|
+
|
|
6
|
+
Scenario: getting the string of a single node
|
|
7
|
+
When I parse the XML:
|
|
8
|
+
"""
|
|
9
|
+
<root>
|
|
10
|
+
<name>St. George's Arms</name>
|
|
11
|
+
<average_cost>20.32</average_cost>
|
|
12
|
+
<number_of_beers>2</number_of_beers>
|
|
13
|
+
</root>
|
|
14
|
+
"""
|
|
15
|
+
And I call "string('/root/name')" on the document
|
|
16
|
+
Then the result should be "St. George's Arms"
|
|
17
|
+
|
|
18
|
+
Scenario: getting the text of a single node with whitespace
|
|
19
|
+
When I parse the XML:
|
|
20
|
+
"""
|
|
21
|
+
<root>
|
|
22
|
+
<name>St. George's Arms</name>
|
|
23
|
+
<average_cost>20.32</average_cost>
|
|
24
|
+
<number_of_beers>2</number_of_beers>
|
|
25
|
+
</root>
|
|
26
|
+
"""
|
|
27
|
+
And I call "string('/root/name')" on the document
|
|
28
|
+
Then the result should be "St. George's Arms"
|
|
29
|
+
|
|
30
|
+
Scenario: getting a non-existent node
|
|
31
|
+
When I parse the XML:
|
|
32
|
+
"""
|
|
33
|
+
<root>
|
|
34
|
+
<name>St. George's Arms</name>
|
|
35
|
+
<average_cost>20.32</average_cost>
|
|
36
|
+
<number_of_beers>2</number_of_beers>
|
|
37
|
+
</root>
|
|
38
|
+
"""
|
|
39
|
+
Then calling "string('//something/missing')" on the document should raise a "NotFound" error
|
|
40
|
+
And the error should have the xpath "//something/missing"
|
|
41
|
+
And the error should have a stored node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'dozuki'
|
data/lib/dozuki.rb
ADDED
data/lib/dozuki/xml.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dozuki
|
|
2
|
+
module XML
|
|
3
|
+
|
|
4
|
+
class NotFound < StandardError
|
|
5
|
+
attr_accessor :xpath, :node
|
|
6
|
+
|
|
7
|
+
def initialize(msg=nil, opts={})
|
|
8
|
+
super msg
|
|
9
|
+
self.xpath = opts[:xpath]
|
|
10
|
+
self.node = opts[:node]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class InvalidFormat < StandardError
|
|
16
|
+
attr_accessor :format, :node, :value
|
|
17
|
+
|
|
18
|
+
def initialize(msg=nil, opts={})
|
|
19
|
+
super msg
|
|
20
|
+
self.format = opts[:format]
|
|
21
|
+
self.node = opts[:node]
|
|
22
|
+
self.value = opts[:value]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Dozuki
|
|
2
|
+
module XML
|
|
3
|
+
class Node
|
|
4
|
+
attr_accessor :nokogiri_node
|
|
5
|
+
|
|
6
|
+
def initialize(nokogiri_node)
|
|
7
|
+
self.nokogiri_node = nokogiri_node
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def method_missing(method, *args, &blk)
|
|
11
|
+
nokogiri_node.send(method, *args, &blk)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def respond_to?(method)
|
|
15
|
+
nokogiri_node.respond_to?(method) ? true : super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def each(xpath, &blk)
|
|
19
|
+
collection = NodeCollection.new(nokogiri_node.xpath(xpath))
|
|
20
|
+
block_given? ? collection.as_node(&blk) : collection
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def string(xpath)
|
|
24
|
+
Parser.to_string(get_first_node(xpath))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def int(xpath)
|
|
28
|
+
Parser.to_int(get_first_node(xpath))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def float(xpath)
|
|
32
|
+
Parser.to_float(get_first_node(xpath))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get(xpath)
|
|
36
|
+
node = Node.new(get_first_node(xpath))
|
|
37
|
+
yield node if block_given?
|
|
38
|
+
node
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def exists?(xpath)
|
|
42
|
+
!nokogiri_node.xpath(xpath).empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def get_first_node(xpath)
|
|
47
|
+
node = nokogiri_node.xpath(xpath)
|
|
48
|
+
raise NotFound.new("Node not found", :xpath => xpath, :node => nokogiri_node) if node.empty?
|
|
49
|
+
node.first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dozuki
|
|
2
|
+
module XML
|
|
3
|
+
class NodeCollection
|
|
4
|
+
attr_accessor :collection
|
|
5
|
+
|
|
6
|
+
def initialize(collection)
|
|
7
|
+
self.collection = collection
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def as_node(&blk)
|
|
11
|
+
collection.each{|item| blk.call(Node.new(item))}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def as_string(&blk)
|
|
15
|
+
collection.each{|item| blk.call(Parser.to_string(item))}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def as_int(&blk)
|
|
19
|
+
collection.each{|item| blk.call(Parser.to_int(item))}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def as_float(&blk)
|
|
23
|
+
collection.each{|item| blk.call(Parser.to_float(item))}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Dozuki
|
|
2
|
+
module XML
|
|
3
|
+
module Parser
|
|
4
|
+
|
|
5
|
+
def self.to_string(node)
|
|
6
|
+
node.text.strip
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.to_int(node)
|
|
10
|
+
string = to_string(node)
|
|
11
|
+
raise InvalidFormat.new("Parsing error", :node => node, :value => string, :format => "int") unless string =~ /^-?[0-9]+$/
|
|
12
|
+
string.to_i
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.to_float(node)
|
|
16
|
+
string = to_string(node)
|
|
17
|
+
raise InvalidFormat.new("Parsing error", :node => node, :value => string, :format => "float") unless string =~ /^-?[0-9]+(\.[0-9]+)?$/
|
|
18
|
+
string.to_f
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
module Dozuki
|
|
3
|
+
module XML
|
|
4
|
+
describe NodeCollection do
|
|
5
|
+
|
|
6
|
+
describe "as_node" do
|
|
7
|
+
let(:collection){mock "some_nodes"}
|
|
8
|
+
let(:blk){Proc.new{}}
|
|
9
|
+
let(:node_collection){NodeCollection.new(collection)}
|
|
10
|
+
let(:collection_item){mock "collection item"}
|
|
11
|
+
let(:new_node){mock "new node"}
|
|
12
|
+
before(:each) do
|
|
13
|
+
collection.stub(:each).and_yield(collection_item)
|
|
14
|
+
Node.stub(:new).and_return(new_node)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
subject{node_collection.as_node(&blk)}
|
|
18
|
+
|
|
19
|
+
it "should iterate through the collection" do
|
|
20
|
+
collection.should_receive(:each)
|
|
21
|
+
subject
|
|
22
|
+
end
|
|
23
|
+
it "should create a new Node with the yielded collection item" do
|
|
24
|
+
Node.should_receive(:new).with(collection_item)
|
|
25
|
+
subject
|
|
26
|
+
end
|
|
27
|
+
it "should call the bloc with the node" do
|
|
28
|
+
blk.should_receive(:call).with(new_node)
|
|
29
|
+
subject
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "as_string" do
|
|
34
|
+
let(:collection){mock "some_nodes"}
|
|
35
|
+
let(:blk){Proc.new{}}
|
|
36
|
+
let(:node_collection){NodeCollection.new(collection)}
|
|
37
|
+
let(:collection_item){mock "collection item"}
|
|
38
|
+
let(:string){"string"}
|
|
39
|
+
before(:each) do
|
|
40
|
+
collection.stub(:each).and_yield(collection_item)
|
|
41
|
+
Parser.stub(:to_string).and_return(string)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
subject{node_collection.as_string(&blk)}
|
|
45
|
+
|
|
46
|
+
it "should iterate through the collection" do
|
|
47
|
+
collection.should_receive(:each)
|
|
48
|
+
subject
|
|
49
|
+
end
|
|
50
|
+
it "should create a new Node with the yielded collection item" do
|
|
51
|
+
Parser.should_receive(:to_string).with(collection_item)
|
|
52
|
+
subject
|
|
53
|
+
end
|
|
54
|
+
it "should call the bloc with the node" do
|
|
55
|
+
blk.should_receive(:call).with("string")
|
|
56
|
+
subject
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "as_int" do
|
|
61
|
+
let(:collection){mock "some_nodes"}
|
|
62
|
+
let(:blk){Proc.new{}}
|
|
63
|
+
let(:node_collection){NodeCollection.new(collection)}
|
|
64
|
+
let(:collection_item){mock "collection item"}
|
|
65
|
+
let(:int){39}
|
|
66
|
+
before(:each) do
|
|
67
|
+
collection.stub(:each).and_yield(collection_item)
|
|
68
|
+
Parser.stub(:to_int).and_return(int)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
subject{node_collection.as_int(&blk)}
|
|
72
|
+
|
|
73
|
+
it "should iterate through the collection" do
|
|
74
|
+
collection.should_receive(:each)
|
|
75
|
+
subject
|
|
76
|
+
end
|
|
77
|
+
it "should create a new Node with the yielded collection item" do
|
|
78
|
+
Parser.should_receive(:to_int).with(collection_item)
|
|
79
|
+
subject
|
|
80
|
+
end
|
|
81
|
+
it "should call the bloc with the node" do
|
|
82
|
+
blk.should_receive(:call).with(int)
|
|
83
|
+
subject
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "as_float" do
|
|
88
|
+
let(:collection){mock "some_nodes"}
|
|
89
|
+
let(:blk){Proc.new{}}
|
|
90
|
+
let(:node_collection){NodeCollection.new(collection)}
|
|
91
|
+
let(:collection_item){mock "collection item"}
|
|
92
|
+
let(:float){39.50}
|
|
93
|
+
before(:each) do
|
|
94
|
+
collection.stub(:each).and_yield(collection_item)
|
|
95
|
+
Parser.stub(:to_float).and_return(float)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
subject{node_collection.as_float(&blk)}
|
|
99
|
+
|
|
100
|
+
it "should iterate through the collection" do
|
|
101
|
+
collection.should_receive(:each)
|
|
102
|
+
subject
|
|
103
|
+
end
|
|
104
|
+
it "should create a new Node with the yielded collection item" do
|
|
105
|
+
Parser.should_receive(:to_float).with(collection_item)
|
|
106
|
+
subject
|
|
107
|
+
end
|
|
108
|
+
it "should call the bloc with the node" do
|
|
109
|
+
blk.should_receive(:call).with(float)
|
|
110
|
+
subject
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|