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