ruby_speech 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/lib/ruby_speech.rb +1 -0
- data/lib/ruby_speech/generic_element.rb +1 -0
- data/lib/ruby_speech/grxml.rb +1 -0
- data/lib/ruby_speech/grxml/element.rb +17 -0
- data/lib/ruby_speech/grxml/grammar.rb +21 -3
- data/lib/ruby_speech/grxml/item.rb +36 -7
- data/lib/ruby_speech/grxml/one_of.rb +4 -0
- data/lib/ruby_speech/grxml/potential_match.rb +10 -0
- data/lib/ruby_speech/grxml/token.rb +4 -0
- data/lib/ruby_speech/version.rb +1 -1
- data/spec/ruby_speech/grxml/grammar_spec.rb +226 -8
- data/spec/ruby_speech/grxml/item_spec.rb +427 -13
- data/spec/ruby_speech/grxml/one_of_spec.rb +237 -0
- data/spec/ruby_speech/grxml/potential_match_spec.rb +17 -0
- data/spec/ruby_speech/grxml/token_spec.rb +5 -0
- metadata +116 -176
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# develop
|
2
2
|
|
3
|
+
# 0.5.1 - 2012-01-09
|
4
|
+
* Feature: Chaining child injection using #<< now works
|
5
|
+
* Feature: Reading the repeat value for a GRXML Item now returns an Integer or a Range, rather than the plain string
|
6
|
+
* Feature: Most simple GRXML grammars now return PotentialMatch when the provided input is valid but incomplete. This does not work for complex grammars including repeats and deep nesting. Fixes for these coming soon.
|
7
|
+
|
3
8
|
# 0.5.0 - 2012-01-03
|
4
9
|
* Feature: Add a whole bunch more SSML elements:
|
5
10
|
** p & s
|
data/lib/ruby_speech.rb
CHANGED
data/lib/ruby_speech/grxml.rb
CHANGED
@@ -22,6 +22,23 @@ module RubySpeech
|
|
22
22
|
def regexp_content # :nodoc:
|
23
23
|
children.map(&:regexp_content).join
|
24
24
|
end
|
25
|
+
|
26
|
+
def potential_match?(other)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def max_input_length
|
31
|
+
0
|
32
|
+
end
|
33
|
+
|
34
|
+
def longest_potential_match(input)
|
35
|
+
input.dup.tap do |longest_input|
|
36
|
+
begin
|
37
|
+
return longest_input if potential_match? longest_input
|
38
|
+
longest_input.chop!
|
39
|
+
end until longest_input.length.zero?
|
40
|
+
end
|
41
|
+
end
|
25
42
|
end # Element
|
26
43
|
end # GRXML
|
27
44
|
end # RubySpeech
|
@@ -195,15 +195,19 @@ module RubySpeech
|
|
195
195
|
# @interpretation = "1234#"
|
196
196
|
# >
|
197
197
|
# >> subject.match '111'
|
198
|
-
# => #<RubySpeech::GRXML::
|
198
|
+
# => #<RubySpeech::GRXML::PotentialMatch:0x00000101371660>
|
199
|
+
#
|
200
|
+
# >> subject.match '11111'
|
201
|
+
# => #<RubySpeech::GRXML::NoMatch:0x00000101371936>
|
199
202
|
#
|
200
203
|
# ```
|
201
204
|
#
|
202
205
|
def match(other)
|
206
|
+
other = other.dup
|
203
207
|
regex = to_regexp
|
204
|
-
return
|
208
|
+
return check_for_potential_match(other) if regex == //
|
205
209
|
match = regex.match other
|
206
|
-
return
|
210
|
+
return check_for_potential_match(other) unless match
|
207
211
|
|
208
212
|
Match.new :mode => mode,
|
209
213
|
:confidence => dtmf? ? 1 : 0,
|
@@ -211,6 +215,20 @@ module RubySpeech
|
|
211
215
|
:interpretation => interpret_utterance(other)
|
212
216
|
end
|
213
217
|
|
218
|
+
def check_for_potential_match(other)
|
219
|
+
potential_match?(other) ? PotentialMatch.new : NoMatch.new
|
220
|
+
end
|
221
|
+
|
222
|
+
def potential_match?(other)
|
223
|
+
root_rule.children.each do |token|
|
224
|
+
return true if other.length.zero?
|
225
|
+
longest_potential_match = token.longest_potential_match other
|
226
|
+
return false if longest_potential_match.length.zero?
|
227
|
+
other.gsub! /^#{Regexp.escape longest_potential_match}/, ''
|
228
|
+
end
|
229
|
+
other.length.zero?
|
230
|
+
end
|
231
|
+
|
214
232
|
##
|
215
233
|
# Converts the grammar into a regular expression for matching
|
216
234
|
#
|
@@ -26,6 +26,7 @@ module RubySpeech
|
|
26
26
|
# xml:lang declares declaration declares the language of the grammar section for the item element just as xml:lang in the <grammar> element declares for the entire document
|
27
27
|
#
|
28
28
|
class Item < Element
|
29
|
+
Inf = 1.0 / 0.0
|
29
30
|
|
30
31
|
register :item
|
31
32
|
|
@@ -61,7 +62,14 @@ module RubySpeech
|
|
61
62
|
# @return [String]
|
62
63
|
#
|
63
64
|
def repeat
|
64
|
-
read_attr :repeat
|
65
|
+
repeat = read_attr :repeat
|
66
|
+
return nil unless repeat
|
67
|
+
if repeat.include?('-')
|
68
|
+
min, max = repeat.split('-').map &:to_i
|
69
|
+
(min || 0)..(max || Inf)
|
70
|
+
else
|
71
|
+
repeat.to_i
|
72
|
+
end
|
65
73
|
end
|
66
74
|
|
67
75
|
##
|
@@ -72,7 +80,7 @@ module RubySpeech
|
|
72
80
|
# @param [String] r
|
73
81
|
#
|
74
82
|
def repeat=(r)
|
75
|
-
r = "#{r.min}-#{r.max}" if r.is_a?(Range)
|
83
|
+
r = "#{r.min}-#{r.max unless r.max == Inf}" if r.is_a?(Range)
|
76
84
|
r = r.to_s
|
77
85
|
error = ArgumentError.new "A Item's repeat must be 0 or a positive integer"
|
78
86
|
|
@@ -122,13 +130,34 @@ module RubySpeech
|
|
122
130
|
end
|
123
131
|
|
124
132
|
def regexp_content # :nodoc:
|
125
|
-
|
133
|
+
case repeat
|
134
|
+
when Range
|
135
|
+
"#{super}{#{repeat.min},#{repeat.max unless repeat.max == Inf}}"
|
136
|
+
when Integer
|
137
|
+
"#{super}{#{repeat}}"
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
126
142
|
|
127
|
-
|
128
|
-
|
129
|
-
|
143
|
+
def potential_match?(other)
|
144
|
+
tokens = children
|
145
|
+
return false if other.length > max_input_length
|
146
|
+
other.chars.each_with_index do |digit, index|
|
147
|
+
index -= tokens.size until index < tokens.size if repeat
|
148
|
+
return false unless tokens[index].potential_match?(digit)
|
149
|
+
end
|
150
|
+
true
|
151
|
+
end
|
152
|
+
|
153
|
+
def max_input_length # :nodoc:
|
154
|
+
case repeat
|
155
|
+
when Range
|
156
|
+
children.size * repeat.max
|
157
|
+
when Integer
|
158
|
+
children.size * repeat
|
130
159
|
else
|
131
|
-
|
160
|
+
children.size
|
132
161
|
end
|
133
162
|
end
|
134
163
|
end # Item
|
data/lib/ruby_speech/version.rb
CHANGED
@@ -360,11 +360,13 @@ module RubySpeech
|
|
360
360
|
end
|
361
361
|
|
362
362
|
it "should match '6'" do
|
363
|
+
input = '6'
|
363
364
|
expected_match = GRXML::Match.new :mode => :dtmf,
|
364
365
|
:confidence => 1,
|
365
366
|
:utterance => '6',
|
366
367
|
:interpretation => 'dtmf-6'
|
367
|
-
subject.match(
|
368
|
+
subject.match(input).should == expected_match
|
369
|
+
input.should == '6'
|
368
370
|
end
|
369
371
|
|
370
372
|
%w{1 2 3 4 5 7 8 9 10 66 26 61}.each do |input|
|
@@ -391,7 +393,13 @@ module RubySpeech
|
|
391
393
|
subject.match('56').should == expected_match
|
392
394
|
end
|
393
395
|
|
394
|
-
|
396
|
+
it "should potentially match '5'" do
|
397
|
+
input = '5'
|
398
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
399
|
+
input.should == '5'
|
400
|
+
end
|
401
|
+
|
402
|
+
%w{* *7 #6 6* 1 2 3 4 6 7 8 9 10 65 57 46 26 61}.each do |input|
|
395
403
|
it "should not match '#{input}'" do
|
396
404
|
subject.match(input).should == GRXML::NoMatch.new
|
397
405
|
end
|
@@ -415,7 +423,11 @@ module RubySpeech
|
|
415
423
|
subject.match('*6').should == expected_match
|
416
424
|
end
|
417
425
|
|
418
|
-
|
426
|
+
it "should potentially match '*'" do
|
427
|
+
subject.match('*').should == GRXML::PotentialMatch.new
|
428
|
+
end
|
429
|
+
|
430
|
+
%w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
419
431
|
it "should not match '#{input}'" do
|
420
432
|
subject.match(input).should == GRXML::NoMatch.new
|
421
433
|
end
|
@@ -439,6 +451,10 @@ module RubySpeech
|
|
439
451
|
subject.match('#6').should == expected_match
|
440
452
|
end
|
441
453
|
|
454
|
+
it "should potentially match '#'" do
|
455
|
+
subject.match('#').should == GRXML::PotentialMatch.new
|
456
|
+
end
|
457
|
+
|
442
458
|
%w{* *6 #7 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
443
459
|
it "should not match '#{input}'" do
|
444
460
|
subject.match(input).should == GRXML::NoMatch.new
|
@@ -468,7 +484,142 @@ module RubySpeech
|
|
468
484
|
subject.match('*6').should == expected_match
|
469
485
|
end
|
470
486
|
|
471
|
-
|
487
|
+
it "should potentially match '*'" do
|
488
|
+
subject.match('*').should == GRXML::PotentialMatch.new
|
489
|
+
end
|
490
|
+
|
491
|
+
%w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
492
|
+
it "should not match '#{input}'" do
|
493
|
+
subject.match(input).should == GRXML::NoMatch.new
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
context "with a grammar that takes a single digit alternative" do
|
499
|
+
subject do
|
500
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
501
|
+
rule :id => 'digits' do
|
502
|
+
one_of do
|
503
|
+
item { '6' }
|
504
|
+
item { '7' }
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
it "should match '6'" do
|
511
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
512
|
+
:confidence => 1,
|
513
|
+
:utterance => '6',
|
514
|
+
:interpretation => 'dtmf-6'
|
515
|
+
subject.match('6').should == expected_match
|
516
|
+
end
|
517
|
+
|
518
|
+
it "should match '7'" do
|
519
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
520
|
+
:confidence => 1,
|
521
|
+
:utterance => '7',
|
522
|
+
:interpretation => 'dtmf-7'
|
523
|
+
subject.match('7').should == expected_match
|
524
|
+
end
|
525
|
+
|
526
|
+
%w{* # 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
|
527
|
+
it "should not match '#{input}'" do
|
528
|
+
subject.match(input).should == GRXML::NoMatch.new
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
context "with a grammar that takes a double digit alternative" do
|
534
|
+
subject do
|
535
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
536
|
+
rule :id => 'digits' do
|
537
|
+
one_of do
|
538
|
+
item do
|
539
|
+
token { '6' }
|
540
|
+
token { '5' }
|
541
|
+
end
|
542
|
+
item do
|
543
|
+
token { '7' }
|
544
|
+
token { '2' }
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
it "should match '65'" do
|
552
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
553
|
+
:confidence => 1,
|
554
|
+
:utterance => '65',
|
555
|
+
:interpretation => 'dtmf-6 dtmf-5'
|
556
|
+
subject.match('65').should == expected_match
|
557
|
+
end
|
558
|
+
|
559
|
+
it "should match '72'" do
|
560
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
561
|
+
:confidence => 1,
|
562
|
+
:utterance => '72',
|
563
|
+
:interpretation => 'dtmf-7 dtmf-2'
|
564
|
+
subject.match('72').should == expected_match
|
565
|
+
end
|
566
|
+
|
567
|
+
%w{6 7}.each do |input|
|
568
|
+
it "should potentially match '#{input}'" do
|
569
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
%w{* # 1 2 3 4 5 8 9 10 66 26 61 75}.each do |input|
|
574
|
+
it "should not match '#{input}'" do
|
575
|
+
subject.match(input).should == GRXML::NoMatch.new
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
context "with a grammar that takes a triple digit alternative" do
|
581
|
+
subject do
|
582
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
583
|
+
rule :id => 'digits' do
|
584
|
+
one_of do
|
585
|
+
item do
|
586
|
+
token { '6' }
|
587
|
+
token { '5' }
|
588
|
+
token { '2' }
|
589
|
+
end
|
590
|
+
item do
|
591
|
+
token { '7' }
|
592
|
+
token { '2' }
|
593
|
+
token { '8' }
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
it "should match '652'" do
|
601
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
602
|
+
:confidence => 1,
|
603
|
+
:utterance => '652',
|
604
|
+
:interpretation => 'dtmf-6 dtmf-5 dtmf-2'
|
605
|
+
subject.match('652').should == expected_match
|
606
|
+
end
|
607
|
+
|
608
|
+
it "should match '728'" do
|
609
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
610
|
+
:confidence => 1,
|
611
|
+
:utterance => '728',
|
612
|
+
:interpretation => 'dtmf-7 dtmf-2 dtmf-8'
|
613
|
+
subject.match('728').should == expected_match
|
614
|
+
end
|
615
|
+
|
616
|
+
%w{6 65 7 72}.each do |input|
|
617
|
+
it "should potentially match '#{input}'" do
|
618
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
%w{* # 1 2 3 4 5 8 9 10 66 26 61 75 729 654}.each do |input|
|
472
623
|
it "should not match '#{input}'" do
|
473
624
|
subject.match(input).should == GRXML::NoMatch.new
|
474
625
|
end
|
@@ -504,7 +655,55 @@ module RubySpeech
|
|
504
655
|
subject.match('*7').should == expected_match
|
505
656
|
end
|
506
657
|
|
507
|
-
|
658
|
+
it "should potentially match '*'" do
|
659
|
+
subject.match('*').should == GRXML::PotentialMatch.new
|
660
|
+
end
|
661
|
+
|
662
|
+
%w{*8 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
|
663
|
+
it "should not match '#{input}'" do
|
664
|
+
subject.match(input).should == GRXML::NoMatch.new
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
context "with a grammar that takes two specific digits with the first being an alternative" do
|
670
|
+
subject do
|
671
|
+
GRXML.draw :mode => :dtmf, :root => 'digits' do
|
672
|
+
rule :id => 'digits' do
|
673
|
+
one_of do
|
674
|
+
item { '6' }
|
675
|
+
item { '7' }
|
676
|
+
end
|
677
|
+
string '*'
|
678
|
+
end
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
it "should match '6*'" do
|
683
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
684
|
+
:confidence => 1,
|
685
|
+
:utterance => '6*',
|
686
|
+
:interpretation => 'dtmf-6 dtmf-star'
|
687
|
+
subject.match('6*').should == expected_match
|
688
|
+
end
|
689
|
+
|
690
|
+
it "should match '7*'" do
|
691
|
+
expected_match = GRXML::Match.new :mode => :dtmf,
|
692
|
+
:confidence => 1,
|
693
|
+
:utterance => '7*',
|
694
|
+
:interpretation => 'dtmf-7 dtmf-star'
|
695
|
+
subject.match('7*').should == expected_match
|
696
|
+
end
|
697
|
+
|
698
|
+
it "should potentially match '6'" do
|
699
|
+
subject.match('6').should == GRXML::PotentialMatch.new
|
700
|
+
end
|
701
|
+
|
702
|
+
it "should potentially match '7'" do
|
703
|
+
subject.match('7').should == GRXML::PotentialMatch.new
|
704
|
+
end
|
705
|
+
|
706
|
+
%w{8* 6# *6 *7 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
|
508
707
|
it "should not match '#{input}'" do
|
509
708
|
subject.match(input).should == GRXML::NoMatch.new
|
510
709
|
end
|
@@ -531,7 +730,13 @@ module RubySpeech
|
|
531
730
|
subject.match('166').should == expected_match
|
532
731
|
end
|
533
732
|
|
534
|
-
%w{1 16
|
733
|
+
%w{1 16}.each do |input|
|
734
|
+
it "should potentially match '#{input}'" do
|
735
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
%w{1666 16666 17}.each do |input|
|
535
740
|
it "should not match '#{input}'" do
|
536
741
|
subject.match(input).should == GRXML::NoMatch.new
|
537
742
|
end
|
@@ -598,7 +803,13 @@ module RubySpeech
|
|
598
803
|
end
|
599
804
|
end
|
600
805
|
|
601
|
-
%w{1 16
|
806
|
+
%w{1 16}.each do |input|
|
807
|
+
it "should potentially match '#{input}'" do
|
808
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
%w{7 17}.each do |input|
|
602
813
|
it "should not match '#{input}'" do
|
603
814
|
subject.match(input).should == GRXML::NoMatch.new
|
604
815
|
end
|
@@ -645,7 +856,14 @@ module RubySpeech
|
|
645
856
|
end
|
646
857
|
end
|
647
858
|
|
648
|
-
%w{
|
859
|
+
%w{* 1 12 123 1234}.each do |input|
|
860
|
+
it "should potentially match '#{input}'" do
|
861
|
+
pending
|
862
|
+
subject.match(input).should == GRXML::PotentialMatch.new
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
%w{11111 #1111 *7}.each do |input|
|
649
867
|
it "should not match '#{input}'" do
|
650
868
|
subject.match(input).should == GRXML::NoMatch.new
|
651
869
|
end
|