ruby_speech 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/CHANGELOG.md +15 -0
  2. data/README.md +108 -14
  3. data/lib/ruby_speech/generic_element.rb +71 -10
  4. data/lib/ruby_speech/grxml.rb +4 -1
  5. data/lib/ruby_speech/grxml/element.rb +4 -0
  6. data/lib/ruby_speech/grxml/grammar.rb +177 -46
  7. data/lib/ruby_speech/grxml/item.rb +12 -11
  8. data/lib/ruby_speech/grxml/match.rb +16 -0
  9. data/lib/ruby_speech/grxml/no_match.rb +10 -0
  10. data/lib/ruby_speech/grxml/one_of.rb +4 -11
  11. data/lib/ruby_speech/grxml/rule.rb +0 -11
  12. data/lib/ruby_speech/grxml/ruleref.rb +0 -11
  13. data/lib/ruby_speech/grxml/tag.rb +0 -11
  14. data/lib/ruby_speech/grxml/token.rb +8 -11
  15. data/lib/ruby_speech/ssml.rb +6 -0
  16. data/lib/ruby_speech/ssml/audio.rb +1 -12
  17. data/lib/ruby_speech/ssml/break.rb +0 -11
  18. data/lib/ruby_speech/ssml/desc.rb +24 -0
  19. data/lib/ruby_speech/ssml/emphasis.rb +1 -12
  20. data/lib/ruby_speech/ssml/mark.rb +43 -0
  21. data/lib/ruby_speech/ssml/p.rb +25 -0
  22. data/lib/ruby_speech/ssml/phoneme.rb +72 -0
  23. data/lib/ruby_speech/ssml/prosody.rb +1 -12
  24. data/lib/ruby_speech/ssml/s.rb +25 -0
  25. data/lib/ruby_speech/ssml/say_as.rb +0 -11
  26. data/lib/ruby_speech/ssml/speak.rb +2 -44
  27. data/lib/ruby_speech/ssml/sub.rb +42 -0
  28. data/lib/ruby_speech/ssml/voice.rb +1 -12
  29. data/lib/ruby_speech/version.rb +1 -1
  30. data/spec/ruby_speech/grxml/grammar_spec.rb +478 -35
  31. data/spec/ruby_speech/grxml/item_spec.rb +5 -2
  32. data/spec/ruby_speech/grxml/match_spec.rb +49 -0
  33. data/spec/ruby_speech/grxml/no_match_spec.rb +17 -0
  34. data/spec/ruby_speech/grxml/one_of_spec.rb +1 -1
  35. data/spec/ruby_speech/grxml/rule_spec.rb +1 -1
  36. data/spec/ruby_speech/grxml/ruleref_spec.rb +1 -1
  37. data/spec/ruby_speech/grxml/tag_spec.rb +1 -1
  38. data/spec/ruby_speech/grxml/token_spec.rb +11 -1
  39. data/spec/ruby_speech/grxml_spec.rb +64 -5
  40. data/spec/ruby_speech/ssml/audio_spec.rb +5 -6
  41. data/spec/ruby_speech/ssml/break_spec.rb +1 -1
  42. data/spec/ruby_speech/ssml/desc_spec.rb +57 -0
  43. data/spec/ruby_speech/ssml/emphasis_spec.rb +1 -4
  44. data/spec/ruby_speech/ssml/mark_spec.rb +53 -0
  45. data/spec/ruby_speech/ssml/p_spec.rb +96 -0
  46. data/spec/ruby_speech/ssml/phoneme_spec.rb +65 -0
  47. data/spec/ruby_speech/ssml/prosody_spec.rb +9 -4
  48. data/spec/ruby_speech/ssml/s_spec.rb +92 -0
  49. data/spec/ruby_speech/ssml/say_as_spec.rb +1 -1
  50. data/spec/ruby_speech/ssml/speak_spec.rb +1 -6
  51. data/spec/ruby_speech/ssml/sub_spec.rb +57 -0
  52. data/spec/ruby_speech/ssml/voice_spec.rb +1 -6
  53. data/spec/spec_helper.rb +0 -4
  54. data/spec/support/matchers.rb +13 -53
  55. metadata +200 -113
@@ -1,5 +1,20 @@
1
1
  # develop
2
2
 
3
+ # 0.5.0 - 2012-01-03
4
+ * Feature: Add a whole bunch more SSML elements:
5
+ ** p & s
6
+ ** mark
7
+ ** desc
8
+ ** sub
9
+ ** phoneme
10
+ * Feature: Added the ability to inline grammar rule references in both destructive and non-destructive modes
11
+ * Feature: Added the ability to tokenize a grammar, turning all tokens into unambiguous `<token/>` elements
12
+ * Feature: Added the ability to whitespace normalize a grammar
13
+ * Feature: Added the ability to match an input string against a Grammar
14
+ * Feature: Constructing a GRXML grammar with a root rule specified but not provided will raise an exception
15
+ * Feature: Embedding a GRXML grammar of a mode different from the host will raise an exception
16
+ * Bugfix: Fix upward traversal through a document via #parent
17
+
3
18
  # 0.4.0 - 2011-12-30
4
19
  * Feature: Add the ability to look up child elements by name/attributes easily
5
20
  * Feature: Allow easy access to a GRXML grammar's root rule element
data/README.md CHANGED
@@ -46,15 +46,15 @@ Once your `Speak` is fully prepared and you're ready to send it off for processi
46
46
  You may also then need to call `to_s`.
47
47
 
48
48
 
49
- Contruct a GRXML (SRGS) document like this:
49
+ Construct a GRXML (SRGS) document like this:
50
50
 
51
51
  ```ruby
52
52
  require 'ruby_speech'
53
53
 
54
- grammy = RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'digits' do
55
- rule id: 'digits' do
54
+ grammy = RubySpeech::GRXML.draw mode: :dtmf, root: 'pin' do
55
+ rule id: 'digit' do
56
56
  one_of do
57
- 0.upto(9) {|d| item { d.to_s } }
57
+ ('0'..'9').map { |d| item { d } }
58
58
  end
59
59
  end
60
60
 
@@ -79,8 +79,8 @@ grammy.to_s
79
79
  which becomes
80
80
 
81
81
  ```xml
82
- <grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="digits">
83
- <rule id="digits">
82
+ <grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="pin">
83
+ <rule id="digit">
84
84
  <one-of>
85
85
  <item>0</item>
86
86
  <item>1</item>
@@ -103,6 +103,101 @@ which becomes
103
103
  </grammar>
104
104
  ```
105
105
 
106
+ ### Grammar matching
107
+
108
+ 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:
109
+
110
+ ```ruby
111
+ grammy.inline!
112
+ grammy.tokenize!
113
+ grammy.normalize_whitespace
114
+ ```
115
+
116
+ ```xml
117
+ <grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="pin">
118
+ <rule id="pin" scope="public">
119
+ <one-of>
120
+ <item>
121
+ <item repeat="4">
122
+ <one-of>
123
+ <item>
124
+ <token>0</token>
125
+ </item>
126
+ <item>
127
+ <token>1</token>
128
+ </item>
129
+ <item>
130
+ <token>2</token>
131
+ </item>
132
+ <item>
133
+ <token>3</token>
134
+ </item>
135
+ <item>
136
+ <token>4</token>
137
+ </item>
138
+ <item>
139
+ <token>5</token>
140
+ </item>
141
+ <item>
142
+ <token>6</token>
143
+ </item>
144
+ <item>
145
+ <token>7</token>
146
+ </item>
147
+ <item>
148
+ <token>8</token>
149
+ </item>
150
+ <item>
151
+ <token>9</token>
152
+ </item>
153
+ </one-of>
154
+ </item>
155
+ <token>#</token>
156
+ </item>
157
+ <item>
158
+ <token>*</token>
159
+ <token>9</token>
160
+ </item>
161
+ </one-of>
162
+ </rule>
163
+ </grammar>
164
+ ```
165
+
166
+ Matching against some sample input strings then returns the following results:
167
+
168
+ ```ruby
169
+ >> subject.match '*9'
170
+ => #<RubySpeech::GRXML::Match:0x00000100ae5d98
171
+ @mode = :dtmf,
172
+ @confidence = 1,
173
+ @utterance = "*9",
174
+ @interpretation = "*9"
175
+ >
176
+ >> subject.match '1234#'
177
+ => #<RubySpeech::GRXML::Match:0x00000100b7e020
178
+ @mode = :dtmf,
179
+ @confidence = 1,
180
+ @utterance = "1234#",
181
+ @interpretation = "1234#"
182
+ >
183
+ >> subject.match '5678#'
184
+ => #<RubySpeech::GRXML::Match:0x00000101218688
185
+ @mode = :dtmf,
186
+ @confidence = 1,
187
+ @utterance = "5678#",
188
+ @interpretation = "5678#"
189
+ >
190
+ >> subject.match '1111#'
191
+ => #<RubySpeech::GRXML::Match:0x000001012f69d8
192
+ @mode = :dtmf,
193
+ @confidence = 1,
194
+ @utterance = "1111#",
195
+ @interpretation = "1111#"
196
+ >
197
+ >> subject.match '111'
198
+ => #<RubySpeech::GRXML::NoMatch:0x00000101371660>
199
+ ```
200
+
106
201
  Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_speech/master/frames) for more
107
202
 
108
203
  ## Features:
@@ -114,6 +209,13 @@ Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_spee
114
209
  * `<say-as/>`
115
210
  * `<break/>`
116
211
  * `<audio/>`
212
+ * `<p/>` and `<s/>`
213
+ * `<phoneme/>`
214
+ * `<sub/>`
215
+
216
+ #### Misc
217
+ * `<mark/>`
218
+ * `<desc/>`
117
219
 
118
220
  ### GRXML
119
221
  * Document construction
@@ -126,17 +228,9 @@ Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_spee
126
228
 
127
229
  ## TODO:
128
230
  ### SSML
129
- #### Document Structure
130
- * `<p/>` and `<s/>`
131
- * `<phoneme/>`
132
- * `<sub/>`
133
231
  * `<lexicon/>`
134
232
  * `<meta/>` and `<metadata/>`
135
233
 
136
- #### Misc
137
- * `<mark/>`
138
- * `<desc/>`
139
-
140
234
  ### GRXML
141
235
  * `<meta/>` and `<metadata/>`
142
236
  * `<example/>`
@@ -4,7 +4,7 @@ module RubySpeech
4
4
  module GenericElement
5
5
 
6
6
  def self.included(klass)
7
- klass.class_attribute :registered_ns, :registered_name
7
+ klass.class_attribute :registered_ns, :registered_name, :defaults
8
8
  klass.extend ClassMethods
9
9
  end
10
10
 
@@ -43,7 +43,7 @@ module RubySpeech
43
43
  def import(node)
44
44
  node = Nokogiri::XML.parse(node, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root unless node.is_a?(Nokogiri::XML::Node)
45
45
  return node.content if node.is_a?(Nokogiri::XML::Text)
46
- klass = class_from_registration(node.element_name)
46
+ klass = class_from_registration node.element_name
47
47
  if klass && klass != self
48
48
  klass.import node
49
49
  else
@@ -51,26 +51,73 @@ module RubySpeech
51
51
  end
52
52
  end
53
53
 
54
- def new(element_name, atts = {}, &block)
54
+ def new(atts = {}, &block)
55
55
  blk_proc = lambda do |new_node|
56
- atts.each_pair { |k, v| new_node.send :"#{k}=", v }
56
+ (self.defaults || {}).merge(atts).each_pair { |k, v| new_node.send :"#{k}=", v }
57
57
  block_return = new_node.eval_dsl_block &block
58
- new_node << new_node.encode_special_chars(block_return) if block_return.is_a?(String)
58
+ new_node << block_return if block_return.is_a?(String)
59
59
  end
60
60
 
61
61
  case RUBY_VERSION.split('.')[0,2].join.to_i
62
62
  when 18
63
- super(element_name).tap do |n|
63
+ super(self.registered_name, nil, self.namespace).tap do |n|
64
64
  blk_proc[n]
65
65
  end
66
66
  else
67
- super(element_name) do |n|
67
+ super(self.registered_name, nil, self.namespace) do |n|
68
68
  blk_proc[n]
69
69
  end
70
70
  end
71
71
  end
72
72
  end
73
73
 
74
+ attr_writer :parent
75
+
76
+ def parent
77
+ @parent || super
78
+ end
79
+
80
+ def inherit(node)
81
+ self.parent = node.parent
82
+ super
83
+ end
84
+
85
+ def version
86
+ read_attr :version
87
+ end
88
+
89
+ def version=(other)
90
+ write_attr :version, other
91
+ end
92
+
93
+ ##
94
+ # @return [String] the base URI to which relative URLs are resolved
95
+ #
96
+ def base_uri
97
+ read_attr :base
98
+ end
99
+
100
+ ##
101
+ # @param [String] uri the base URI to which relative URLs are resolved
102
+ #
103
+ def base_uri=(uri)
104
+ write_attr 'xml:base', uri
105
+ end
106
+
107
+ def to_doc
108
+ Nokogiri::XML::Document.new.tap do |doc|
109
+ doc << self
110
+ end
111
+ end
112
+
113
+ def +(other)
114
+ self.class.new(:base_uri => base_uri).tap do |new_element|
115
+ (self.children + other.children).each do |child|
116
+ new_element << child
117
+ end
118
+ end
119
+ end
120
+
74
121
  def eval_dsl_block(&block)
75
122
  return unless block_given?
76
123
  @block_binding = eval "self", block.binding
@@ -106,19 +153,24 @@ module RubySpeech
106
153
  when self.class.module::Element
107
154
  self << other
108
155
  else
109
- raise ArgumentError, "Can only embed a String or an SSML element"
156
+ raise ArgumentError, "Can only embed a String or a #{self.class.module} element, not a #{other}"
110
157
  end
111
158
  end
112
159
 
113
160
  def string(other)
114
- self << encode_special_chars(other)
161
+ self << other
162
+ end
163
+
164
+ def <<(other)
165
+ other = encode_special_chars other if other.is_a? String
166
+ super other
115
167
  end
116
168
 
117
169
  def method_missing(method_name, *args, &block)
118
170
  const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '')
119
171
  if self.class.module.const_defined?(const_name)
120
172
  const = self.class.module.const_get const_name
121
- self << const.new(*args, &block)
173
+ embed const.new(*args, &block)
122
174
  elsif @block_binding && @block_binding.respond_to?(method_name)
123
175
  @block_binding.send method_name, *args, &block
124
176
  else
@@ -126,6 +178,15 @@ module RubySpeech
126
178
  end
127
179
  end
128
180
 
181
+ def clone
182
+ GRXML.import to_xml
183
+ end
184
+
185
+ def traverse(&block)
186
+ nokogiri_children.each { |j| j.traverse &block }
187
+ block.call self
188
+ end
189
+
129
190
  def eql?(o, *args)
130
191
  super o, :content, :children, *args
131
192
  end
@@ -13,6 +13,9 @@ module RubySpeech
13
13
  autoload :Token
14
14
  end
15
15
 
16
+ autoload :Match
17
+ autoload :NoMatch
18
+
16
19
  InvalidChildError = Class.new StandardError
17
20
 
18
21
  GRXML_NAMESPACE = 'http://www.w3.org/2001/06/grammar'
@@ -21,7 +24,7 @@ module RubySpeech
21
24
  Grammar.new(attributes).tap do |grammar|
22
25
  block_return = grammar.eval_dsl_block &block
23
26
  grammar << block_return if block_return.is_a?(String)
24
- end
27
+ end.assert_has_matching_root_rule
25
28
  end
26
29
 
27
30
  def self.import(other)
@@ -18,6 +18,10 @@ module RubySpeech
18
18
  alias_method :nokogiri_children, :children
19
19
 
20
20
  include GenericElement
21
+
22
+ def regexp_content # :nodoc:
23
+ children.map(&:regexp_content).join
24
+ end
21
25
  end # Element
22
26
  end # GRXML
23
27
  end # RubySpeech
@@ -18,37 +18,9 @@ module RubySpeech
18
18
 
19
19
  register :grammar
20
20
 
21
- VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, Rule, Tag].freeze
22
-
23
- ##
24
- # Create a new GRXML grammar root element
25
- #
26
- # @param [Hash] atts Key-value pairs of options mapping to setter methods
27
- #
28
- # @return [Grammar] an element for use in an GRXML document
29
- #
30
- def self.new(atts = {}, &block)
31
- new_node = super('grammar', atts)
32
- new_node[:version] = '1.0'
33
- new_node.namespace = GRXML_NAMESPACE
34
- new_node.language ||= "en-US"
35
- new_node.eval_dsl_block &block
36
- new_node
37
- end
38
-
39
- ##
40
- # @return [String] the base URI to which relative URLs are resolved
41
- #
42
- def base_uri
43
- read_attr :base
44
- end
21
+ self.defaults = { :version => '1.0', :language => "en-US" }
45
22
 
46
- ##
47
- # @param [String] uri the base URI to which relative URLs are resolved
48
- #
49
- def base_uri=(uri)
50
- write_attr 'xml:base', uri
51
- end
23
+ VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, Rule, Tag].freeze
52
24
 
53
25
  ##
54
26
  #
@@ -57,7 +29,7 @@ module RubySpeech
57
29
  # @return [String]
58
30
  #
59
31
  def mode
60
- read_attr :mode
32
+ read_attr :mode, :to_sym
61
33
  end
62
34
 
63
35
  ##
@@ -84,17 +56,6 @@ module RubySpeech
84
56
  write_attr :root, ia
85
57
  end
86
58
 
87
- def <<(arg)
88
- raise InvalidChildError, "A Grammar can only accept Rule and Tag as children" unless VALID_CHILD_TYPES.include? arg.class
89
- super
90
- end
91
-
92
- def to_doc
93
- Nokogiri::XML::Document.new.tap do |doc|
94
- doc << self
95
- end
96
- end
97
-
98
59
  ##
99
60
  #
100
61
  # @return [String]
@@ -110,11 +71,39 @@ module RubySpeech
110
71
  write_attr :'tag-format', s
111
72
  end
112
73
 
74
+ ##
75
+ # @return [Rule] The root rule node for the document
76
+ #
113
77
  def root_rule
114
78
  children(:rule, :id => root).first
115
79
  end
116
80
 
81
+ ##
82
+ # Checks for a root rule matching the value of the root tag
83
+ #
84
+ # @raises [InvalidChildError] if there is not a rule present in the document with the correct ID
85
+ #
86
+ # @return [Grammar] self
87
+ #
88
+ def assert_has_matching_root_rule
89
+ raise InvalidChildError, "A GRXML document must have a rule matching the root rule name" unless has_matching_root_rule?
90
+ self
91
+ end
92
+
93
+ ##
94
+ # @return [Grammar] an inlined copy of self
95
+ #
117
96
  def inline
97
+ clone.inline!
98
+ end
99
+
100
+ ##
101
+ # Replaces rulerefs in the document with a copy of the original rule.
102
+ # Removes all top level rules except the root rule
103
+ #
104
+ # @return self
105
+ #
106
+ def inline!
118
107
  find("//ns:ruleref", :ns => namespace_href).each do |ref|
119
108
  rule = children(:rule, :id => ref[:uri].sub(/^#/, '')).first
120
109
  ref.swap rule.nokogiri_children
@@ -126,17 +115,159 @@ module RubySpeech
126
115
  self
127
116
  end
128
117
 
129
- def +(other)
130
- self.class.new(:base_uri => base_uri).tap do |new_grammar|
131
- (self.children + other.children).each do |child|
132
- new_grammar << child
118
+ ##
119
+ # Replaces textual content of the document with token elements containing such content.
120
+ # This homogenises all tokens in the document to a consistent format for processing.
121
+ #
122
+ def tokenize!
123
+ traverse do |element|
124
+ next unless element.is_a? Nokogiri::XML::Text
125
+
126
+ next if self.class.import(element.parent).is_a? Token
127
+
128
+ tokens = split_tokens(element).map do |string|
129
+ Token.new.tap { |token| token << string }
133
130
  end
131
+
132
+ element.swap Nokogiri::XML::NodeSet.new(Nokogiri::XML::Document.new, tokens)
134
133
  end
135
134
  end
136
135
 
136
+ ##
137
+ # Normalizes whitespace within tokens in the document according to the rules in the SRGS spec (http://www.w3.org/TR/speech-grammar/#S2.1)
138
+ # Leading and trailing whitespace is removed, and multiple spaces within the string are collapsed down to single spaces.
139
+ #
140
+ def normalize_whitespace
141
+ traverse do |element|
142
+ next if element === self
143
+
144
+ imported_element = self.class.import element
145
+ next unless imported_element.respond_to? :normalize_whitespace
146
+
147
+ imported_element.normalize_whitespace
148
+ element.swap imported_element
149
+ end
150
+ end
151
+
152
+ ##
153
+ # Checks the grammar for a match against an input string
154
+ #
155
+ # @param [String] other the input string to check for a match with the grammar
156
+ #
157
+ # @return [NoMatch, Match] depending on the result of a match attempt. If a match can be found, it will be returned with appropriate mode/confidence/utterance and interpretation attributes
158
+ #
159
+ # @example A grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence
160
+ # ```ruby
161
+ # grammar = RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
162
+ # rule :id => 'digit' do
163
+ # one_of do
164
+ # ('0'..'9').map { |d| item { d } }
165
+ # end
166
+ # end
167
+ #
168
+ # rule :id => 'pin', :scope => 'public' do
169
+ # one_of do
170
+ # item do
171
+ # item :repeat => '4' do
172
+ # ruleref :uri => '#digit'
173
+ # end
174
+ # "#"
175
+ # end
176
+ # item do
177
+ # "\* 9"
178
+ # end
179
+ # end
180
+ # end
181
+ # end
182
+ #
183
+ # >> subject.match '*9'
184
+ # => #<RubySpeech::GRXML::Match:0x00000100ae5d98
185
+ # @mode = :dtmf,
186
+ # @confidence = 1,
187
+ # @utterance = "*9",
188
+ # @interpretation = "*9"
189
+ # >
190
+ # >> subject.match '1234#'
191
+ # => #<RubySpeech::GRXML::Match:0x00000100b7e020
192
+ # @mode = :dtmf,
193
+ # @confidence = 1,
194
+ # @utterance = "1234#",
195
+ # @interpretation = "1234#"
196
+ # >
197
+ # >> subject.match '111'
198
+ # => #<RubySpeech::GRXML::NoMatch:0x00000101371660>
199
+ #
200
+ # ```
201
+ #
202
+ def match(other)
203
+ regex = to_regexp
204
+ return NoMatch.new if regex == //
205
+ match = regex.match other
206
+ return NoMatch.new unless match
207
+
208
+ Match.new :mode => mode,
209
+ :confidence => dtmf? ? 1 : 0,
210
+ :utterance => other,
211
+ :interpretation => interpret_utterance(other)
212
+ end
213
+
214
+ ##
215
+ # Converts the grammar into a regular expression for matching
216
+ #
217
+ # @return [Regexp] a regular expression which is equivalent to the grammar
218
+ #
219
+ def to_regexp
220
+ /^#{regexp_content.join}$/
221
+ end
222
+
223
+ def regexp_content
224
+ root_rule.children.map &:regexp_content
225
+ end
226
+
227
+ def dtmf?
228
+ mode == :dtmf
229
+ end
230
+
231
+ def voice?
232
+ mode == :voice
233
+ end
234
+
235
+ def <<(arg)
236
+ raise InvalidChildError, "A Grammar can only accept Rule and Tag as children" unless VALID_CHILD_TYPES.include? arg.class
237
+ super
238
+ end
239
+
137
240
  def eql?(o)
138
241
  super o, :language, :base_uri, :mode, :root
139
242
  end
243
+
244
+ def embed(other)
245
+ raise InvalidChildError, "Embedded grammars must have the same mode" if other.is_a?(self.class) && other.mode != mode
246
+ super
247
+ end
248
+
249
+ private
250
+
251
+ def has_matching_root_rule?
252
+ !root || root_rule
253
+ end
254
+
255
+ def interpret_utterance(utterance)
256
+ conversion = Hash.new { |hash, key| hash[key] = key }
257
+ conversion['*'] = 'star'
258
+ conversion['#'] = 'pound'
259
+
260
+ utterance.chars.inject [] do |array, digit|
261
+ array << "dtmf-#{conversion[digit]}"
262
+ end.join ' '
263
+ end
264
+
265
+ def split_tokens(element)
266
+ element.to_s.split(/(\".*\")/).reject(&:empty?).map do |string|
267
+ match = string.match /^\"(.*)\"$/
268
+ match ? match[1] : string.split(' ')
269
+ end.flatten
270
+ end
140
271
  end # Grammar
141
272
  end # GRXML
142
273
  end # RubySpeech