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
@@ -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
- VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, String, Audio, Break, Emphasis, Prosody, SayAs, Voice].freeze
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".
@@ -1,3 +1,3 @@
1
1
  module RubySpeech
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -18,13 +18,17 @@ module RubySpeech
18
18
  end
19
19
 
20
20
  describe "setting dtmf mode" do
21
- subject { Grammar.new :mode => 'dtmf' }
22
- its(:mode) { should == 'dtmf' }
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 { Grammar.new :mode => 'voice' }
27
- its(:mode) { should == 'voice' }
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 parse_xml(document).root }
45
+ subject { Element.import document }
42
46
 
43
47
  it { should be_instance_of Grammar }
44
48
 
45
- its(:language) { pending; should == 'jp' }
49
+ its(:language) { should == 'jp' }
46
50
  its(:base_uri) { should == 'blah' }
47
- its(:mode) { should == 'dtmf' }
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
- it "should allow inlining rule references" do
166
- grammar = GRXML.draw :root => 'pin', :mode => :dtmf do
167
- rule :id => 'digits' do
168
- one_of do
169
- 0.upto(9) { |d| item { d.to_s } }
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
- rule :id => 'pin', :scope => 'public' do
174
- one_of do
175
- item do
176
- item :repeat => '4' do
177
- ruleref :uri => '#digits'
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
- item do
182
- "* 9"
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
- expected_grammar = GRXML.draw :root => 'pin', :mode => :dtmf do
189
- rule :id => 'pin', :scope => 'public' do
190
- one_of do
191
- item do
192
- item :repeat => '4' do
193
- one_of do
194
- 0.upto(9) { |d| item { d.to_s } }
195
- end
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
- item do
200
- "* 9"
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.inline.should == expected_grammar
207
- end
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
- describe "#tokens" do
210
- context "with unquoted tokens"
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