badgerhash 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/.gitignore +17 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +507 -0
- data/README.md +48 -0
- data/Rakefile +6 -0
- data/badgerhash.gemspec +24 -0
- data/lib/badgerhash.rb +31 -0
- data/lib/badgerhash/handlers/sax_handler.rb +95 -0
- data/lib/badgerhash/parsers/rexml.rb +49 -0
- data/lib/badgerhash/version.rb +5 -0
- data/lib/badgerhash/xml_stream.rb +40 -0
- data/spec/badgerhash/handlers/sax_handler_spec.rb +134 -0
- data/spec/badgerhash/parsers/rexml_spec.rb +60 -0
- data/spec/badgerhash/xml_stream_spec.rb +26 -0
- data/spec/badgerhash_spec.rb +23 -0
- data/spec/integration/stream_parser_spec.rb +100 -0
- data/spec/spec_helper.rb +10 -0
- metadata +107 -0
data/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Badgerhash
|
|
2
|
+
[](https://travis-ci.org/gfmurphy/badgerhash)
|
|
3
|
+
[](https://codeclimate.com/github/gfmurphy/badgerhash)
|
|
4
|
+
|
|
5
|
+
Convert XML to a Ruby Hash using the BadgerFish convention: http://badgerfish.ning.com/
|
|
6
|
+
|
|
7
|
+
The resulting Hash can be easily converted to JSON using a JSON library.
|
|
8
|
+
|
|
9
|
+
The reference implementation of the generators use REXML for parsing the given
|
|
10
|
+
XML. REXML is included in the Ruby Standard Library so no depencies are
|
|
11
|
+
introduced. Nevertheless, this library provides interfaces that allow using
|
|
12
|
+
alternate parsers if performance is a concern.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add this line to your application's Gemfile:
|
|
17
|
+
|
|
18
|
+
gem 'badgerhash'
|
|
19
|
+
|
|
20
|
+
And then execute:
|
|
21
|
+
|
|
22
|
+
$ bundle
|
|
23
|
+
|
|
24
|
+
Or install it yourself as:
|
|
25
|
+
|
|
26
|
+
$ gem install badgerhash
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Generating a BadgerFish Hash Using a Stream Parser
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "badgerhash"
|
|
34
|
+
require "stringio"
|
|
35
|
+
|
|
36
|
+
xml = StringIO.new("<alice>bob</alice>")
|
|
37
|
+
xml_stream = Badgerhash::XmlStream.create(xml)
|
|
38
|
+
|
|
39
|
+
puts xml_stream.to_badgerfish.inspect
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Contributing
|
|
43
|
+
|
|
44
|
+
1. Fork it ( http://github.com/gfmurphy/badgerhash/fork )
|
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
48
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/badgerhash.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'badgerhash/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "badgerhash"
|
|
8
|
+
spec.version = Badgerhash::VERSION
|
|
9
|
+
spec.authors = ["George F. Murphy"]
|
|
10
|
+
spec.email = ["gmurphy@epublishing.com"]
|
|
11
|
+
spec.summary = "Parse XML as IO into hash using badgerfish convention."
|
|
12
|
+
spec.homepage = "http://github.com/gfmurphy/badgerhash"
|
|
13
|
+
spec.license = "GNU LGPL"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.required_ruby_version = ">= 1.9.3"
|
|
21
|
+
|
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
|
23
|
+
spec.add_development_dependency "rake", "~> 10.1.1"
|
|
24
|
+
end
|
data/lib/badgerhash.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "badgerhash/handlers/sax_handler"
|
|
2
|
+
require "badgerhash/parsers/rexml"
|
|
3
|
+
require "badgerhash/version"
|
|
4
|
+
require "badgerhash/xml_stream"
|
|
5
|
+
|
|
6
|
+
# Convert XML to Ruby Hash using Badgerfish convention: http://badgerfish.ning.com/
|
|
7
|
+
# @api public
|
|
8
|
+
module Badgerhash
|
|
9
|
+
# Class of the default sax parser implementation
|
|
10
|
+
DEFAULT_SAX_PARSER = Parsers::REXML::SaxDocument
|
|
11
|
+
|
|
12
|
+
# Set the sax parser for the module
|
|
13
|
+
#
|
|
14
|
+
# @param parser [Class] a parser that supports the required interface
|
|
15
|
+
# @see Parsers::REXML::SaxDocument for the reference implementation
|
|
16
|
+
# @example
|
|
17
|
+
# Badgerhash.sax_parser = MyFastParser => MyFastParser
|
|
18
|
+
# @return [Class] the new parser class
|
|
19
|
+
def self.sax_parser=(parser)
|
|
20
|
+
@sax_parser = parser
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The current sax parser implementation
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# Badgerhash.sax_parser => Parsers::REXML::SaxDocument
|
|
27
|
+
# @return [Class] the current sax parser implementation
|
|
28
|
+
def self.sax_parser
|
|
29
|
+
@sax_parser || DEFAULT_SAX_PARSER
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Badgerhash
|
|
2
|
+
module Handlers
|
|
3
|
+
# SaxHandler that is passed to a sax parser implementation
|
|
4
|
+
# @api public
|
|
5
|
+
class SaxHandler
|
|
6
|
+
attr_reader :node
|
|
7
|
+
|
|
8
|
+
# Initialize the SaxHandler
|
|
9
|
+
#
|
|
10
|
+
# @param node [Hash] Used to preinitialzie the internal node.
|
|
11
|
+
# @api private
|
|
12
|
+
def initialize(node={})
|
|
13
|
+
@parents = []
|
|
14
|
+
@node = node
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Sends message to handler that a new element was encountered in
|
|
18
|
+
# stream
|
|
19
|
+
#
|
|
20
|
+
# @param name [String] the name of the new element
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# handler.start_element "foo" => handler
|
|
24
|
+
# @return [SaxHandler]
|
|
25
|
+
def start_element(name)
|
|
26
|
+
name = name.to_s
|
|
27
|
+
element = node_namespaces
|
|
28
|
+
|
|
29
|
+
@node[name] = case @node[name]
|
|
30
|
+
when nil
|
|
31
|
+
element
|
|
32
|
+
when Hash
|
|
33
|
+
[@node[name], element]
|
|
34
|
+
else
|
|
35
|
+
@node[name] << element
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@parents << @node
|
|
39
|
+
@node = element
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Sends message to handler that an attribute was encountered in
|
|
44
|
+
# the stream
|
|
45
|
+
#
|
|
46
|
+
# @param name [String] the name of the attribute
|
|
47
|
+
# @param value [String] the attribute's value
|
|
48
|
+
# @example
|
|
49
|
+
# handler.attr "foo", "bar" => handler
|
|
50
|
+
# @return [SaxHandler]
|
|
51
|
+
def attr(name, value)
|
|
52
|
+
if name =~ /^xmlns(:?(.*))/i
|
|
53
|
+
key = $2.to_s.length > 0 ? $2 : "$"
|
|
54
|
+
(@node["@xmlns"] ||= {})[key] = value
|
|
55
|
+
else
|
|
56
|
+
@node["@#{name}"] = value
|
|
57
|
+
end
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Sends a message to handler that a text node or cdata section
|
|
62
|
+
# was found in the stream
|
|
63
|
+
#
|
|
64
|
+
# @param value [String] the text encountered
|
|
65
|
+
# @example
|
|
66
|
+
# handler.text "this is my text node" => handler
|
|
67
|
+
# @return [SaxHandler]
|
|
68
|
+
def text(value)
|
|
69
|
+
value = value.to_s.strip
|
|
70
|
+
@node["$"] = value unless value.empty?
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
alias :cdata :text
|
|
75
|
+
|
|
76
|
+
# Sends a message to the handler that the end of an element has
|
|
77
|
+
# been found in the stream
|
|
78
|
+
#
|
|
79
|
+
# @param name [String] the name of the element
|
|
80
|
+
# @example
|
|
81
|
+
# handler.end_element "foo" => handler
|
|
82
|
+
# @return [SaxHandler]
|
|
83
|
+
def end_element(name)
|
|
84
|
+
@node = @parents.pop || {}
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
def node_namespaces
|
|
90
|
+
ns = @node.fetch("@xmlns", {})
|
|
91
|
+
ns.size > 0 ? { "@xmlns" => ns } : {}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "rexml/document"
|
|
3
|
+
require "rexml/streamlistener"
|
|
4
|
+
|
|
5
|
+
module Badgerhash
|
|
6
|
+
module Parsers
|
|
7
|
+
module REXML
|
|
8
|
+
# SaxDocument provides an implmentation of the required
|
|
9
|
+
# public interface for a parser that is to be used when parsing
|
|
10
|
+
# an XmlStream. An implementation is required to provide a parse method
|
|
11
|
+
# that accepts a Badgerhash::Handler::SaxHandler and an IO object.
|
|
12
|
+
# When parsing the given IO stream, it must send messages to the handler
|
|
13
|
+
# in order to build the correct Hash. This also serves as a reference
|
|
14
|
+
# implementation for other sax parsers.
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
class SaxDocument
|
|
18
|
+
include ::REXML::StreamListener
|
|
19
|
+
extend Forwardable
|
|
20
|
+
|
|
21
|
+
def_delegators :@handler, :text, :cdata
|
|
22
|
+
def_delegator :@handler, :end_element, :tag_end
|
|
23
|
+
|
|
24
|
+
# Parse the given stream and update the handler.
|
|
25
|
+
#
|
|
26
|
+
# @param handler [Badgerhash::Handler::SaxHandler] parsing handler
|
|
27
|
+
# @param io [IO] the XML to be parsed.
|
|
28
|
+
# @return void
|
|
29
|
+
def self.parse(handler, io)
|
|
30
|
+
::REXML::Document.parse_stream(io, new(handler))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Initialize the parser.
|
|
34
|
+
#
|
|
35
|
+
# @param handler [Badgerhash::Handlers::SaxHandler]
|
|
36
|
+
def initialize(handler)
|
|
37
|
+
@handler = handler
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def tag_start(name, attributes=[])
|
|
41
|
+
@handler.start_element name
|
|
42
|
+
Array(attributes).each do |attr|
|
|
43
|
+
@handler.attr(*attr)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Badgerhash
|
|
2
|
+
# Converts XML as IO to a Badgerfish Hash using a stream parser
|
|
3
|
+
class XmlStream
|
|
4
|
+
# Create a properly initialized Badgerhash::XmlStream object
|
|
5
|
+
#
|
|
6
|
+
# @param io [IO] an object containing the XML to be parsed
|
|
7
|
+
# @example
|
|
8
|
+
# io = StringIO.new("<alice>bob</alice>")
|
|
9
|
+
# Badgerhash::XmlStream.create(io)
|
|
10
|
+
# @return [Badgerhash::XmlStream] the XmlStream
|
|
11
|
+
# @api public
|
|
12
|
+
def self.create(io)
|
|
13
|
+
new(Handlers::SaxHandler.new, Badgerhash.sax_parser, io)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The Badgerfish representation of the XML
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# io = StringIO.new("<alice>bob</alice>")
|
|
20
|
+
# Badgerhash::XmlStream.create(io).to_badgerfish => {"alice" => {"$" => "bob" }}
|
|
21
|
+
# @return [Hash] the Badgerfish hash
|
|
22
|
+
# @api public
|
|
23
|
+
def to_badgerfish
|
|
24
|
+
@parser.parse(@handler, @io)
|
|
25
|
+
@handler.node.dup
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Initialize an XmlStream object.
|
|
29
|
+
#
|
|
30
|
+
# @param handler [Badgerhash::Handlers::SaxHandler] the handler
|
|
31
|
+
# @param parser [Class] a parser conforming to the required interface.
|
|
32
|
+
# @param io [IO] object containing the XML to be parsed.
|
|
33
|
+
# @api private
|
|
34
|
+
def initialize(handler, parser, io)
|
|
35
|
+
@handler = handler
|
|
36
|
+
@parser = parser
|
|
37
|
+
@io = io
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require File.expand_path "../../../spec_helper.rb", __FILE__
|
|
2
|
+
|
|
3
|
+
module Badgerhash
|
|
4
|
+
module Handlers
|
|
5
|
+
describe SaxHandler do
|
|
6
|
+
subject(:handler) { SaxHandler.new }
|
|
7
|
+
|
|
8
|
+
describe "#start_element" do
|
|
9
|
+
it "sets the current node to new hash" do
|
|
10
|
+
handler.start_element "foo"
|
|
11
|
+
expect(handler.node).to eq({})
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "when no element present with name" do
|
|
15
|
+
subject(:handler) { SaxHandler.new }
|
|
16
|
+
it "adds the node name and empty hash" do
|
|
17
|
+
handler.start_element("foo").end_element("foo")
|
|
18
|
+
expect(handler.node).to eq({"foo" => {}})
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "when one element present with name" do
|
|
23
|
+
subject(:handler) { SaxHandler.new({"foo" => { "$" => "bar" }}) }
|
|
24
|
+
it "add the new node and old node to an Array" do
|
|
25
|
+
handler.start_element("foo").end_element("foo")
|
|
26
|
+
expect(handler.node)
|
|
27
|
+
.to eq({"foo" => [{"\$" => "bar"}, {}]})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "when more than one element present with name" do
|
|
32
|
+
subject(:handler) { SaxHandler.new({ "foo" => [{ "\$" => "bar" },
|
|
33
|
+
{ "\$" => "baz" }] }) }
|
|
34
|
+
it "adds the new node to the Array" do
|
|
35
|
+
handler.start_element("foo").end_element("foo")
|
|
36
|
+
expect(handler.node)
|
|
37
|
+
.to eq({"foo" => [{"\$" => "bar"}, {"\$" => "baz"}, {}]})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#attr" do
|
|
43
|
+
it "adds attribute as property that begins with '@'" do
|
|
44
|
+
handler.start_element("foo")
|
|
45
|
+
.attr("george", "bar")
|
|
46
|
+
.end_element("foo")
|
|
47
|
+
expect(handler.node).to eq("foo" => {"@george"=> "bar"})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "with parent namespaces" do
|
|
51
|
+
before do
|
|
52
|
+
handler.start_element("foo")
|
|
53
|
+
.text("test")
|
|
54
|
+
.attr("xmlns", "http://foo.com/namespace")
|
|
55
|
+
.attr("xmlns:charlie", "http://foo.com/charlie")
|
|
56
|
+
.start_element("charlie:bar")
|
|
57
|
+
.text("test2")
|
|
58
|
+
.end_element("charlie:bar")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "adds parents namespaces" do
|
|
62
|
+
expect(handler.node["charlie:bar"]["@xmlns"]["$"])
|
|
63
|
+
.to eq("http://foo.com/namespace")
|
|
64
|
+
expect(handler.node["charlie:bar"]["@xmlns"]["charlie"])
|
|
65
|
+
.to eq("http://foo.com/charlie")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "when namespace" do
|
|
70
|
+
it "stores main namespace in @xmlns $ property" do
|
|
71
|
+
handler.start_element("foo")
|
|
72
|
+
.attr("xmlns", "http://foo.com/namespace")
|
|
73
|
+
.end_element("foo")
|
|
74
|
+
expect(handler.node)
|
|
75
|
+
.to eq("foo" => {"@xmlns" => {"$" => "http://foo.com/namespace"}})
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "stores additional namespaces in named property" do
|
|
79
|
+
handler.start_element("foo")
|
|
80
|
+
.attr("xmlns:george", "http://foo.com/george")
|
|
81
|
+
.end_element("foo")
|
|
82
|
+
expect(handler.node)
|
|
83
|
+
.to eq("foo" => {"@xmlns" => {"george" => "http://foo.com/george"}})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#text" do
|
|
89
|
+
it "sets the $ key in the hash with the text value" do
|
|
90
|
+
handler.text "foo"
|
|
91
|
+
expect(handler.node).to eq({"\$" => "foo"})
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "compresses leading whitespace" do
|
|
95
|
+
handler.text "\nfoo"
|
|
96
|
+
expect(handler.node).to eq({"\$" => "foo"})
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "compresses trailing whitespace" do
|
|
100
|
+
handler.text "foo\n"
|
|
101
|
+
expect(handler.node).to eq({"\$" => "foo"})
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe "#cdata" do
|
|
106
|
+
it "sets the $ key in the hash with the cdata section" do
|
|
107
|
+
handler.cdata "foo"
|
|
108
|
+
expect(handler.node).to eq({"\$" => "foo"})
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "#end_element" do
|
|
113
|
+
context "with parent tag on stack" do
|
|
114
|
+
before do
|
|
115
|
+
handler.start_element "foo"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "closes the current node" do
|
|
119
|
+
handler.end_element "foo"
|
|
120
|
+
expect(handler.node).to eq({"foo" => {}})
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when no parent tag on stack" do
|
|
125
|
+
it "sets node to empty hash" do
|
|
126
|
+
handler.end_element "foo"
|
|
127
|
+
expect(handler.node).to eq({})
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require File.expand_path "../../../spec_helper.rb", __FILE__
|
|
2
|
+
|
|
3
|
+
module Badgerhash
|
|
4
|
+
module Parsers
|
|
5
|
+
module REXML
|
|
6
|
+
describe SaxDocument do
|
|
7
|
+
let(:handler) { double(:handler) }
|
|
8
|
+
subject(:document) { SaxDocument.new(handler) }
|
|
9
|
+
|
|
10
|
+
describe ".parse" do
|
|
11
|
+
let(:parser) { double(:parser) }
|
|
12
|
+
let(:io) { double(:io) }
|
|
13
|
+
|
|
14
|
+
it "delegates to the REXML sax parser" do
|
|
15
|
+
expect(::REXML::Document).to receive(:parse_stream)
|
|
16
|
+
SaxDocument.parse(handler, io)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#tag_end" do
|
|
21
|
+
it "delegates to handler" do
|
|
22
|
+
expect(handler).to receive(:end_element).with("foo")
|
|
23
|
+
document.tag_end("foo")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "#cdata" do
|
|
28
|
+
it "delegates to handler's text method" do
|
|
29
|
+
expect(handler).to receive(:cdata).with "foo"
|
|
30
|
+
document.cdata "foo"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#text" do
|
|
35
|
+
it "delegates to the handler's text method" do
|
|
36
|
+
expect(handler).to receive(:text).with "foo"
|
|
37
|
+
document.text "foo"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#tag_start" do
|
|
42
|
+
let(:attributes) { [["one", '1'], ["two", '2']] }
|
|
43
|
+
|
|
44
|
+
before do
|
|
45
|
+
expect(handler).to receive(:start_element).with "foo"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "delegates to handler's start_element_method" do
|
|
49
|
+
document.tag_start "foo"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "delegates attributes to handler's attr method" do
|
|
53
|
+
expect(handler).to receive(:attr).exactly(attributes.size).times
|
|
54
|
+
document.tag_start "foo", attributes
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|