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
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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 :
|
55
|
-
rule id: '
|
54
|
+
grammy = RubySpeech::GRXML.draw mode: :dtmf, root: 'pin' do
|
55
|
+
rule id: 'digit' do
|
56
56
|
one_of do
|
57
|
-
0
|
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="
|
83
|
-
<rule id="
|
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
|
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(
|
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 <<
|
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(
|
63
|
+
super(self.registered_name, nil, self.namespace).tap do |n|
|
64
64
|
blk_proc[n]
|
65
65
|
end
|
66
66
|
else
|
67
|
-
super(
|
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
|
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 <<
|
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
|
-
|
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
|
data/lib/ruby_speech/grxml.rb
CHANGED
@@ -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,37 +18,9 @@ module RubySpeech
|
|
18
18
|
|
19
19
|
register :grammar
|
20
20
|
|
21
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|