diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..5f16476
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,2 @@
+--color
+--format progress
diff --git a/lib/callback_handler.rb b/lib/callback_handler.rb
new file mode 100644
index 0000000..f84b9e7
--- /dev/null
+++ b/lib/callback_handler.rb
@@ -0,0 +1,31 @@
+require 'nokogiri'
+require 'open-uri'
+require_relative 'line_counter'
+
+include Nokogiri
+
+class CallbackHandler < XML::SAX::Document
+ def initialize
+ @counter = LineCounter.new
+ @parsing_speaker = false
+ end
+
+ def results
+ @counter.count
+ end
+
+ def start_element(element, attributes)
+ @counter.count_line if element == 'LINE'
+ @counter.new_speech if element == 'SPEECH'
+ @counter.new_scene if element == 'SCENE'
+ @parsing_speaker = (element == 'SPEAKER')
+ end
+
+ def end_element(element)
+ @parsing_speaker = false if element == 'SPEAKER'
+ end
+
+ def characters string
+ @counter.add_speaker string if @parsing_speaker
+ end
+end
diff --git a/lib/line_counter.rb b/lib/line_counter.rb
new file mode 100644
index 0000000..1dc4538
--- /dev/null
+++ b/lib/line_counter.rb
@@ -0,0 +1,38 @@
+require 'set'
+
+class LineCounter
+ def initialize
+ @speaker = []
+ @scene_speakers = Set.new
+ @count = Hash.new(0)
+ end
+
+ def count
+ @count
+ end
+
+ def add_speaker(name)
+ @speaker.push name
+ @scene_speakers.add name unless name == 'ALL'
+ end
+
+ def count_line
+ names = (@speaker.include?("ALL")) ? @scene_speakers : @speaker
+ increment_names(names)
+ raise ArgumentError, 'Unable to count lines without a speaker.' if @speaker.empty?
+ end
+
+ def increment_names(names)
+ names.each do |name|
+ @count[name] += 1
+ end
+ end
+
+ def new_speech
+ @speaker = []
+ end
+
+ def new_scene
+ @scene_speakers = Set.new
+ end
+end
diff --git a/lib/shakespeare_analyzer.rb b/lib/shakespeare_analyzer.rb
new file mode 100644
index 0000000..9771324
--- /dev/null
+++ b/lib/shakespeare_analyzer.rb
@@ -0,0 +1,6 @@
+require_relative 'callback_handler'
+
+handler = CallbackHandler.new
+XML::SAX::Parser.new(handler).parse_io(open("http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml"))
+results = handler.results.sort_by {|k,v| v}.reverse
+results.each {|k,v| puts " #{v} #{k.capitalize}" }
diff --git a/output.txt b/output.txt
new file mode 100644
index 0000000..fe90e63
--- /dev/null
+++ b/output.txt
@@ -0,0 +1,40 @@
+ 729 Macbeth
+ 267 Lady macbeth
+ 215 Malcolm
+ 183 Macduff
+ 136 Ross
+ 115 Banquo
+ 83 First witch
+ 76 Lennox
+ 70 Duncan
+ 48 Porter
+ 48 Third witch
+ 48 Second witch
+ 46 Hecate
+ 45 Doctor
+ 41 Lady macduff
+ 35 Sergeant
+ 31 Siward
+ 30 First murderer
+ 23 Gentlewoman
+ 23 Messenger
+ 21 Angus
+ 21 Lord
+ 20 Son
+ 15 Second murderer
+ 12 Donalbain
+ 12 Menteith
+ 11 Old man
+ 11 Caithness
+ 8 Third apparition
+ 8 Second apparition
+ 8 Third murderer
+ 7 Young siward
+ 6 First apparition
+ 5 Servant
+ 5 Seyton
+ 3 Lords
+ 2 Fleance
+ 2 Both murderers
+ 1 Soldiers
+ 1 Attendant
diff --git a/spec/all_speaker.xml b/spec/all_speaker.xml
new file mode 100644
index 0000000..db72196
--- /dev/null
+++ b/spec/all_speaker.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+SCENE I. A desert place.
+Thunder and lightning. Enter three Witches
+
+First Witch
+When shall we three meet again
+In thunder, lightning, or in rain?
+
+
+Second Witch
+When the hurlyburly's done,
+When the battle's lost and won.
+
+
+Third Witch
+That will be ere the set of sun.
+
+
+First Witch
+Where the place?
+
+
+Second Witch
+Upon the heath.
+
+
+Third Witch
+There to meet with Macbeth.
+
+
+First Witch
+I come, Graymalkin!
+
+
+Second Witch
+Paddock calls.
+
+
+Third Witch
+Anon.
+
+
+ALL
+Fair is foul, and foul is fair:
+Hover through the fog and filthy air.
+
+Exeunt
+
+
+
diff --git a/spec/callback_handler_spec.rb b/spec/callback_handler_spec.rb
new file mode 100644
index 0000000..2f91aa2
--- /dev/null
+++ b/spec/callback_handler_spec.rb
@@ -0,0 +1,38 @@
+require 'callback_handler'
+
+describe CallbackHandler do
+ before (:each) do
+ @handler = CallbackHandler.new
+ @parser = XML::SAX::Parser.new(@handler)
+ end
+
+ it "correctly parses an empty file" do
+ @parser.parse_file("spec/empty_file.xml")
+ @handler.results.keys.should be_empty
+ end
+
+ it "correctly parses a file with only one speaker" do
+ @parser.parse_file("spec/one_speaker.xml")
+ @handler.results.keys.should_not be_empty
+ @handler.results["MACBETH"].should eq(1)
+ end
+
+ it "correctly parses a file with multiple speakers" do
+ @parser.parse_file("spec/multiple_speakers.xml")
+ @handler.results.keys.should_not be_empty
+
+ @handler.results["LENNOX"].should eq(1)
+ @handler.results["MACBETH"].should eq(2)
+ @handler.results["MACDUFF"].should eq(6)
+ end
+
+ it "correctly parses a file with a speaker of all" do
+ @parser.parse_file("spec/all_speaker.xml")
+ @handler.results.keys.should_not be_empty
+
+ @handler.results["First Witch"].should eq(6)
+ @handler.results["Second Witch"].should eq(6)
+ @handler.results["Third Witch"].should eq(5)
+ @handler.results["ALL"].should eq(0)
+ end
+end
diff --git a/spec/empty_file.xml b/spec/empty_file.xml
new file mode 100644
index 0000000..e69de29
diff --git a/spec/line_counter_spec.rb b/spec/line_counter_spec.rb
new file mode 100644
index 0000000..1812bd0
--- /dev/null
+++ b/spec/line_counter_spec.rb
@@ -0,0 +1,92 @@
+require 'line_counter'
+
+describe LineCounter, "#count" do
+ before (:each) do
+ @counter = LineCounter.new
+ end
+
+ it "returns an empty list when no lines are counted" do
+ @counter.count.should be_empty
+ end
+
+ it "increments the current speaker when a speaker is added" do
+ @counter.add_speaker "Macbeth"
+
+ @counter.count_line
+ @counter.count["Macbeth"].should eq(1)
+
+ @counter.count_line
+ @counter.count["Macbeth"].should eq(2)
+ end
+
+ it "throws exception when a line is counted without a speaker" do
+ expect {@counter.count_line}.to raise_error(ArgumentError)
+ end
+
+ it "allows two people to speak at the same time" do
+ @counter.add_speaker "Macbeth"
+ @counter.add_speaker "Banquo"
+
+ @counter.count_line
+ @counter.count["Macbeth"].should eq(1)
+ @counter.count["Banquo"].should eq(1)
+
+ @counter.count_line
+ @counter.count["Macbeth"].should eq(2)
+ @counter.count["Banquo"].should eq(2)
+ end
+
+ it "does not count lines from speakers of previous speeches" do
+ @counter.add_speaker "Macbeth"
+
+ @counter.new_speech
+
+ @counter.add_speaker "Duncan"
+ @counter.count_line
+
+ @counter.count["Duncan"].should eq(1)
+ @counter.count.key?("Macbeth").should be_false
+ end
+
+ it "increments all speakers in the scene when the speaker is all" do
+ @counter.add_speaker "Macbeth"
+ @counter.count_line
+
+ @counter.new_speech
+ @counter.add_speaker "Banquo"
+ @counter.count_line
+ @counter.count_line
+
+ @counter.new_speech
+ @counter.add_speaker "ALL"
+ @counter.count_line
+
+ @counter.count["Macbeth"].should eq(2)
+ @counter.count["Banquo"].should eq(3)
+ @counter.count["ALL"].should eq(0)
+ end
+
+ it "increments only the speakers in the scene when the speaker is all" do
+ @counter.add_speaker "Macbeth"
+ @counter.count_line
+
+ @counter.new_scene
+ @counter.new_speech
+ @counter.add_speaker "Banquo"
+ @counter.count_line
+
+ @counter.new_speech
+ @counter.add_speaker "Duncan"
+ @counter.count_line
+ @counter.count_line
+
+ @counter.new_speech
+ @counter.add_speaker "ALL"
+ @counter.count_line
+
+ @counter.count["Macbeth"].should eq(1)
+ @counter.count["Banquo"].should eq(2)
+ @counter.count["Duncan"].should eq(3)
+ @counter.count["ALL"].should eq(0)
+ end
+end
diff --git a/spec/multiple_speakers.xml b/spec/multiple_speakers.xml
new file mode 100644
index 0000000..01a72b5
--- /dev/null
+++ b/spec/multiple_speakers.xml
@@ -0,0 +1,28 @@
+
+
+
+
+MACDUFF
+O horror, horror, horror! Tongue nor heart
+Cannot conceive nor name thee!
+
+
+
+MACBETH
+LENNOX
+What's the matter.
+
+
+
+MACDUFF
+Confusion now hath made his masterpiece!
+Most sacrilegious murder hath broke ope
+The Lord's anointed temple, and stole thence
+The life o' the building!
+
+
+
+MACBETH
+What is 't you say? the life?
+
+
diff --git a/spec/one_speaker.xml b/spec/one_speaker.xml
new file mode 100644
index 0000000..7f5babf
--- /dev/null
+++ b/spec/one_speaker.xml
@@ -0,0 +1,8 @@
+
+
+
+
+MACBETH
+Methought I heard a voice cry 'Sleep no more!
+
+
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..dbc4f1a
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,17 @@
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# Require this file using `require "spec_helper"` to ensure that it is only
+# loaded once.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ config.treat_symbols_as_metadata_keys_with_true_values = true
+ config.run_all_when_everything_filtered = true
+ config.filter_run :focus
+
+ # Run specs in random order to surface order dependencies. If you find an
+ # order dependency and want to debug it, you can fix the order by providing
+ # the seed, which is printed after each run.
+ # --seed 1234
+ config.order = 'random'
+end