Permalink
Browse files

Merge branch 'release/0.5.0'

  • Loading branch information...
2 parents 8a7af1c + 45e6ad0 commit 798638ce98f8558ce8777c8f1d5fb4d5299f3cc6 @benlangfeld benlangfeld committed Jan 3, 2012
Showing with 1,753 additions and 384 deletions.
  1. +15 −0 CHANGELOG.md
  2. +108 −14 README.md
  3. +71 −10 lib/ruby_speech/generic_element.rb
  4. +4 −1 lib/ruby_speech/grxml.rb
  5. +4 −0 lib/ruby_speech/grxml/element.rb
  6. +177 −46 lib/ruby_speech/grxml/grammar.rb
  7. +12 −11 lib/ruby_speech/grxml/item.rb
  8. +16 −0 lib/ruby_speech/grxml/match.rb
  9. +10 −0 lib/ruby_speech/grxml/no_match.rb
  10. +4 −11 lib/ruby_speech/grxml/one_of.rb
  11. +0 −11 lib/ruby_speech/grxml/rule.rb
  12. +0 −11 lib/ruby_speech/grxml/ruleref.rb
  13. +0 −11 lib/ruby_speech/grxml/tag.rb
  14. +8 −11 lib/ruby_speech/grxml/token.rb
  15. +6 −0 lib/ruby_speech/ssml.rb
  16. +1 −12 lib/ruby_speech/ssml/audio.rb
  17. +0 −11 lib/ruby_speech/ssml/break.rb
  18. +24 −0 lib/ruby_speech/ssml/desc.rb
  19. +1 −12 lib/ruby_speech/ssml/emphasis.rb
  20. +43 −0 lib/ruby_speech/ssml/mark.rb
  21. +25 −0 lib/ruby_speech/ssml/p.rb
  22. +72 −0 lib/ruby_speech/ssml/phoneme.rb
  23. +1 −12 lib/ruby_speech/ssml/prosody.rb
  24. +25 −0 lib/ruby_speech/ssml/s.rb
  25. +0 −11 lib/ruby_speech/ssml/say_as.rb
  26. +2 −44 lib/ruby_speech/ssml/speak.rb
  27. +42 −0 lib/ruby_speech/ssml/sub.rb
  28. +1 −12 lib/ruby_speech/ssml/voice.rb
  29. +1 −1 lib/ruby_speech/version.rb
  30. +478 −35 spec/ruby_speech/grxml/grammar_spec.rb
  31. +5 −2 spec/ruby_speech/grxml/item_spec.rb
  32. +49 −0 spec/ruby_speech/grxml/match_spec.rb
  33. +17 −0 spec/ruby_speech/grxml/no_match_spec.rb
  34. +1 −1 spec/ruby_speech/grxml/one_of_spec.rb
  35. +1 −1 spec/ruby_speech/grxml/rule_spec.rb
  36. +1 −1 spec/ruby_speech/grxml/ruleref_spec.rb
  37. +1 −1 spec/ruby_speech/grxml/tag_spec.rb
  38. +11 −1 spec/ruby_speech/grxml/token_spec.rb
  39. +64 −5 spec/ruby_speech/grxml_spec.rb
  40. +5 −6 spec/ruby_speech/ssml/audio_spec.rb
  41. +1 −1 spec/ruby_speech/ssml/break_spec.rb
  42. +57 −0 spec/ruby_speech/ssml/desc_spec.rb
  43. +1 −4 spec/ruby_speech/ssml/emphasis_spec.rb
  44. +53 −0 spec/ruby_speech/ssml/mark_spec.rb
  45. +96 −0 spec/ruby_speech/ssml/p_spec.rb
  46. +65 −0 spec/ruby_speech/ssml/phoneme_spec.rb
  47. +9 −4 spec/ruby_speech/ssml/prosody_spec.rb
  48. +92 −0 spec/ruby_speech/ssml/s_spec.rb
  49. +1 −1 spec/ruby_speech/ssml/say_as_spec.rb
  50. +1 −6 spec/ruby_speech/ssml/speak_spec.rb
  51. +57 −0 spec/ruby_speech/ssml/sub_spec.rb
  52. +1 −6 spec/ruby_speech/ssml/voice_spec.rb
  53. +0 −4 spec/spec_helper.rb
  54. +13 −53 spec/support/matchers.rb
View
@@ -1,5 +1,20 @@
# develop
+# 0.5.0 - 2012-01-03
+ * Feature: Add a whole bunch more SSML elements:
+ ** p & s
+ ** mark
+ ** desc
+ ** sub
+ ** phoneme
+ * Feature: Added the ability to inline grammar rule references in both destructive and non-destructive modes
+ * Feature: Added the ability to tokenize a grammar, turning all tokens into unambiguous `<token/>` elements
+ * Feature: Added the ability to whitespace normalize a grammar
+ * Feature: Added the ability to match an input string against a Grammar
+ * Feature: Constructing a GRXML grammar with a root rule specified but not provided will raise an exception
+ * Feature: Embedding a GRXML grammar of a mode different from the host will raise an exception
+ * Bugfix: Fix upward traversal through a document via #parent
+
# 0.4.0 - 2011-12-30
* Feature: Add the ability to look up child elements by name/attributes easily
* Feature: Allow easy access to a GRXML grammar's root rule element
View
122 README.md
@@ -46,15 +46,15 @@ Once your `Speak` is fully prepared and you're ready to send it off for processi
You may also then need to call `to_s`.
-Contruct a GRXML (SRGS) document like this:
+Construct a GRXML (SRGS) document like this:
```ruby
require 'ruby_speech'
-grammy = RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'digits' do
- rule id: 'digits' do
+grammy = RubySpeech::GRXML.draw mode: :dtmf, root: 'pin' do
+ rule id: 'digit' do
one_of do
- 0.upto(9) {|d| item { d.to_s } }
+ ('0'..'9').map { |d| item { d } }
end
end
@@ -79,8 +79,8 @@ grammy.to_s
which becomes
```xml
-<grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="digits">
- <rule id="digits">
+<grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="pin">
+ <rule id="digit">
<one-of>
<item>0</item>
<item>1</item>
@@ -103,6 +103,101 @@ which becomes
</grammar>
```
+### Grammar matching
+
+It is possible to match some arbitrary input against a GRXML grammar. In order to do so, certain normalization routines should first be run on the grammar in order to prepare it for matching. These are reference inlining, tokenization and whitespace normalization, and are described [in the SRGS spec](http://www.w3.org/TR/speech-grammar/#S2.1). This process will transform the above grammar like so:
+
+```ruby
+grammy.inline!
+grammy.tokenize!
+grammy.normalize_whitespace
+```
+
+```xml
+<grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="pin">
+ <rule id="pin" scope="public">
+ <one-of>
+ <item>
+ <item repeat="4">
+ <one-of>
+ <item>
+ <token>0</token>
+ </item>
+ <item>
+ <token>1</token>
+ </item>
+ <item>
+ <token>2</token>
+ </item>
+ <item>
+ <token>3</token>
+ </item>
+ <item>
+ <token>4</token>
+ </item>
+ <item>
+ <token>5</token>
+ </item>
+ <item>
+ <token>6</token>
+ </item>
+ <item>
+ <token>7</token>
+ </item>
+ <item>
+ <token>8</token>
+ </item>
+ <item>
+ <token>9</token>
+ </item>
+ </one-of>
+ </item>
+ <token>#</token>
+ </item>
+ <item>
+ <token>*</token>
+ <token>9</token>
+ </item>
+ </one-of>
+ </rule>
+</grammar>
+```
+
+Matching against some sample input strings then returns the following results:
+
+```ruby
+>> subject.match '*9'
+=> #<RubySpeech::GRXML::Match:0x00000100ae5d98
+ @mode = :dtmf,
+ @confidence = 1,
+ @utterance = "*9",
+ @interpretation = "*9"
+ >
+>> subject.match '1234#'
+=> #<RubySpeech::GRXML::Match:0x00000100b7e020
+ @mode = :dtmf,
+ @confidence = 1,
+ @utterance = "1234#",
+ @interpretation = "1234#"
+ >
+>> subject.match '5678#'
+=> #<RubySpeech::GRXML::Match:0x00000101218688
+ @mode = :dtmf,
+ @confidence = 1,
+ @utterance = "5678#",
+ @interpretation = "5678#"
+ >
+>> subject.match '1111#'
+=> #<RubySpeech::GRXML::Match:0x000001012f69d8
+ @mode = :dtmf,
+ @confidence = 1,
+ @utterance = "1111#",
+ @interpretation = "1111#"
+ >
+>> subject.match '111'
+=> #<RubySpeech::GRXML::NoMatch:0x00000101371660>
+```
+
Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_speech/master/frames) for more
## Features:
@@ -114,6 +209,13 @@ Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_spee
* `<say-as/>`
* `<break/>`
* `<audio/>`
+* `<p/>` and `<s/>`
+* `<phoneme/>`
+* `<sub/>`
+
+#### Misc
+* `<mark/>`
+* `<desc/>`
### GRXML
* Document construction
@@ -126,17 +228,9 @@ Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_spee
## TODO:
### SSML
-#### Document Structure
-* `<p/>` and `<s/>`
-* `<phoneme/>`
-* `<sub/>`
* `<lexicon/>`
* `<meta/>` and `<metadata/>`
-#### Misc
-* `<mark/>`
-* `<desc/>`
-
### GRXML
* `<meta/>` and `<metadata/>`
* `<example/>`
@@ -4,7 +4,7 @@ module RubySpeech
module GenericElement
def self.included(klass)
- klass.class_attribute :registered_ns, :registered_name
+ klass.class_attribute :registered_ns, :registered_name, :defaults
klass.extend ClassMethods
end
@@ -43,34 +43,81 @@ def class_from_registration(name)
def import(node)
node = Nokogiri::XML.parse(node, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root unless node.is_a?(Nokogiri::XML::Node)
return node.content if node.is_a?(Nokogiri::XML::Text)
- klass = class_from_registration(node.element_name)
+ klass = class_from_registration node.element_name
if klass && klass != self
klass.import node
else
new.inherit node
end
end
- def new(element_name, atts = {}, &block)
+ def new(atts = {}, &block)
blk_proc = lambda do |new_node|
- atts.each_pair { |k, v| new_node.send :"#{k}=", v }
+ (self.defaults || {}).merge(atts).each_pair { |k, v| new_node.send :"#{k}=", v }
block_return = new_node.eval_dsl_block &block
- new_node << new_node.encode_special_chars(block_return) if block_return.is_a?(String)
+ new_node << block_return if block_return.is_a?(String)
end
case RUBY_VERSION.split('.')[0,2].join.to_i
when 18
- super(element_name).tap do |n|
+ super(self.registered_name, nil, self.namespace).tap do |n|
blk_proc[n]
end
else
- super(element_name) do |n|
+ super(self.registered_name, nil, self.namespace) do |n|
blk_proc[n]
end
end
end
end
+ attr_writer :parent
+
+ def parent
+ @parent || super
+ end
+
+ def inherit(node)
+ self.parent = node.parent
+ super
+ end
+
+ def version
+ read_attr :version
+ end
+
+ def version=(other)
+ write_attr :version, other
+ end
+
+ ##
+ # @return [String] the base URI to which relative URLs are resolved
+ #
+ def base_uri
+ read_attr :base
+ end
+
+ ##
+ # @param [String] uri the base URI to which relative URLs are resolved
+ #
+ def base_uri=(uri)
+ write_attr 'xml:base', uri
+ end
+
+ def to_doc
+ Nokogiri::XML::Document.new.tap do |doc|
+ doc << self
+ end
+ end
+
+ def +(other)
+ self.class.new(:base_uri => base_uri).tap do |new_element|
+ (self.children + other.children).each do |child|
+ new_element << child
+ end
+ end
+ end
+
def eval_dsl_block(&block)
return unless block_given?
@block_binding = eval "self", block.binding
@@ -106,26 +153,40 @@ def embed(other)
when self.class.module::Element
self << other
else
- raise ArgumentError, "Can only embed a String or an SSML element"
+ raise ArgumentError, "Can only embed a String or a #{self.class.module} element, not a #{other}"
end
end
def string(other)
- self << encode_special_chars(other)
+ self << other
+ end
+
+ def <<(other)
+ other = encode_special_chars other if other.is_a? String
+ super other
end
def method_missing(method_name, *args, &block)
const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '')
if self.class.module.const_defined?(const_name)
const = self.class.module.const_get const_name
- self << const.new(*args, &block)
+ embed const.new(*args, &block)
elsif @block_binding && @block_binding.respond_to?(method_name)
@block_binding.send method_name, *args, &block
else
super
end
end
+ def clone
+ GRXML.import to_xml
+ end
+
+ def traverse(&block)
+ nokogiri_children.each { |j| j.traverse &block }
+ block.call self
+ end
+
def eql?(o, *args)
super o, :content, :children, *args
end
View
@@ -13,6 +13,9 @@ module GRXML
autoload :Token
end
+ autoload :Match
+ autoload :NoMatch
+
InvalidChildError = Class.new StandardError
GRXML_NAMESPACE = 'http://www.w3.org/2001/06/grammar'
@@ -21,7 +24,7 @@ def self.draw(attributes = {}, &block)
Grammar.new(attributes).tap do |grammar|
block_return = grammar.eval_dsl_block &block
grammar << block_return if block_return.is_a?(String)
- end
+ end.assert_has_matching_root_rule
end
def self.import(other)
@@ -18,6 +18,10 @@ def self.module
alias_method :nokogiri_children, :children
include GenericElement
+
+ def regexp_content # :nodoc:
+ children.map(&:regexp_content).join
+ end
end # Element
end # GRXML
end # RubySpeech
Oops, something went wrong.

0 comments on commit 798638c

Please sign in to comment.