saxerator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ .idea/
7
+ coverage/
8
+
data/.rvmrc ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="1.9.3@saxerator"
8
+
9
+ #
10
+ # First we attempt to load the desired environment directly from the environment
11
+ # file. This is very fast and efficient compared to running through the entire
12
+ # CLI and selector. If you want feedback on which environment was used then
13
+ # insert the word 'use' after --create as this triggers verbose mode.
14
+ #
15
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
16
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
17
+ then
18
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
19
+
20
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
21
+ then
22
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
23
+ fi
24
+ else
25
+ # If the environment file has not yet been created, use the RVM CLI to select.
26
+ if ! rvm --create "$environment_id"
27
+ then
28
+ echo "Failed to create RVM environment '${environment_id}'."
29
+ return 1
30
+ fi
31
+ fi
32
+
33
+ if [[ $- == *i* ]] # check for interactive shells
34
+ then
35
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
36
+ else
37
+ echo "Using: $GEM_HOME" # don't use colors in interactive shells
38
+ fi
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in saxerator.gemspec
4
+ gemspec
@@ -0,0 +1,13 @@
1
+ # More info at https://github.com/guard/guard#readme
2
+
3
+ guard :bundler do
4
+ watch('Gemfile')
5
+ watch(/^saxerator\.gemspec$/)
6
+ end
7
+
8
+ guard :rspec, :cli => '--color --format doc' do
9
+ watch(%r{^spec/.+_spec\.rb$})
10
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
11
+ watch(%r{^spec/fixtures/.+\.xml$}) { :spec }
12
+ watch('spec/spec_helper.rb') { :spec }
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2012 Bradley Schaefer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6
+ persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9
+ Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ Saxerator
2
+ =========
3
+
4
+ Saxerator is a SAX-based xml parser designed for parsing very large files into manageable chunks. Rather than
5
+ dealing directly with SAX callback methods, Saxerator gives you Enumerable access to chunks of an xml document.
6
+ This approach is ideal for large xml files that represent a collection of elements.
7
+
8
+ Examples
9
+ --------
10
+
11
+ ```ruby
12
+ Saxerator.parser(File.new("rss.xml")).for_tag(:item).each do |item|
13
+ puts "#{item['title']}: #{item['author']}"
14
+ end
15
+ ```
16
+
17
+ Compatibility
18
+ -------------
19
+ This library is known to work with the following rubies:
20
+
21
+ * MRI 1.9.3-p125
22
+ * MRI 1.9.2-p318
23
+ * JRuby 1.6.7 (with JRUBY_OPTS=--1.9)
24
+
25
+ Saxerator may work with other versions with support for Fiber.
26
+
27
+ Known incompatible rubies:
28
+
29
+ * MRI 1.9.2-p290 (Fiber segfaults)
30
+
31
+ FAQ
32
+ ---
33
+ Why the name 'Saxerator'?
34
+
35
+ > It's a combination of SAX + Enumerator.
36
+
37
+ Why use Saxerator over regular SAX parsing?
38
+
39
+ > Much of the SAX parsing code I've written over the years has fallen into a pattern that Saxerator encapsulates:
40
+ > marshall a chunk of an XML document into an object, operate on that object, then move on to the
41
+ > next chunk. Saxerator alleviates the pain of marshalling and allows you to focus solely on operating on the
42
+ > document chunk.
43
+
44
+ Why not DOM parsing?
45
+
46
+ > DOM parsers load the entire document into memory. Saxerator only holds a single chunk in memory at a time. If your
47
+ > document is very large, this can be an important consideration.
48
+
49
+ ### Acknowledgements ###
50
+ Saxerator was inspired by [Nori](https://github.com/rubiii/nori) and [Gregory Brown](http://majesticseacreature.com/)'s
51
+ [Practicing Ruby](http://practicingruby.com/)
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,18 @@
1
+ $:.push File.expand_path('../../lib', __FILE__)
2
+ require 'saxerator'
3
+ require 'benchmark'
4
+
5
+ file = ARGV.shift
6
+ if !File.exists?(file)
7
+ puts "Cannot find file #{file}"
8
+ exit 1
9
+ end
10
+ file = File.new(file)
11
+
12
+ tag = ARGV.shift || :artist
13
+
14
+ count = 0
15
+ Benchmark.bm do |x|
16
+ x.report { Saxerator.parser(file).for_tag(tag).each { count = count + 1 } }
17
+ end
18
+ puts "#{count} #{tag} elements parsed"
@@ -0,0 +1,32 @@
1
+ require 'ipsum'
2
+
3
+ filename = ARGV.shift
4
+
5
+ unless filename
6
+ puts "Syntax: ruby #{__FILE__} <filename>"
7
+ exit 1
8
+ end
9
+
10
+ num_records = ARGV.shift || 100000
11
+ num_records = num_records.to_i
12
+
13
+ element = <<-eos
14
+ <artist id="%i">
15
+ <name>Rock Star %i</name>
16
+ <active>true</active>
17
+ <description>%s</description>
18
+ <genre>Rock,Metal</genre>
19
+ </artist>
20
+ eos
21
+
22
+ puts "Writing #{num_records} sample records to #{filename}, this will take a while..."
23
+
24
+ File.open(filename, 'w') do |f|
25
+ f.puts '<?xml version="1.0" encoding="UTF-8"?>'
26
+ f.puts '<artists>'
27
+ num_records.times do |count|
28
+ f.puts(element % [count, count, 5.sentences])
29
+ end
30
+ f.puts '</artists>'
31
+ end
32
+ puts "DONE!"
@@ -0,0 +1,12 @@
1
+ require "saxerator/version"
2
+ require 'saxerator/configuration'
3
+ require 'saxerator/xml_node'
4
+ require 'saxerator/parser/nokogiri'
5
+
6
+ module Saxerator
7
+ extend self
8
+
9
+ def parser(xml)
10
+ Saxerator::Configuration.new(xml)
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Saxerator
2
+ class Configuration
3
+ attr_reader :source
4
+
5
+ def initialize(source)
6
+ @source = source
7
+ end
8
+
9
+ def for_tag(tag)
10
+ tag = tag.to_s
11
+ Saxerator::Parser::Nokogiri.new(self, source, tag)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ require 'nokogiri'
2
+ require 'fiber'
3
+
4
+ module Saxerator
5
+ module Parser
6
+ class Nokogiri
7
+ include Enumerable
8
+
9
+ def initialize(config, source, tag)
10
+ @config = config
11
+ @source = source
12
+ @tag = tag
13
+ end
14
+
15
+ def each(&block)
16
+ begin
17
+ fiber = Fiber.new do
18
+ document = Document.new(@config, @tag)
19
+ parser = ::Nokogiri::XML::SAX::Parser.new document
20
+ parser.parse(@source)
21
+ end
22
+ while fiber.alive? do
23
+ result = fiber.resume
24
+ yield(result) unless result.nil?
25
+ end
26
+ rescue FiberError
27
+ end
28
+ end
29
+
30
+ class Document < ::Nokogiri::XML::SAX::Document
31
+ attr_accessor :stack
32
+
33
+ def initialize(config, tag)
34
+ @config = config
35
+ @tag = tag
36
+ @stack = []
37
+ end
38
+
39
+ def start_element(name, attrs = [])
40
+ if stack.size > 0 || name == @tag
41
+ stack.push Saxerator::XmlNode.new(@config, name, Hash[*attrs.flatten])
42
+ end
43
+ end
44
+
45
+ def end_element(name)
46
+ if stack.size > 1
47
+ last = stack.pop
48
+ stack.last.add_node last
49
+ elsif stack.size == 1
50
+ Fiber.yield(stack.pop.to_hash)
51
+ end
52
+ end
53
+
54
+ def characters(string)
55
+ stack.last.add_node(string) unless string.strip.length == 0 || stack.empty?
56
+ end
57
+
58
+ alias cdata_block characters
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ module Saxerator
2
+ class StringWithAttributes < String
3
+ attr_accessor :attributes
4
+
5
+ def attribrutes
6
+ @attributes ||= {}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Saxerator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,38 @@
1
+ module Saxerator
2
+ class XmlNode
3
+ attr_accessor :name, :attributes, :children, :type
4
+
5
+ def initialize(config, name, attributes)
6
+ @config = config
7
+ self.name = name
8
+ self.attributes = attributes
9
+ self.children = []
10
+ @text = false
11
+ end
12
+
13
+ def add_node(node)
14
+ @text = true if node.is_a? String
15
+ children << node
16
+ end
17
+
18
+ def to_hash
19
+ if @text
20
+ return children.join(' ')
21
+ else
22
+ out = {}
23
+ @children.each do |child|
24
+ name = child.name
25
+ if out[name]
26
+ if !out[name].is_a?(Array)
27
+ out[name] = [out[name]]
28
+ end
29
+ out[name] << child.to_hash
30
+ else
31
+ out[name] = child.to_hash
32
+ end
33
+ end
34
+ out
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'saxerator/version'
4
+ require 'rubygems/package_task'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'saxerator'
8
+ s.version = Saxerator::VERSION
9
+ s.authors = ['Bradley Schaefer']
10
+ s.email = ['bradley.schaefer@gmail.com']
11
+ s.homepage = 'https://github.com/soulcutter/saxerator'
12
+ s.summary = 'A SAX-based XML parser for parsing large files into manageable chunks'
13
+ s.description = 'A SAX-based XML parser for parsing large files into manageable chunks'
14
+
15
+ s.required_ruby_version = '>= 1.9.2'
16
+
17
+ s.rubyforge_project = 'saxerator'
18
+
19
+ s.files = FileList[
20
+ 'LICENSE',
21
+ 'README.md',
22
+ 'saxerator.gemspec',
23
+ 'lib/**/*.rb',
24
+ 'spec/**/*.*',
25
+ 'benchmark/**/*.rb',
26
+ 'Gemfile',
27
+ 'Guardfile',
28
+ 'Rakefile',
29
+ '.rvmrc',
30
+ '.gitignore'
31
+ ]
32
+ s.test_files = FileList['spec/**/*.*']
33
+ s.executables = []
34
+ s.require_paths = ['lib']
35
+
36
+ s.add_runtime_dependency 'nokogiri'
37
+
38
+ s.add_development_dependency 'rspec'
39
+ s.add_development_dependency 'guard'
40
+ s.add_development_dependency 'guard-bundler'
41
+ s.add_development_dependency 'guard-rspec'
42
+ s.add_development_dependency 'simplecov'
43
+ s.add_development_dependency 'ipsum'
44
+ end
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <blurbs>
3
+ <blurb>one</blurb>
4
+ <blurb>two</blurb>
5
+ <blurb>three</blurb>
6
+ </blurbs>
@@ -0,0 +1,53 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xml:lang="en-US" xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">
3
+ <id>/blog</id>
4
+ <link type="text/html" href="https://example.com/blog" rel="alternate"/>
5
+ <link type="application/atom+xml" href="https://example.com/blog.atom" rel="self"/>
6
+ <title>Example Blog</title>
7
+ <updated>2012-01-01T16:17:00-06:00</updated>
8
+ <entry>
9
+ <id>1</id>
10
+ <published>2012-01-01T16:17:00-06:00</published>
11
+ <updated>2012-01-01T16:17:00-06:00</updated>
12
+ <link type="text/html" href="https://example.com/blog/how-to-eat-an-airplane" rel="alternate"/>
13
+ <title>How to eat an airplane</title>
14
+ <content type="html">&lt;p&gt;Airplanes are very large, which can present difficulty in digestion.&lt;/p&gt;
15
+
16
+ &lt;p&gt;The key is to break it down into bite-sized portions and go slowly.&lt;/p&gt;
17
+ </content>
18
+ <media:thumbnail url="http://www.gravatar.com/avatar/a9eb6ba22e482b71b266daadf9c9a080?s=80"/>
19
+ <author>
20
+ <name>Soulcutter</name>
21
+ </author>
22
+ <contributor>
23
+ <name>Jane Doe</name>
24
+ </contributor>
25
+ <contributor>
26
+ <name>Leviticus Alabaster</name>
27
+ </contributor>
28
+ </entry>
29
+ <entry>
30
+ <id>2</id>
31
+ <published>2012-01-02T18:19:00-06:00</published>
32
+ <updated>2012-01-02T18:19:00-06:00</updated>
33
+ <link type="text/html" href="https://example.com/blog/sample-data-is-hard-work" rel="alternate"/>
34
+ <title>Sample data is hard work</title>
35
+ <content type="html">&lt;p&gt;People don't believe me when I tell them how tedious it is.&lt;/p&gt;</content>
36
+ <media:thumbnail url="http://www.gravatar.com/avatar/742798d92b705cfeba3937e122974982?s=80"/>
37
+ <author>
38
+ <name>Bradley Schaefer</name>
39
+ </author>
40
+ </entry>
41
+ <entry>
42
+ <id>3</id>
43
+ <published>2012-01-15T21:22:00-06:00</published>
44
+ <updated>2012-01-15T21:22:00-06:00</updated>
45
+ <link type="text/html" href="https://example.com/blog/three-should-be-enough" rel="alternate"/>
46
+ <title>Three should be enough</title>
47
+ <content type="html">&lt;p&gt;EOM&lt;/p&gt;</content>
48
+ <media:thumbnail url="http://www.gravatar.com/avatar/a9eb6ba22e482b71b266daadf9c9a080?s=80"/>
49
+ <author>
50
+ <name>Soulcutter</name>
51
+ </author>
52
+ </entry>
53
+ </feed>
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saxerator do
4
+ def fixture_file(name)
5
+ File.new(File.join(File.dirname(__FILE__), '..', 'fixtures', name))
6
+ end
7
+
8
+ context ".parser" do
9
+ subject { parser }
10
+ let(:parser) { Saxerator.parser(xml) }
11
+
12
+ context "with a string with blurbs" do
13
+ let(:xml) { "<blurbs><blurb>one</blurb><blurb>two</blurb><blurb>three</blurb></blurbs>" }
14
+
15
+ it "should parse simple strings" do
16
+ results = []
17
+ subject.for_tag(:blurb).each { |x| results << x }
18
+ results.should == ['one', 'two', 'three']
19
+ end
20
+
21
+ context "and one non-blurb" do
22
+ let(:xml) { "<blurbs><blurb>one</blurb><blurb>two</blurb><blurb>three</blurb><notablurb>four</notablurb></blurbs>" }
23
+ it "should only parse the requested tag" do
24
+ results = []
25
+ subject.for_tag(:blurb).each { |x| results << x }
26
+ results.should == ['one', 'two', 'three']
27
+ subject.for_tag(:notablurb).each { |x| results << x }
28
+ results.should == ['one', 'two', 'three', 'four']
29
+ end
30
+ end
31
+ end
32
+
33
+ context "with a file with blurbs" do
34
+ let(:xml) { fixture_file('flat_blurbs.xml') }
35
+
36
+ it "should parse simple strings" do
37
+ results = []
38
+ subject.for_tag(:blurb).each { |x| results << x }
39
+ results.should == ['one', 'two', 'three']
40
+ end
41
+ end
42
+
43
+ context "with a file with nested elements" do
44
+ let(:xml) { fixture_file('nested_elements.xml') }
45
+ subject { parser.for_tag(:entry).first }
46
+
47
+ specify { subject['title'].should == 'How to eat an airplane' }
48
+ specify { subject['author'].should == {'name' => 'Soulcutter'} }
49
+ specify { subject['contributor'].should == [{'name' => 'Jane Doe'}, {'name' => 'Leviticus Alabaster'}] }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "spec/"
4
+ end
5
+
6
+ require 'saxerator'
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: saxerator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bradley Schaefer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &70348029624860 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70348029624860
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70348029624300 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70348029624300
36
+ - !ruby/object:Gem::Dependency
37
+ name: guard
38
+ requirement: &70348029623840 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70348029623840
47
+ - !ruby/object:Gem::Dependency
48
+ name: guard-bundler
49
+ requirement: &70348029623320 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70348029623320
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-rspec
60
+ requirement: &70348029622860 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70348029622860
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: &70348029622300 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70348029622300
80
+ - !ruby/object:Gem::Dependency
81
+ name: ipsum
82
+ requirement: &70348029621880 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70348029621880
91
+ description: A SAX-based XML parser for parsing large files into manageable chunks
92
+ email:
93
+ - bradley.schaefer@gmail.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - LICENSE
99
+ - README.md
100
+ - saxerator.gemspec
101
+ - lib/saxerator/configuration.rb
102
+ - lib/saxerator/parser/nokogiri.rb
103
+ - lib/saxerator/string_with_attributes.rb
104
+ - lib/saxerator/version.rb
105
+ - lib/saxerator/xml_node.rb
106
+ - lib/saxerator.rb
107
+ - spec/fixtures/flat_blurbs.xml
108
+ - spec/fixtures/nested_elements.xml
109
+ - spec/lib/saxerator_spec.rb
110
+ - spec/spec_helper.rb
111
+ - benchmark/benchmark.rb
112
+ - benchmark/generate_sample_file.rb
113
+ - Gemfile
114
+ - Guardfile
115
+ - Rakefile
116
+ - .rvmrc
117
+ - .gitignore
118
+ homepage: https://github.com/soulcutter/saxerator
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: 1.9.2
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project: saxerator
138
+ rubygems_version: 1.8.11
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: A SAX-based XML parser for parsing large files into manageable chunks
142
+ test_files:
143
+ - spec/fixtures/flat_blurbs.xml
144
+ - spec/fixtures/nested_elements.xml
145
+ - spec/lib/saxerator_spec.rb
146
+ - spec/spec_helper.rb