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.
- data/CHANGELOG.md +15 -0
- data/README.md +108 -14
- data/lib/ruby_speech/generic_element.rb +71 -10
- data/lib/ruby_speech/grxml.rb +4 -1
- data/lib/ruby_speech/grxml/element.rb +4 -0
- data/lib/ruby_speech/grxml/grammar.rb +177 -46
- data/lib/ruby_speech/grxml/item.rb +12 -11
- data/lib/ruby_speech/grxml/match.rb +16 -0
- data/lib/ruby_speech/grxml/no_match.rb +10 -0
- data/lib/ruby_speech/grxml/one_of.rb +4 -11
- data/lib/ruby_speech/grxml/rule.rb +0 -11
- data/lib/ruby_speech/grxml/ruleref.rb +0 -11
- data/lib/ruby_speech/grxml/tag.rb +0 -11
- data/lib/ruby_speech/grxml/token.rb +8 -11
- data/lib/ruby_speech/ssml.rb +6 -0
- data/lib/ruby_speech/ssml/audio.rb +1 -12
- data/lib/ruby_speech/ssml/break.rb +0 -11
- data/lib/ruby_speech/ssml/desc.rb +24 -0
- data/lib/ruby_speech/ssml/emphasis.rb +1 -12
- data/lib/ruby_speech/ssml/mark.rb +43 -0
- data/lib/ruby_speech/ssml/p.rb +25 -0
- data/lib/ruby_speech/ssml/phoneme.rb +72 -0
- data/lib/ruby_speech/ssml/prosody.rb +1 -12
- data/lib/ruby_speech/ssml/s.rb +25 -0
- data/lib/ruby_speech/ssml/say_as.rb +0 -11
- data/lib/ruby_speech/ssml/speak.rb +2 -44
- data/lib/ruby_speech/ssml/sub.rb +42 -0
- data/lib/ruby_speech/ssml/voice.rb +1 -12
- data/lib/ruby_speech/version.rb +1 -1
- data/spec/ruby_speech/grxml/grammar_spec.rb +478 -35
- data/spec/ruby_speech/grxml/item_spec.rb +5 -2
- data/spec/ruby_speech/grxml/match_spec.rb +49 -0
- data/spec/ruby_speech/grxml/no_match_spec.rb +17 -0
- data/spec/ruby_speech/grxml/one_of_spec.rb +1 -1
- data/spec/ruby_speech/grxml/rule_spec.rb +1 -1
- data/spec/ruby_speech/grxml/ruleref_spec.rb +1 -1
- data/spec/ruby_speech/grxml/tag_spec.rb +1 -1
- data/spec/ruby_speech/grxml/token_spec.rb +11 -1
- data/spec/ruby_speech/grxml_spec.rb +64 -5
- data/spec/ruby_speech/ssml/audio_spec.rb +5 -6
- data/spec/ruby_speech/ssml/break_spec.rb +1 -1
- data/spec/ruby_speech/ssml/desc_spec.rb +57 -0
- data/spec/ruby_speech/ssml/emphasis_spec.rb +1 -4
- data/spec/ruby_speech/ssml/mark_spec.rb +53 -0
- data/spec/ruby_speech/ssml/p_spec.rb +96 -0
- data/spec/ruby_speech/ssml/phoneme_spec.rb +65 -0
- data/spec/ruby_speech/ssml/prosody_spec.rb +9 -4
- data/spec/ruby_speech/ssml/s_spec.rb +92 -0
- data/spec/ruby_speech/ssml/say_as_spec.rb +1 -1
- data/spec/ruby_speech/ssml/speak_spec.rb +1 -6
- data/spec/ruby_speech/ssml/sub_spec.rb +57 -0
- data/spec/ruby_speech/ssml/voice_spec.rb +1 -6
- data/spec/spec_helper.rb +0 -4
- data/spec/support/matchers.rb +13 -53
- metadata +200 -113
@@ -0,0 +1,25 @@
|
|
1
|
+
module RubySpeech
|
2
|
+
module SSML
|
3
|
+
##
|
4
|
+
# As s element represents a sentence.
|
5
|
+
# The use of s elements is optional. Where text occurs without an enclosing s element the synthesis processor should attempt to determine the structure using language-specific knowledge of the format of plain text.
|
6
|
+
#
|
7
|
+
# http://www.w3.org/TR/speech-synthesis/#S3.1.7
|
8
|
+
#
|
9
|
+
class S < Element
|
10
|
+
|
11
|
+
register :s
|
12
|
+
|
13
|
+
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Mark, Phoneme, Prosody, SayAs, Sub, Voice].freeze
|
14
|
+
|
15
|
+
def <<(arg)
|
16
|
+
raise InvalidChildError, "An S can only accept String, Audio, Break, Emphasis, Mark, Phoneme, Prosody, SayAs, Sub, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def eql?(o)
|
21
|
+
super o, :language
|
22
|
+
end
|
23
|
+
end # S
|
24
|
+
end # SSML
|
25
|
+
end # RubySpeech
|
@@ -26,17 +26,6 @@ module RubySpeech
|
|
26
26
|
|
27
27
|
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String].freeze
|
28
28
|
|
29
|
-
##
|
30
|
-
# Create a new SSML say-as element
|
31
|
-
#
|
32
|
-
# @param [Hash] atts Key-value pairs of options mapping to setter methods
|
33
|
-
#
|
34
|
-
# @return [Prosody] an element for use in an SSML document
|
35
|
-
#
|
36
|
-
def self.new(atts = {}, &block)
|
37
|
-
super 'say-as', atts, &block
|
38
|
-
end
|
39
|
-
|
40
29
|
##
|
41
30
|
#
|
42
31
|
# The interpret-as attribute indicates the content type of the contained text construct. Specifying the content type helps the synthesis processor to distinguish and interpret text constructs that may be rendered in different ways depending on what type of information is intended.
|
@@ -10,57 +10,15 @@ module RubySpeech
|
|
10
10
|
|
11
11
|
register :speak
|
12
12
|
|
13
|
-
|
13
|
+
self.defaults = { :version => '1.0', :language => "en-US" }
|
14
14
|
|
15
|
-
|
16
|
-
# Create a new SSML speak root element
|
17
|
-
#
|
18
|
-
# @param [Hash] atts Key-value pairs of options mapping to setter methods
|
19
|
-
#
|
20
|
-
# @return [Speak] an element for use in an SSML document
|
21
|
-
#
|
22
|
-
def self.new(atts = {}, &block)
|
23
|
-
new_node = super('speak', atts)
|
24
|
-
new_node[:version] = '1.0'
|
25
|
-
new_node.namespace = SSML_NAMESPACE
|
26
|
-
new_node.language ||= "en-US"
|
27
|
-
new_node.eval_dsl_block &block
|
28
|
-
new_node
|
29
|
-
end
|
30
|
-
|
31
|
-
##
|
32
|
-
# @return [String] the base URI to which relative URLs are resolved
|
33
|
-
#
|
34
|
-
def base_uri
|
35
|
-
read_attr :base
|
36
|
-
end
|
37
|
-
|
38
|
-
##
|
39
|
-
# @param [String] uri the base URI to which relative URLs are resolved
|
40
|
-
#
|
41
|
-
def base_uri=(uri)
|
42
|
-
write_attr 'xml:base', uri
|
43
|
-
end
|
15
|
+
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, S, SayAs, Sub, Voice].freeze
|
44
16
|
|
45
17
|
def <<(arg)
|
46
18
|
raise InvalidChildError, "A Speak can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
|
47
19
|
super
|
48
20
|
end
|
49
21
|
|
50
|
-
def to_doc
|
51
|
-
Nokogiri::XML::Document.new.tap do |doc|
|
52
|
-
doc << self
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def +(other)
|
57
|
-
self.class.new(:base_uri => base_uri).tap do |new_speak|
|
58
|
-
(self.children + other.children).each do |child|
|
59
|
-
new_speak << child
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
22
|
def eql?(o)
|
65
23
|
super o, :language, :base_uri
|
66
24
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RubySpeech
|
2
|
+
module SSML
|
3
|
+
##
|
4
|
+
# The sub element is employed to indicate that the text in the alias attribute value replaces the contained text for pronunciation. This allows a document to contain both a spoken and written form. The required alias attribute specifies the string to be spoken instead of the enclosed string. The processor should apply text normalization to the alias value.
|
5
|
+
#
|
6
|
+
# The sub element can only contain text (no elements).
|
7
|
+
#
|
8
|
+
# http://www.w3.org/TR/speech-synthesis/#S3.1.10
|
9
|
+
#
|
10
|
+
class Sub < Element
|
11
|
+
|
12
|
+
register :sub
|
13
|
+
|
14
|
+
VALID_CHILD_TYPES = [Nokogiri::XML::Text, String].freeze
|
15
|
+
|
16
|
+
##
|
17
|
+
# Indicates the string to be spoken instead of the enclosed string
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
#
|
21
|
+
def alias
|
22
|
+
read_attr :alias
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# @param [String] other the string to be spoken instead of the enclosed string
|
27
|
+
#
|
28
|
+
def alias=(other)
|
29
|
+
write_attr :alias, other
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(arg)
|
33
|
+
raise InvalidChildError, "A Sub can only accept Strings as children" unless VALID_CHILD_TYPES.include? arg.class
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def eql?(o)
|
38
|
+
super o, :alias
|
39
|
+
end
|
40
|
+
end # Sub
|
41
|
+
end # SSML
|
42
|
+
end # RubySpeech
|
@@ -11,18 +11,7 @@ module RubySpeech
|
|
11
11
|
register :voice
|
12
12
|
|
13
13
|
VALID_GENDERS = [:male, :female, :neutral].freeze
|
14
|
-
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio,Break, Emphasis, Prosody, SayAs, Voice].freeze
|
15
|
-
|
16
|
-
##
|
17
|
-
# Create a new SSML voice element
|
18
|
-
#
|
19
|
-
# @param [Hash] atts Key-value pairs of options mapping to setter methods
|
20
|
-
#
|
21
|
-
# @return [Voice] an element for use in an SSML document
|
22
|
-
#
|
23
|
-
def self.new(atts = {}, &block)
|
24
|
-
super 'voice', atts, &block
|
25
|
-
end
|
14
|
+
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, S, SayAs, Sub, Voice].freeze
|
26
15
|
|
27
16
|
##
|
28
17
|
# Indicates the preferred gender of the voice to speak the contained text. Enumerated values are: "male", "female", "neutral".
|
data/lib/ruby_speech/version.rb
CHANGED
@@ -18,13 +18,17 @@ module RubySpeech
|
|
18
18
|
end
|
19
19
|
|
20
20
|
describe "setting dtmf mode" do
|
21
|
-
subject
|
22
|
-
its(:mode)
|
21
|
+
subject { Grammar.new :mode => 'dtmf' }
|
22
|
+
its(:mode) { should == :dtmf }
|
23
|
+
its(:dtmf?) { should be true }
|
24
|
+
its(:voice?) { should be false }
|
23
25
|
end
|
24
26
|
|
25
27
|
describe "setting voice mode" do
|
26
|
-
subject
|
27
|
-
its(:mode)
|
28
|
+
subject { Grammar.new :mode => 'voice' }
|
29
|
+
its(:mode) { should == :voice }
|
30
|
+
its(:voice?) { should be true }
|
31
|
+
its(:dtmf?) { should be false }
|
28
32
|
end
|
29
33
|
|
30
34
|
it 'registers itself' do
|
@@ -38,13 +42,13 @@ module RubySpeech
|
|
38
42
|
http://www.w3.org/TR/speech-grammar/grammar.xsd"
|
39
43
|
xmlns="http://www.w3.org/2001/06/grammar" />' }
|
40
44
|
|
41
|
-
subject { Element.import
|
45
|
+
subject { Element.import document }
|
42
46
|
|
43
47
|
it { should be_instance_of Grammar }
|
44
48
|
|
45
|
-
its(:language) {
|
49
|
+
its(:language) { should == 'jp' }
|
46
50
|
its(:base_uri) { should == 'blah' }
|
47
|
-
its(:mode) { should ==
|
51
|
+
its(:mode) { should == :dtmf }
|
48
52
|
its(:root) { should == 'main_rule' }
|
49
53
|
end
|
50
54
|
|
@@ -162,52 +166,491 @@ module RubySpeech
|
|
162
166
|
grammar.root_rule.should == foo
|
163
167
|
end
|
164
168
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
169
|
+
describe "inlining rule references" do
|
170
|
+
let :grammar do
|
171
|
+
GRXML.draw :root => 'pin', :mode => :dtmf do
|
172
|
+
rule :id => 'digits' do
|
173
|
+
one_of do
|
174
|
+
0.upto(9) { |d| item { d.to_s } }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
rule :id => 'pin', :scope => 'public' do
|
179
|
+
one_of do
|
180
|
+
item do
|
181
|
+
item :repeat => '4' do
|
182
|
+
ruleref :uri => '#digits'
|
183
|
+
end
|
184
|
+
"#"
|
185
|
+
end
|
186
|
+
item do
|
187
|
+
"* 9"
|
188
|
+
end
|
189
|
+
end
|
170
190
|
end
|
171
191
|
end
|
192
|
+
end
|
172
193
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
194
|
+
let :inline_grammar do
|
195
|
+
GRXML.draw :root => 'pin', :mode => :dtmf do
|
196
|
+
rule :id => 'pin', :scope => 'public' do
|
197
|
+
one_of do
|
198
|
+
item do
|
199
|
+
item :repeat => '4' do
|
200
|
+
one_of do
|
201
|
+
0.upto(9) { |d| item { d.to_s } }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
"#"
|
178
205
|
end
|
179
|
-
|
206
|
+
item do
|
207
|
+
"* 9"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should be possible in a non-destructive manner" do
|
215
|
+
grammar.inline.should == inline_grammar
|
216
|
+
grammar.should_not == inline_grammar
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should be possible in a destructive manner" do
|
220
|
+
grammar.inline!.should == inline_grammar
|
221
|
+
grammar.should == inline_grammar
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "#tokenize!" do
|
226
|
+
def single_rule_grammar(content = [])
|
227
|
+
GRXML.draw :root => 'm', :mode => :speech do
|
228
|
+
rule :id => 'm' do
|
229
|
+
Array(content).each { |e| embed e }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
subject { single_rule_grammar content }
|
235
|
+
|
236
|
+
let(:tokenized_version) do
|
237
|
+
expected_tokens = Array(tokens).map do |s|
|
238
|
+
Token.new.tap { |t| t << s }
|
239
|
+
end
|
240
|
+
single_rule_grammar expected_tokens
|
241
|
+
end
|
242
|
+
|
243
|
+
before { subject.tokenize! }
|
244
|
+
|
245
|
+
context "with a single unquoted token" do
|
246
|
+
let(:content) { 'hello' }
|
247
|
+
let(:tokens) { 'hello' }
|
248
|
+
|
249
|
+
it "should tokenize correctly" do
|
250
|
+
should == tokenized_version
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context "with a single unquoted token (non-alphabetic)" do
|
255
|
+
let(:content) { '2' }
|
256
|
+
let(:tokens) { ['2'] }
|
257
|
+
|
258
|
+
it "should tokenize correctly" do
|
259
|
+
should == tokenized_version
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "with a single quoted token (including whitespace)" do
|
264
|
+
let(:content) { '"San Francisco"' }
|
265
|
+
let(:tokens) { ['San Francisco'] }
|
266
|
+
|
267
|
+
it "should tokenize correctly" do
|
268
|
+
should == tokenized_version
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "with a single quoted token (no whitespace)" do
|
273
|
+
let(:content) { '"hello"' }
|
274
|
+
let(:tokens) { ['hello'] }
|
275
|
+
|
276
|
+
it "should tokenize correctly" do
|
277
|
+
should == tokenized_version
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
context "with two tokens delimited by white space" do
|
282
|
+
let(:content) { 'bon voyage' }
|
283
|
+
let(:tokens) { ['bon', 'voyage'] }
|
284
|
+
|
285
|
+
it "should tokenize correctly" do
|
286
|
+
should == tokenized_version
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context "with four tokens delimited by white space" do
|
291
|
+
let(:content) { 'this is a test' }
|
292
|
+
let(:tokens) { ['this', 'is', 'a', 'test'] }
|
293
|
+
|
294
|
+
it "should tokenize correctly" do
|
295
|
+
should == tokenized_version
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context "with a single XML token" do
|
300
|
+
let(:content) { [Token.new.tap { |t| t << 'San Francisco' }] }
|
301
|
+
let(:tokens) { ['San Francisco'] }
|
302
|
+
|
303
|
+
it "should tokenize correctly" do
|
304
|
+
should == tokenized_version
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context "with a mixture of token types" do
|
309
|
+
let(:content) do
|
310
|
+
[
|
311
|
+
'Welcome to "San Francisco"',
|
312
|
+
Token.new.tap { |t| t << 'Have Fun!' }
|
313
|
+
]
|
314
|
+
end
|
315
|
+
|
316
|
+
let(:tokens) { ['Welcome', 'to', 'San Francisco', 'Have Fun!'] }
|
317
|
+
|
318
|
+
it "should tokenize correctly" do
|
319
|
+
should == tokenized_version
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe "#normalize_whitespace" do
|
325
|
+
it "should normalize whitespace in all of the tokens contained within it" do
|
326
|
+
grammar = GRXML.draw do
|
327
|
+
rule do
|
328
|
+
token { ' Welcome to ' }
|
329
|
+
token { ' San Francisco ' }
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
normalized_grammar = GRXML.draw do
|
334
|
+
rule do
|
335
|
+
token { 'Welcome to' }
|
336
|
+
token { 'San Francisco' }
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
grammar.should_not == normalized_grammar
|
341
|
+
grammar.normalize_whitespace
|
342
|
+
grammar.should == normalized_grammar
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe "matching against an input string" do
|
347
|
+
before do
|
348
|
+
subject.inline!
|
349
|
+
subject.tokenize!
|
350
|
+
subject.normalize_whitespace
|
351
|
+
end
|
352
|
+
|
353
|
+
context "with a grammar that takes a single specific digit" do
|
354
|
+
subject do
|
355
|
+
GRXML.draw :mode => :dtmf, :root => 'digit' do
|
356
|
+
rule :id => 'digit' do
|
357
|
+
'6'
|
180
358
|
end
|
181
|
-
|
182
|
-
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should match '6'" do
|
363
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
364
|
+
:confidence => 1,
|
365
|
+
:utterance => '6',
|
366
|
+
:interpretation => 'dtmf-6'
|
367
|
+
subject.match('6').should == expected_match
|
368
|
+
end
|
369
|
+
|
370
|
+
%w{1 2 3 4 5 7 8 9 10 66 26 61}.each do |input|
|
371
|
+
it "should not match '#{input}'" do
|
372
|
+
subject.match(input).should == GRXML::NoMatch.new
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context "with a grammar that takes two specific digits" do
|
378
|
+
subject do
|
379
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
380
|
+
rule :id => 'digits' do
|
381
|
+
'5 6'
|
183
382
|
end
|
184
383
|
end
|
185
384
|
end
|
385
|
+
|
386
|
+
it "should match '56'" do
|
387
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
388
|
+
:confidence => 1,
|
389
|
+
:utterance => '56',
|
390
|
+
:interpretation => 'dtmf-5 dtmf-6'
|
391
|
+
subject.match('56').should == expected_match
|
392
|
+
end
|
393
|
+
|
394
|
+
%w{* *7 #6 6* 1 2 3 4 5 6 7 8 9 10 65 57 46 26 61}.each do |input|
|
395
|
+
it "should not match '#{input}'" do
|
396
|
+
subject.match(input).should == GRXML::NoMatch.new
|
397
|
+
end
|
398
|
+
end
|
186
399
|
end
|
187
400
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
401
|
+
context "with a grammar that takes star and a digit" do
|
402
|
+
subject do
|
403
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
404
|
+
rule :id => 'digits' do
|
405
|
+
'* 6'
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
it "should match '*6'" do
|
411
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
412
|
+
:confidence => 1,
|
413
|
+
:utterance => '*6',
|
414
|
+
:interpretation => 'dtmf-star dtmf-6'
|
415
|
+
subject.match('*6').should == expected_match
|
416
|
+
end
|
417
|
+
|
418
|
+
%w{* *7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
419
|
+
it "should not match '#{input}'" do
|
420
|
+
subject.match(input).should == GRXML::NoMatch.new
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
context "with a grammar that takes hash and a digit" do
|
426
|
+
subject do
|
427
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
428
|
+
rule :id => 'digits' do
|
429
|
+
'# 6'
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
it "should match '#6'" do
|
435
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
436
|
+
:confidence => 1,
|
437
|
+
:utterance => '#6',
|
438
|
+
:interpretation => 'dtmf-pound dtmf-6'
|
439
|
+
subject.match('#6').should == expected_match
|
440
|
+
end
|
441
|
+
|
442
|
+
%w{* *6 #7 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
443
|
+
it "should not match '#{input}'" do
|
444
|
+
subject.match(input).should == GRXML::NoMatch.new
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context "with a grammar that takes two specific digits, via a ruleref, and whitespace normalization" do
|
450
|
+
subject do
|
451
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
452
|
+
rule :id => 'digits' do
|
453
|
+
ruleref :uri => '#star'
|
454
|
+
'" 6 "'
|
455
|
+
end
|
456
|
+
|
457
|
+
rule :id => 'star' do
|
458
|
+
'" * "'
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
it "should match '*6'" do
|
464
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
465
|
+
:confidence => 1,
|
466
|
+
:utterance => '*6',
|
467
|
+
:interpretation => 'dtmf-star dtmf-6'
|
468
|
+
subject.match('*6').should == expected_match
|
469
|
+
end
|
470
|
+
|
471
|
+
%w{* *7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
472
|
+
it "should not match '#{input}'" do
|
473
|
+
subject.match(input).should == GRXML::NoMatch.new
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
context "with a grammar that takes two specific digits with the second being an alternative" do
|
479
|
+
subject do
|
480
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
481
|
+
rule :id => 'digits' do
|
482
|
+
string '*'
|
483
|
+
one_of do
|
484
|
+
item { '6' }
|
485
|
+
item { '7' }
|
196
486
|
end
|
197
|
-
"#"
|
198
487
|
end
|
199
|
-
|
200
|
-
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
it "should match '*6'" do
|
492
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
493
|
+
:confidence => 1,
|
494
|
+
:utterance => '*6',
|
495
|
+
:interpretation => 'dtmf-star dtmf-6'
|
496
|
+
subject.match('*6').should == expected_match
|
497
|
+
end
|
498
|
+
|
499
|
+
it "should match '*7'" do
|
500
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
501
|
+
:confidence => 1,
|
502
|
+
:utterance => '*7',
|
503
|
+
:interpretation => 'dtmf-star dtmf-7'
|
504
|
+
subject.match('*7').should == expected_match
|
505
|
+
end
|
506
|
+
|
507
|
+
%w{* *8 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
508
|
+
it "should not match '#{input}'" do
|
509
|
+
subject.match(input).should == GRXML::NoMatch.new
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
context "with a grammar that takes a specific digit, followed by a specific digit repeated an exact number of times" do
|
515
|
+
subject do
|
516
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
517
|
+
rule :id => 'digits' do
|
518
|
+
string '1'
|
519
|
+
item :repeat => 2 do
|
520
|
+
'6'
|
521
|
+
end
|
201
522
|
end
|
202
523
|
end
|
203
524
|
end
|
525
|
+
|
526
|
+
it "should match '166'" do
|
527
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
528
|
+
:confidence => 1,
|
529
|
+
:utterance => '166',
|
530
|
+
:interpretation => 'dtmf-1 dtmf-6 dtmf-6'
|
531
|
+
subject.match('166').should == expected_match
|
532
|
+
end
|
533
|
+
|
534
|
+
%w{1 16 1666 16666 17}.each do |input|
|
535
|
+
it "should not match '#{input}'" do
|
536
|
+
subject.match(input).should == GRXML::NoMatch.new
|
537
|
+
end
|
538
|
+
end
|
204
539
|
end
|
205
540
|
|
206
|
-
grammar
|
207
|
-
|
541
|
+
context "with a grammar that takes a specific digit, followed by a specific digit repeated within a range" do
|
542
|
+
subject do
|
543
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
544
|
+
rule :id => 'digits' do
|
545
|
+
string '1'
|
546
|
+
item :repeat => 0..3 do
|
547
|
+
'6'
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
{
|
554
|
+
'1' => 'dtmf-1',
|
555
|
+
'16' => 'dtmf-1 dtmf-6',
|
556
|
+
'166' => 'dtmf-1 dtmf-6 dtmf-6',
|
557
|
+
'1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6'
|
558
|
+
}.each_pair do |input, interpretation|
|
559
|
+
it "should match '#{input}'" do
|
560
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
561
|
+
:confidence => 1,
|
562
|
+
:utterance => input,
|
563
|
+
:interpretation => interpretation
|
564
|
+
subject.match(input).should == expected_match
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
%w{6 16666 17}.each do |input|
|
569
|
+
it "should not match '#{input}'" do
|
570
|
+
subject.match(input).should == GRXML::NoMatch.new
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
context "with a grammar that takes a specific digit, followed by a specific digit repeated a minimum number of times" do
|
576
|
+
subject do
|
577
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
578
|
+
rule :id => 'digits' do
|
579
|
+
string '1'
|
580
|
+
item :repeat => '2-' do
|
581
|
+
'6'
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
{
|
588
|
+
'166' => 'dtmf-1 dtmf-6 dtmf-6',
|
589
|
+
'1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6',
|
590
|
+
'16666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6 dtmf-6'
|
591
|
+
}.each_pair do |input, interpretation|
|
592
|
+
it "should match '#{input}'" do
|
593
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
594
|
+
:confidence => 1,
|
595
|
+
:utterance => input,
|
596
|
+
:interpretation => interpretation
|
597
|
+
subject.match(input).should == expected_match
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
%w{1 16 17}.each do |input|
|
602
|
+
it "should not match '#{input}'" do
|
603
|
+
subject.match(input).should == GRXML::NoMatch.new
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
context "with a grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence" do
|
609
|
+
subject do
|
610
|
+
RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
|
611
|
+
rule :id => 'digit' do
|
612
|
+
one_of do
|
613
|
+
('0'..'9').map { |d| item { d } }
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
rule :id => 'pin', :scope => 'public' do
|
618
|
+
one_of do
|
619
|
+
item do
|
620
|
+
item :repeat => '4' do
|
621
|
+
ruleref :uri => '#digit'
|
622
|
+
end
|
623
|
+
"#"
|
624
|
+
end
|
625
|
+
item do
|
626
|
+
"\* 9"
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
208
632
|
|
209
|
-
|
210
|
-
|
633
|
+
{
|
634
|
+
'*9' => 'dtmf-star dtmf-9',
|
635
|
+
'1234#' => 'dtmf-1 dtmf-2 dtmf-3 dtmf-4 dtmf-pound',
|
636
|
+
'5678#' => 'dtmf-5 dtmf-6 dtmf-7 dtmf-8 dtmf-pound',
|
637
|
+
'1111#' => 'dtmf-1 dtmf-1 dtmf-1 dtmf-1 dtmf-pound'
|
638
|
+
}.each_pair do |input, interpretation|
|
639
|
+
it "should match '#{input}'" do
|
640
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
641
|
+
:confidence => 1,
|
642
|
+
:utterance => input,
|
643
|
+
:interpretation => interpretation
|
644
|
+
subject.match(input).should == expected_match
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
%w{111}.each do |input|
|
649
|
+
it "should not match '#{input}'" do
|
650
|
+
subject.match(input).should == GRXML::NoMatch.new
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
211
654
|
end
|
212
655
|
end # Grammar
|
213
656
|
end # GRXML
|