Skip to content

Commit

Permalink
Merge b5ebfbf into 303f90f
Browse files Browse the repository at this point in the history
  • Loading branch information
sfgeorge committed Oct 17, 2014
2 parents 303f90f + b5ebfbf commit 1356dbf
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,4 +1,5 @@
# [develop](https://github.com/benlangfeld/ruby_speech)
* Bugfix: Rulerefs referenced n-levels deep under Rulerefs should be expanded.

# [2.3.2](https://github.com/benlangfeld/ruby_speech/compare/v2.3.1...v2.3.2) - [2014-04-21](https://rubygems.org/gems/ruby_speech/versions/2.3.2)
* Bugfix: String nodes should take non-strings and cast to a string (`#to_s`)
Expand Down
21 changes: 18 additions & 3 deletions lib/ruby_speech/grxml/grammar.rb
Expand Up @@ -32,6 +32,7 @@ class Grammar < Element
self.defaults = { :version => '1.0', :language => "en-US", namespace: GRXML_NAMESPACE }

VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, Rule, Tag].freeze
MAX_RULE_NESTING_DEFAULT = 25.freeze

##
#
Expand Down Expand Up @@ -116,9 +117,19 @@ def inline
# @return self
#
def inline!
xpath("//ns:ruleref", :ns => GRXML_NAMESPACE).each do |ref|
rule = rule_with_id ref[:uri].sub(/^#/, '')
ref.swap rule.dup.children
(self.class.max_rule_nesting + 1).times do |i|
rule = nil
j = 0
xpath("//ns:ruleref", :ns => GRXML_NAMESPACE).each do |ref|
if ([i,j].max + 1) > self.class.max_rule_nesting
raise ArgumentError, "Max ruleref recursion level of #{self.class.max_rule_nesting} has been exceeded."
end
rule = rule_with_id ref[:uri].sub(/^#/, '')
raise ArgumentError, "The Ruleref \"#{ref[:uri]}\" is referenced but not defined" unless rule
ref.swap rule.dup.children
j += 1
end
break unless rule
end

query = "./ns:rule[@id!='#{root}']"
Expand Down Expand Up @@ -185,6 +196,10 @@ def embed(other)

private

def self.max_rule_nesting
(ENV['RUBYSPEECH_MAX_RULE_NESTING'] || MAX_RULE_NESTING_DEFAULT).to_i
end

def has_matching_root_rule?
!root || root_rule
end
Expand Down
105 changes: 105 additions & 0 deletions spec/ruby_speech/grxml/grammar_spec.rb
Expand Up @@ -232,6 +232,111 @@ module GRXML
grammar.inline!.should == inline_grammar
grammar.should == inline_grammar
end

context 'nested' do
context 'in a self-referencial infinite loop' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule :id => :main, :scope => 'public' do
ruleref uri: '#paradox'
end
rule id: 'paradox' do
ruleref uri: '#paradox'
end
end
end

it 'should be rejected with an error' do
expect { subject.inline }.to raise_error ArgumentError, /Max ruleref recursion level of 25 has been exceeded./
end
end

context 'with an invalid-reference' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule :id => :main, :scope => 'public' do
ruleref uri: '#lost'
end
end.inline
end

it 'should raise a descriptive exception' do
expect { subject }.to raise_error ArgumentError, 'The Ruleref "#lost" is referenced but not defined'
end
end

context 'deeply' do
before :each do
subject.root = 'main'
subject << Rule.new(doc, :id => 'main', :scope => 'public') do
ruleref uri: '#level0'
end

levels.times do |i|
next_ref = i + 1
subject << if next_ref < levels
Rule.new(doc, :id => "level#{i}") do
ruleref uri: "#level#{next_ref}"
end
else
Rule.new(doc, :id => "level#{i}") do
string "How about an oatmeal cookie? You'll feel better."
end
end
end
end

after :each do
ENV['RUBYSPEECH_MAX_RULE_NESTING'] = nil
end

let :expected_doc do
RubySpeech::GRXML.draw root: 'main' do
rule :id => :main, :scope => 'public' do
string "How about an oatmeal cookie? You'll feel better."
end
end
end

context '25 levels deep' do
let(:levels) { 25 }

it 'should equal the expected doc' do
expect(subject.inline).to eq expected_doc
end
end

context '26 levels deep' do
let(:levels) { 26 }

it 'should be rejected with an error' do
expect { subject.inline }.to raise_error ArgumentError, /Max ruleref recursion level of 25 has been exceeded./
end
end

context 'with RUBYSPEECH_MAX_RULE_NESTING=100' do
before :each do
ENV['RUBYSPEECH_MAX_RULE_NESTING'] = '100'
end

context '100 levels deep' do
let(:levels) { 100 }

it 'should equal the expected doc' do
expect(subject.inline).to eq expected_doc
end
end

context '101 levels deep' do
let(:levels) { 101 }

it 'should be rejected with an error' do
expect { subject.inline }.to raise_error ArgumentError, /Max ruleref recursion level of 100 has been exceeded./
end
end
end
end
end
end

describe "#tokenize!" do
Expand Down

0 comments on commit 1356dbf

Please sign in to comment.