Permalink
Browse files

Merge branch 'release/0.2.0'

  • Loading branch information...
benlangfeld committed Aug 27, 2011
2 parents 66c0b3c + 1df750e commit 39e7a80243a3b08478b34540827e9496565c17dc
View
@@ -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
Feature: Now added support for SSML <audio/>
View
@@ -18,7 +18,7 @@ speak = RubySpeech::SSML.draw do
end
end
end
-speak.to\_s
+speak.to_s
```
becomes:
View
@@ -13,6 +13,8 @@ module SSML
InvalidChildError = Class.new StandardError
+ SSML_NAMESPACE = 'http://www.w3.org/2001/10/synthesis'
+
def self.draw(&block)
Speak.new.tap do |speak|
block_return = speak.instance_eval(&block) if block_given?
@@ -14,7 +14,9 @@ module SSML
#
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
@@ -7,6 +7,8 @@ module SSML
#
class Break < Element
+ register :break
+
VALID_STRENGTHS = [:none, :'x-weak', :weak, :medium, :strong, :'x-strong'].freeze
##
@@ -1,6 +1,62 @@
+require 'active_support/core_ext/class/inheritable_attributes'
+
module RubySpeech
module SSML
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)
super(element_name) do |new_node|
atts.each_pair { |k, v| new_node.send :"#{k}=", v }
@@ -9,6 +65,10 @@ def self.new(element_name, atts = {}, &block)
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)
const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '')
const = SSML.const_get const_name
@@ -24,7 +84,7 @@ def method_missing(method_name, *args, &block)
end
def eql?(o, *args)
- super o, :content, *args
+ super o, :content, :children, *args
end
end # Element
end # SSML
@@ -7,8 +7,10 @@ module SSML
#
class Emphasis < Element
+ register :emphasis
+
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
@@ -13,10 +13,12 @@ module SSML
#
class Prosody < Element
+ register :prosody
+
VALID_PITCHES = [:'x-low', :low, :medium, :high, :'x-high', :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_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
@@ -150,6 +152,7 @@ def duration=(t)
#
def volume
value = read_attr :volume
+ return unless value
if VALID_VOLUMES.include?(value.to_sym)
value.to_sym
else
@@ -22,7 +22,9 @@ module SSML
#
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
@@ -31,8 +33,8 @@ class SayAs < Element
#
# @return [Prosody] an element for use in an SSML document
#
- def self.new(interpret_as, atts = {}, &block)
- super 'say-as', atts.merge(:interpret_as => interpret_as), &block
+ def self.new(atts = {}, &block)
+ super 'say-as', atts, &block
end
##
@@ -8,7 +8,9 @@ module SSML
class Speak < Element
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
@@ -20,7 +22,7 @@ class Speak < Element
def self.new(atts = {}, &block)
new_node = super('speak', atts)
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.instance_eval &block if block_given?
new_node
@@ -53,8 +55,9 @@ def to_doc
def +(other)
self.class.new(:base_uri => base_uri).tap do |new_speak|
- new_speak.children = self.children + other.children
- new_speak.children.each { |c| c.namespace = new_speak.namespace }
+ (self.children + other.children).each do |child|
+ new_speak << child
+ end
end
end
@@ -8,8 +8,10 @@ module SSML
class Voice < Element
include XML::Language
+ register :voice
+
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
@@ -1,3 +1,3 @@
module RubySpeech
- VERSION = "0.1.5"
+ VERSION = "0.2.0"
end
@@ -12,6 +12,21 @@ module SSML
its(:content) { should == 'Hello' }
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
before { subject.src = 'http://whatever.you-say-boss.com' }
@@ -12,6 +12,21 @@ module SSML
its(:time) { should == 3.seconds }
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
before { subject.strength = :strong }
@@ -11,6 +11,20 @@ module SSML
its(:level) { should == :strong }
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
before { subject.level = :strong }
@@ -16,6 +16,25 @@ module SSML
its(:volume) { should == :loud }
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
context "with a pre-defined value" do
before { subject.pitch = :medium }
@@ -3,46 +3,56 @@
module RubySpeech
module SSML
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(:interpret_as) { should == 'one' }
its(:format) { should == 'two' }
its(:detail) { should == 'three' }
- describe "without interpret_as" do
- it "should raise an ArgumentError" do
- expect { SayAs.new }.should raise_error(ArgumentError)
- end
+ it 'registers itself' do
+ Element.class_from_registration(:'say-as').should == SayAs
+ 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
describe "comparing objects" 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
describe "when the content is different" 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
describe "when the interpret_as is different" 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
describe "when the format is different" 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
describe "when the detail is different" 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
Oops, something went wrong.

0 comments on commit 39e7a80

Please sign in to comment.