Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
benlangfeld committed Aug 27, 2011
2 parents 66c0b3c + 1df750e commit 39e7a80
Show file tree
Hide file tree
Showing 20 changed files with 258 additions and 36 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,8 @@
# 0.2.0
Feature: SSML elements can now be imported from a Nokogiri Node or a string
Feature: SSML elements now respond to #children with an array of SSML elements, rather than a Nokogiri NodeSet
Bugfix/Feature: Comparing SSML elements now compares children

# 0.1.5 # 0.1.5
Feature: Now added support for SSML <audio/> Feature: Now added support for SSML <audio/>


Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -18,7 +18,7 @@ speak = RubySpeech::SSML.draw do
end end
end end
end end
speak.to\_s speak.to_s
``` ```


becomes: becomes:
Expand Down
2 changes: 2 additions & 0 deletions lib/ruby_speech/ssml.rb
Expand Up @@ -13,6 +13,8 @@ module SSML


InvalidChildError = Class.new StandardError InvalidChildError = Class.new StandardError


SSML_NAMESPACE = 'http://www.w3.org/2001/10/synthesis'

def self.draw(&block) def self.draw(&block)
Speak.new.tap do |speak| Speak.new.tap do |speak|
block_return = speak.instance_eval(&block) if block_given? block_return = speak.instance_eval(&block) if block_given?
Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_speech/ssml/audio.rb
Expand Up @@ -14,7 +14,9 @@ module SSML
# #
class Audio < Element class Audio < Element


VALID_CHILD_TYPES = [String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze register :audio

VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text,String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze


## ##
# Create a new SSML audio element # Create a new SSML audio element
Expand Down
2 changes: 2 additions & 0 deletions lib/ruby_speech/ssml/break.rb
Expand Up @@ -7,6 +7,8 @@ module SSML
# #
class Break < Element class Break < Element


register :break

VALID_STRENGTHS = [:none, :'x-weak', :weak, :medium, :strong, :'x-strong'].freeze VALID_STRENGTHS = [:none, :'x-weak', :weak, :medium, :strong, :'x-strong'].freeze


## ##
Expand Down
62 changes: 61 additions & 1 deletion lib/ruby_speech/ssml/element.rb
@@ -1,6 +1,62 @@
require 'active_support/core_ext/class/inheritable_attributes'

module RubySpeech module RubySpeech
module SSML module SSML
class Element < Niceogiri::XML::Node class Element < Niceogiri::XML::Node
@@registrations = {}

class_inheritable_accessor :registered_ns, :registered_name

# Register a new stanza class to a name and/or namespace
#
# This registers a namespace that is used when looking
# up the class name of the object to instantiate when a new
# stanza is received
#
# @param [#to_s] name the name of the node
#
def self.register(name)
self.registered_name = name.to_s
self.registered_ns = SSML_NAMESPACE
@@registrations[[self.registered_name, self.registered_ns]] = self
end

# Find the class to use given the name and namespace of a stanza
#
# @param [#to_s] name the name to lookup
#
# @return [Class, nil] the class appropriate for the name
def self.class_from_registration(name)
@@registrations[[name.to_s, SSML_NAMESPACE]]
end

# Import an XML::Node to the appropriate class
#
# Looks up the class the node should be then creates it based on the
# elements of the XML::Node
# @param [XML::Node] node the node to import
# @return the appropriate object based on the node name and namespace
def self.import(node)
klass = class_from_registration(node.element_name)
event = if klass && klass != self
klass.import node
else
new.inherit node
end
end

# Inherit the attributes and children of an XML::Node
#
# @param [XML::Node] node the node to inherit
# @return [self]
def inherit(node)
inherit_attrs node.attributes
node.children.each do |c|
self << c.dup
end
self
end

def self.new(element_name, atts = {}, &block) def self.new(element_name, atts = {}, &block)
super(element_name) do |new_node| super(element_name) do |new_node|
atts.each_pair { |k, v| new_node.send :"#{k}=", v } atts.each_pair { |k, v| new_node.send :"#{k}=", v }
Expand All @@ -9,6 +65,10 @@ def self.new(element_name, atts = {}, &block)
end end
end end


def children
super.reject { |c| c.is_a?(Nokogiri::XML::Text) }.map { |c| Element.import c }
end

def method_missing(method_name, *args, &block) def method_missing(method_name, *args, &block)
const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '') const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '')
const = SSML.const_get const_name const = SSML.const_get const_name
Expand All @@ -24,7 +84,7 @@ def method_missing(method_name, *args, &block)
end end


def eql?(o, *args) def eql?(o, *args)
super o, :content, *args super o, :content, :children, *args
end end
end # Element end # Element
end # SSML end # SSML
Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_speech/ssml/emphasis.rb
Expand Up @@ -7,8 +7,10 @@ module SSML
# #
class Emphasis < Element class Emphasis < Element


register :emphasis

VALID_LEVELS = [:strong, :moderate, :none, :reduced].freeze VALID_LEVELS = [:strong, :moderate, :none, :reduced].freeze
VALID_CHILD_TYPES = [String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze


## ##
# Create a new SSML emphasis element # Create a new SSML emphasis element
Expand Down
5 changes: 4 additions & 1 deletion lib/ruby_speech/ssml/prosody.rb
Expand Up @@ -13,10 +13,12 @@ module SSML
# #
class Prosody < Element class Prosody < Element


register :prosody

VALID_PITCHES = [:'x-low', :low, :medium, :high, :'x-high', :default].freeze VALID_PITCHES = [:'x-low', :low, :medium, :high, :'x-high', :default].freeze
VALID_VOLUMES = [:silent, :'x-soft', :soft, :medium, :loud, :'x-loud', :default].freeze VALID_VOLUMES = [:silent, :'x-soft', :soft, :medium, :loud, :'x-loud', :default].freeze
VALID_RATES = [:'x-slow', :slow, :medium, :fast, :'x-fast', :default].freeze VALID_RATES = [:'x-slow', :slow, :medium, :fast, :'x-fast', :default].freeze
VALID_CHILD_TYPES = [String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze


## ##
# Create a new SSML prosody element # Create a new SSML prosody element
Expand Down Expand Up @@ -150,6 +152,7 @@ def duration=(t)
# #
def volume def volume
value = read_attr :volume value = read_attr :volume
return unless value
if VALID_VOLUMES.include?(value.to_sym) if VALID_VOLUMES.include?(value.to_sym)
value.to_sym value.to_sym
else else
Expand Down
8 changes: 5 additions & 3 deletions lib/ruby_speech/ssml/say_as.rb
Expand Up @@ -22,7 +22,9 @@ module SSML
# #
class SayAs < Element class SayAs < Element


VALID_CHILD_TYPES = [String].freeze register :'say-as'

VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String].freeze


## ##
# Create a new SSML say-as element # Create a new SSML say-as element
Expand All @@ -31,8 +33,8 @@ class SayAs < Element
# #
# @return [Prosody] an element for use in an SSML document # @return [Prosody] an element for use in an SSML document
# #
def self.new(interpret_as, atts = {}, &block) def self.new(atts = {}, &block)
super 'say-as', atts.merge(:interpret_as => interpret_as), &block super 'say-as', atts, &block
end end


## ##
Expand Down
11 changes: 7 additions & 4 deletions lib/ruby_speech/ssml/speak.rb
Expand Up @@ -8,7 +8,9 @@ module SSML
class Speak < Element class Speak < Element
include XML::Language include XML::Language


VALID_CHILD_TYPES = [String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze register :speak

VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze


## ##
# Create a new SSML speak root element # Create a new SSML speak root element
Expand All @@ -20,7 +22,7 @@ class Speak < Element
def self.new(atts = {}, &block) def self.new(atts = {}, &block)
new_node = super('speak', atts) new_node = super('speak', atts)
new_node[:version] = '1.0' new_node[:version] = '1.0'
new_node.namespace = 'http://www.w3.org/2001/10/synthesis' new_node.namespace = SSML_NAMESPACE
new_node.language ||= "en-US" new_node.language ||= "en-US"
new_node.instance_eval &block if block_given? new_node.instance_eval &block if block_given?
new_node new_node
Expand Down Expand Up @@ -53,8 +55,9 @@ def to_doc


def +(other) def +(other)
self.class.new(:base_uri => base_uri).tap do |new_speak| self.class.new(:base_uri => base_uri).tap do |new_speak|
new_speak.children = self.children + other.children (self.children + other.children).each do |child|
new_speak.children.each { |c| c.namespace = new_speak.namespace } new_speak << child
end
end end
end end


Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_speech/ssml/voice.rb
Expand Up @@ -8,8 +8,10 @@ module SSML
class Voice < Element class Voice < Element
include XML::Language include XML::Language


register :voice

VALID_GENDERS = [:male, :female, :neutral].freeze VALID_GENDERS = [:male, :female, :neutral].freeze
VALID_CHILD_TYPES = [String, Audio,Break, Emphasis, Prosody, SayAs, Voice].freeze VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio,Break, Emphasis, Prosody, SayAs, Voice].freeze


## ##
# Create a new SSML voice element # Create a new SSML voice element
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_speech/version.rb
@@ -1,3 +1,3 @@
module RubySpeech module RubySpeech
VERSION = "0.1.5" VERSION = "0.2.0"
end end
15 changes: 15 additions & 0 deletions spec/ruby_speech/ssml/audio_spec.rb
Expand Up @@ -12,6 +12,21 @@ module SSML
its(:content) { should == 'Hello' } its(:content) { should == 'Hello' }
end end


it 'registers itself' do
Element.class_from_registration(:audio).should == Audio
end

describe "from a document" do
let(:document) { '<audio src="http://whatever.you-say-boss.com">Hello</audio>' }

subject { Element.import parse_xml(document).root }

it { should be_instance_of Audio }

its(:src) { should == 'http://whatever.you-say-boss.com' }
its(:content) { should == 'Hello' }
end

describe "#src" do describe "#src" do
before { subject.src = 'http://whatever.you-say-boss.com' } before { subject.src = 'http://whatever.you-say-boss.com' }


Expand Down
15 changes: 15 additions & 0 deletions spec/ruby_speech/ssml/break_spec.rb
Expand Up @@ -12,6 +12,21 @@ module SSML
its(:time) { should == 3.seconds } its(:time) { should == 3.seconds }
end end


it 'registers itself' do
Element.class_from_registration(:break).should == Break
end

describe "from a document" do
let(:document) { '<break strength="strong" time="3"/>' }

subject { Element.import parse_xml(document).root }

it { should be_instance_of Break }

its(:strength) { should == :strong }
its(:time) { should == 3.seconds }
end

describe "#strength" do describe "#strength" do
before { subject.strength = :strong } before { subject.strength = :strong }


Expand Down
14 changes: 14 additions & 0 deletions spec/ruby_speech/ssml/emphasis_spec.rb
Expand Up @@ -11,6 +11,20 @@ module SSML
its(:level) { should == :strong } its(:level) { should == :strong }
end end


it 'registers itself' do
Element.class_from_registration(:emphasis).should == Emphasis
end

describe "from a document" do
let(:document) { '<emphasis level="strong"/>' }

subject { Element.import parse_xml(document).root }

it { should be_instance_of Emphasis }

its(:level) { should == :strong }
end

describe "#level" do describe "#level" do
before { subject.level = :strong } before { subject.level = :strong }


Expand Down
19 changes: 19 additions & 0 deletions spec/ruby_speech/ssml/prosody_spec.rb
Expand Up @@ -16,6 +16,25 @@ module SSML
its(:volume) { should == :loud } its(:volume) { should == :loud }
end end


it 'registers itself' do
Element.class_from_registration(:prosody).should == Prosody
end

describe "from a document" do
let(:document) { '<prosody pitch="medium" contour="something" range="20Hz" rate="2" duration="10" volume="loud"/>' }

subject { Element.import parse_xml(document).root }

it { should be_instance_of Prosody }

its(:pitch) { should == :medium }
its(:contour) { should == 'something' }
its(:range) { should == '20Hz' }
its(:rate) { should == 2 }
its(:duration) { should == 10.seconds }
its(:volume) { should == :loud }
end

describe "#pitch" do describe "#pitch" do
context "with a pre-defined value" do context "with a pre-defined value" do
before { subject.pitch = :medium } before { subject.pitch = :medium }
Expand Down
30 changes: 20 additions & 10 deletions spec/ruby_speech/ssml/say_as_spec.rb
Expand Up @@ -3,46 +3,56 @@
module RubySpeech module RubySpeech
module SSML module SSML
describe SayAs do describe SayAs do
subject { SayAs.new 'one', :format => 'two', :detail => 'three' } subject { SayAs.new :interpret_as => 'one', :format => 'two', :detail => 'three' }


its(:name) { should == 'say-as' } its(:name) { should == 'say-as' }


its(:interpret_as) { should == 'one' } its(:interpret_as) { should == 'one' }
its(:format) { should == 'two' } its(:format) { should == 'two' }
its(:detail) { should == 'three' } its(:detail) { should == 'three' }


describe "without interpret_as" do it 'registers itself' do
it "should raise an ArgumentError" do Element.class_from_registration(:'say-as').should == SayAs
expect { SayAs.new }.should raise_error(ArgumentError) end
end
describe "from a document" do
let(:document) { '<say-as interpret-as="one" format="two" detail="three"/>' }

subject { Element.import parse_xml(document).root }

it { should be_instance_of SayAs }

its(:interpret_as) { should == 'one' }
its(:format) { should == 'two' }
its(:detail) { should == 'three' }
end end


describe "comparing objects" do describe "comparing objects" do
it "should be equal if the content, interpret_as, format, age, variant, name are the same" do it "should be equal if the content, interpret_as, format, age, variant, name are the same" do
SayAs.new('jp', :format => 'foo', :detail => 'bar', :content => "hello").should == SayAs.new('jp', :format => 'foo', :detail => 'bar', :content => "hello") SayAs.new(:interpret_as => 'jp', :format => 'foo', :detail => 'bar', :content => "hello").should == SayAs.new(:interpret_as => 'jp', :format => 'foo', :detail => 'bar', :content => "hello")
end end


describe "when the content is different" do describe "when the content is different" do
it "should not be equal" do it "should not be equal" do
SayAs.new('jp', :content => "Hello").should_not == SayAs.new('jp', :content => "Hello there") SayAs.new(:interpret_as => 'jp', :content => "Hello").should_not == SayAs.new(:interpret_as => 'jp', :content => "Hello there")
end end
end end


describe "when the interpret_as is different" do describe "when the interpret_as is different" do
it "should not be equal" do it "should not be equal" do
SayAs.new("Hello").should_not == SayAs.new("Hello there") SayAs.new(:interpret_as => "Hello").should_not == SayAs.new(:interpret_as => "Hello there")
end end
end end


describe "when the format is different" do describe "when the format is different" do
it "should not be equal" do it "should not be equal" do
SayAs.new('jp', :format => 'foo').should_not == SayAs.new('jp', :format => 'bar') SayAs.new(:interpret_as => 'jp', :format => 'foo').should_not == SayAs.new(:interpret_as => 'jp', :format => 'bar')
end end
end end


describe "when the detail is different" do describe "when the detail is different" do
it "should not be equal" do it "should not be equal" do
SayAs.new('jp', :detail => 'foo').should_not == SayAs.new('jp', :detail => 'bar') SayAs.new(:interpret_as => 'jp', :detail => 'foo').should_not == SayAs.new(:interpret_as => 'jp', :detail => 'bar')
end end
end end
end end
Expand Down

0 comments on commit 39e7a80

Please sign in to comment.