ruby_speech 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.travis.yml +5 -1
- data/CHANGELOG.md +20 -5
- data/Gemfile +1 -1
- data/Guardfile +4 -0
- data/README.md +47 -101
- data/Rakefile +14 -2
- data/ext/ruby_speech/RubySpeechGRXMLMatcher.java +42 -0
- data/ext/ruby_speech/RubySpeechService.java +23 -0
- data/ext/ruby_speech/extconf.rb +7 -0
- data/ext/ruby_speech/ruby_speech.c +41 -0
- data/lib/ruby_speech/grxml.rb +1 -0
- data/lib/ruby_speech/grxml/element.rb +0 -17
- data/lib/ruby_speech/grxml/grammar.rb +0 -103
- data/lib/ruby_speech/grxml/item.rb +0 -21
- data/lib/ruby_speech/grxml/matcher.rb +129 -0
- data/lib/ruby_speech/grxml/one_of.rb +0 -4
- data/lib/ruby_speech/grxml/token.rb +0 -4
- data/lib/ruby_speech/nlsml.rb +1 -2
- data/lib/ruby_speech/nlsml/builder.rb +2 -15
- data/lib/ruby_speech/nlsml/document.rb +13 -9
- data/lib/ruby_speech/version.rb +1 -1
- data/ruby_speech.gemspec +10 -3
- data/spec/ruby_speech/grxml/grammar_spec.rb +0 -528
- data/spec/ruby_speech/grxml/item_spec.rb +0 -385
- data/spec/ruby_speech/grxml/matcher_spec.rb +644 -0
- data/spec/ruby_speech/grxml/one_of_spec.rb +0 -238
- data/spec/ruby_speech/nlsml_spec.rb +106 -148
- data/spec/ruby_speech_spec.rb +11 -21
- data/spec/spec_helper.rb +0 -1
- metadata +52 -78
data/lib/ruby_speech/grxml.rb
CHANGED
@@ -24,23 +24,6 @@ module RubySpeech
|
|
24
24
|
def regexp_content # :nodoc:
|
25
25
|
children.map(&:regexp_content).join
|
26
26
|
end
|
27
|
-
|
28
|
-
def potential_match?(other)
|
29
|
-
false
|
30
|
-
end
|
31
|
-
|
32
|
-
def max_input_length
|
33
|
-
0
|
34
|
-
end
|
35
|
-
|
36
|
-
def longest_potential_match(input)
|
37
|
-
input.dup.tap do |longest_input|
|
38
|
-
begin
|
39
|
-
return longest_input if potential_match? longest_input
|
40
|
-
longest_input.chop!
|
41
|
-
end until longest_input.length.zero?
|
42
|
-
end
|
43
|
-
end
|
44
27
|
end # Element
|
45
28
|
end # GRXML
|
46
29
|
end # RubySpeech
|
@@ -149,99 +149,6 @@ module RubySpeech
|
|
149
149
|
end
|
150
150
|
end
|
151
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::PotentialMatch:0x00000101371660>
|
199
|
-
#
|
200
|
-
# >> subject.match '11111'
|
201
|
-
# => #<RubySpeech::GRXML::NoMatch:0x00000101371936>
|
202
|
-
#
|
203
|
-
# ```
|
204
|
-
#
|
205
|
-
def match(other)
|
206
|
-
other = other.dup
|
207
|
-
regex = to_regexp
|
208
|
-
return check_for_potential_match(other) if regex == //
|
209
|
-
match = regex.match other
|
210
|
-
return check_for_potential_match(other) unless match
|
211
|
-
|
212
|
-
Match.new :mode => mode,
|
213
|
-
:confidence => dtmf? ? 1 : 0,
|
214
|
-
:utterance => other,
|
215
|
-
:interpretation => interpret_utterance(other)
|
216
|
-
end
|
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
|
-
|
232
|
-
##
|
233
|
-
# Converts the grammar into a regular expression for matching
|
234
|
-
#
|
235
|
-
# @return [Regexp] a regular expression which is equivalent to the grammar
|
236
|
-
#
|
237
|
-
def to_regexp
|
238
|
-
/^#{regexp_content.join}$/
|
239
|
-
end
|
240
|
-
|
241
|
-
def regexp_content
|
242
|
-
root_rule.children.map &:regexp_content
|
243
|
-
end
|
244
|
-
|
245
152
|
def dtmf?
|
246
153
|
mode == :dtmf
|
247
154
|
end
|
@@ -270,16 +177,6 @@ module RubySpeech
|
|
270
177
|
!root || root_rule
|
271
178
|
end
|
272
179
|
|
273
|
-
def interpret_utterance(utterance)
|
274
|
-
conversion = Hash.new { |hash, key| hash[key] = key }
|
275
|
-
conversion['*'] = 'star'
|
276
|
-
conversion['#'] = 'pound'
|
277
|
-
|
278
|
-
utterance.chars.inject [] do |array, digit|
|
279
|
-
array << "dtmf-#{conversion[digit]}"
|
280
|
-
end.join ' '
|
281
|
-
end
|
282
|
-
|
283
180
|
def split_tokens(element)
|
284
181
|
element.to_s.split(/(\".*\")/).reject(&:empty?).map do |string|
|
285
182
|
match = string.match /^\"(.*)\"$/
|
@@ -139,27 +139,6 @@ module RubySpeech
|
|
139
139
|
super
|
140
140
|
end
|
141
141
|
end
|
142
|
-
|
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
|
159
|
-
else
|
160
|
-
children.size
|
161
|
-
end
|
162
|
-
end
|
163
142
|
end # Item
|
164
143
|
end # GRXML
|
165
144
|
end # RubySpeech
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'ruby_speech/ruby_speech'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM =~ /java/
|
4
|
+
require 'jruby'
|
5
|
+
com.benlangfeld.ruby_speech.RubySpeechService.new.basicLoad(JRuby.runtime)
|
6
|
+
end
|
7
|
+
|
8
|
+
module RubySpeech
|
9
|
+
module GRXML
|
10
|
+
class Matcher
|
11
|
+
|
12
|
+
BLANK_REGEX = //.freeze
|
13
|
+
|
14
|
+
attr_reader :grammar, :regex
|
15
|
+
|
16
|
+
def initialize(grammar)
|
17
|
+
@grammar = grammar
|
18
|
+
prepare_grammar
|
19
|
+
@regex = /^#{regexp_content.join}$/
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Checks the grammar for a match against an input string
|
24
|
+
#
|
25
|
+
# @param [String] other the input string to check for a match with the grammar
|
26
|
+
#
|
27
|
+
# @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
|
28
|
+
#
|
29
|
+
# @example A grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence
|
30
|
+
# ```ruby
|
31
|
+
# grammar = RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
|
32
|
+
# rule :id => 'digit' do
|
33
|
+
# one_of do
|
34
|
+
# ('0'..'9').map { |d| item { d } }
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# rule :id => 'pin', :scope => 'public' do
|
39
|
+
# one_of do
|
40
|
+
# item do
|
41
|
+
# item :repeat => '4' do
|
42
|
+
# ruleref :uri => '#digit'
|
43
|
+
# end
|
44
|
+
# "#"
|
45
|
+
# end
|
46
|
+
# item do
|
47
|
+
# "\* 9"
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# matcher = RubySpeech::GRXML::Matcher.new grammar
|
54
|
+
#
|
55
|
+
# >> matcher.match '*9'
|
56
|
+
# => #<RubySpeech::GRXML::Match:0x00000100ae5d98
|
57
|
+
# @mode = :dtmf,
|
58
|
+
# @confidence = 1,
|
59
|
+
# @utterance = "*9",
|
60
|
+
# @interpretation = "*9"
|
61
|
+
# >
|
62
|
+
# >> matcher.match '1234#'
|
63
|
+
# => #<RubySpeech::GRXML::Match:0x00000100b7e020
|
64
|
+
# @mode = :dtmf,
|
65
|
+
# @confidence = 1,
|
66
|
+
# @utterance = "1234#",
|
67
|
+
# @interpretation = "1234#"
|
68
|
+
# >
|
69
|
+
# >> matcher.match '5678#'
|
70
|
+
# => #<RubySpeech::GRXML::Match:0x00000101218688
|
71
|
+
# @mode = :dtmf,
|
72
|
+
# @confidence = 1,
|
73
|
+
# @utterance = "5678#",
|
74
|
+
# @interpretation = "5678#"
|
75
|
+
# >
|
76
|
+
# >> matcher.match '1111#'
|
77
|
+
# => #<RubySpeech::GRXML::Match:0x000001012f69d8
|
78
|
+
# @mode = :dtmf,
|
79
|
+
# @confidence = 1,
|
80
|
+
# @utterance = "1111#",
|
81
|
+
# @interpretation = "1111#"
|
82
|
+
# >
|
83
|
+
# >> matcher.match '111'
|
84
|
+
# => #<RubySpeech::GRXML::NoMatch:0x00000101371660>
|
85
|
+
# ```
|
86
|
+
#
|
87
|
+
def match(buffer)
|
88
|
+
buffer = buffer.dup
|
89
|
+
|
90
|
+
return check_potential_match(buffer) if regex == BLANK_REGEX
|
91
|
+
|
92
|
+
check_full_match(buffer) || check_potential_match(buffer) || NoMatch.new
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def prepare_grammar
|
98
|
+
grammar.inline!
|
99
|
+
grammar.tokenize!
|
100
|
+
grammar.normalize_whitespace
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_full_match(buffer)
|
104
|
+
match = regex.match buffer
|
105
|
+
|
106
|
+
return unless match
|
107
|
+
|
108
|
+
Match.new :mode => grammar.mode,
|
109
|
+
:confidence => grammar.dtmf? ? 1 : 0,
|
110
|
+
:utterance => buffer,
|
111
|
+
:interpretation => interpret_utterance(buffer)
|
112
|
+
end
|
113
|
+
|
114
|
+
def regexp_content
|
115
|
+
grammar.root_rule.children.map &:regexp_content
|
116
|
+
end
|
117
|
+
|
118
|
+
def interpret_utterance(utterance)
|
119
|
+
conversion = Hash.new { |hash, key| hash[key] = key }
|
120
|
+
conversion['*'] = 'star'
|
121
|
+
conversion['#'] = 'pound'
|
122
|
+
|
123
|
+
utterance.chars.inject [] do |array, digit|
|
124
|
+
array << "dtmf-#{conversion[digit]}"
|
125
|
+
end.join ' '
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/ruby_speech/nlsml.rb
CHANGED
@@ -2,8 +2,7 @@ module RubySpeech
|
|
2
2
|
module NLSML
|
3
3
|
extend ActiveSupport::Autoload
|
4
4
|
|
5
|
-
NLSML_NAMESPACE
|
6
|
-
XFORMS_NAMESPACE = 'http://www.w3.org/2000/xforms'
|
5
|
+
NLSML_NAMESPACE = 'http://www.ietf.org/xml/ns/mrcpv2'
|
7
6
|
|
8
7
|
eager_autoload do
|
9
8
|
autoload :Builder
|
@@ -4,7 +4,7 @@ module RubySpeech
|
|
4
4
|
attr_reader :document
|
5
5
|
|
6
6
|
def initialize(options = {}, &block)
|
7
|
-
options = {'xmlns' => NLSML_NAMESPACE
|
7
|
+
options = {'xmlns' => NLSML_NAMESPACE}.merge(options)
|
8
8
|
@document = Nokogiri::XML::Builder.new do |builder|
|
9
9
|
builder.result options do |r|
|
10
10
|
apply_block r, &block
|
@@ -14,19 +14,11 @@ module RubySpeech
|
|
14
14
|
|
15
15
|
def interpretation(*args, &block)
|
16
16
|
if args.last.respond_to?(:has_key?) && args.last.has_key?(:confidence)
|
17
|
-
args.last[:confidence] =
|
17
|
+
args.last[:confidence] = args.last[:confidence].to_f
|
18
18
|
end
|
19
19
|
@result.send :interpretation, *args, &block
|
20
20
|
end
|
21
21
|
|
22
|
-
def model(*args, &block)
|
23
|
-
xf_namespaced_element :model, *args, &block
|
24
|
-
end
|
25
|
-
|
26
|
-
def instance(*args, &block)
|
27
|
-
xf_namespaced_element :instance, *args, &block
|
28
|
-
end
|
29
|
-
|
30
22
|
def method_missing(method_name, *args, &block)
|
31
23
|
@result.send method_name, *args, &block
|
32
24
|
end
|
@@ -37,11 +29,6 @@ module RubySpeech
|
|
37
29
|
@result = result
|
38
30
|
instance_eval &block
|
39
31
|
end
|
40
|
-
|
41
|
-
def xf_namespaced_element(element_name, *args, &block)
|
42
|
-
namespace = @result.send :[], 'xf'
|
43
|
-
namespace.send element_name, &block
|
44
|
-
end
|
45
32
|
end
|
46
33
|
end
|
47
34
|
end
|
@@ -4,8 +4,11 @@ module RubySpeech
|
|
4
4
|
module NLSML
|
5
5
|
class Document < SimpleDelegator
|
6
6
|
def initialize(xml)
|
7
|
+
unless xml.root.namespace
|
8
|
+
xml.root.default_namespace = NLSML_NAMESPACE
|
9
|
+
xml = Nokogiri::XML.parse xml.to_xml, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS
|
10
|
+
end
|
7
11
|
super
|
8
|
-
@xml = xml
|
9
12
|
end
|
10
13
|
|
11
14
|
def grammar
|
@@ -41,19 +44,19 @@ module RubySpeech
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def nomatch_elements
|
44
|
-
result.xpath 'ns:interpretation/ns:input/ns:nomatch
|
47
|
+
result.xpath 'ns:interpretation/ns:input/ns:nomatch', 'ns' => NLSML_NAMESPACE
|
45
48
|
end
|
46
49
|
|
47
50
|
def noinput_elements
|
48
|
-
result.xpath 'ns:interpretation/ns:input/ns:noinput
|
51
|
+
result.xpath 'ns:interpretation/ns:input/ns:noinput', 'ns' => NLSML_NAMESPACE
|
49
52
|
end
|
50
53
|
|
51
54
|
def input_elements
|
52
|
-
result.xpath 'ns:interpretation/ns:input
|
55
|
+
result.xpath 'ns:interpretation/ns:input', 'ns' => NLSML_NAMESPACE
|
53
56
|
end
|
54
57
|
|
55
58
|
def input_hash_for_interpretation(interpretation)
|
56
|
-
input_element = interpretation.at_xpath '
|
59
|
+
input_element = interpretation.at_xpath 'ns:input', 'ns' => NLSML_NAMESPACE
|
57
60
|
{ content: input_element.content }.tap do |h|
|
58
61
|
h[:mode] = input_element['mode'].to_sym if input_element['mode']
|
59
62
|
end
|
@@ -73,10 +76,11 @@ module RubySpeech
|
|
73
76
|
end
|
74
77
|
|
75
78
|
def instance_elements(interpretation)
|
76
|
-
interpretation.xpath '
|
79
|
+
interpretation.xpath 'ns:instance', 'ns' => NLSML_NAMESPACE
|
77
80
|
end
|
78
81
|
|
79
82
|
def element_children_key_value(element)
|
83
|
+
return element.children.first.content if element.children.first.is_a?(Nokogiri::XML::Text)
|
80
84
|
element.children.inject({}) do |acc, child|
|
81
85
|
acc[child.node_name.to_sym] = case child.children.count
|
82
86
|
when 0
|
@@ -96,7 +100,7 @@ module RubySpeech
|
|
96
100
|
|
97
101
|
def interpretation_hash_for_interpretation(interpretation)
|
98
102
|
{
|
99
|
-
confidence: interpretation['confidence'].to_f
|
103
|
+
confidence: interpretation['confidence'].to_f,
|
100
104
|
input: input_hash_for_interpretation(interpretation),
|
101
105
|
instance: instance_hash_for_interpretation(interpretation),
|
102
106
|
instances: instances_collection_for_interpretation(interpretation)
|
@@ -108,8 +112,8 @@ module RubySpeech
|
|
108
112
|
end
|
109
113
|
|
110
114
|
def interpretation_nodes
|
111
|
-
nodes = result.xpath '
|
112
|
-
nodes.sort_by { |int| -int[:confidence].
|
115
|
+
nodes = result.xpath 'ns:interpretation', 'ns' => NLSML_NAMESPACE
|
116
|
+
nodes.sort_by { |int| -int[:confidence].to_f }
|
113
117
|
end
|
114
118
|
end
|
115
119
|
end
|