Skip to content

Commit

Permalink
initial grxml skeleton for ruby_speech
Browse files Browse the repository at this point in the history
  • Loading branch information
taylor authored and benlangfeld committed Sep 12, 2011
1 parent 0a24104 commit 6f18bae
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@ pkg/*
spec/reports
.yardoc
doc
.*.swp
1 change: 1 addition & 0 deletions lib/ruby_speech.rb
Expand Up @@ -11,5 +11,6 @@ module RubySpeech
autoload :Version

autoload :SSML
autoload :GRXML
autoload :XML
end
20 changes: 20 additions & 0 deletions lib/ruby_speech/grxml.rb
@@ -0,0 +1,20 @@
module RubySpeech
module GRXML
extend ActiveSupport::Autoload

autoload :Element
autoload :Grammar
#autoload :Dtfm

InvalidChildError = Class.new StandardError

GRXML_NAMESPACE = 'http://www.w3.org/2001/06/grammar'

def self.draw(&block)
Grammar.new.tap do |grammar|
block_return = grammar.instance_eval(&block) if block_given?
grammar << block_return if block_return.is_a?(String)
end
end
end # GRXML
end # RubySpeech
92 changes: 92 additions & 0 deletions lib/ruby_speech/grxml/element.rb
@@ -0,0 +1,92 @@
require 'active_support/core_ext/class/inheritable_attributes'

module RubySpeech
module GRXML
class Element < Niceogiri::XML::Node
@@registrations = {}

class_inheritable_accessor :registered_ns, :registered_name

# Register a new stanza class to a name and/or namespace
#
# This registers a namespace that is used when looking
# up the class name of the object to instantiate when a new
# stanza is received
#
# @param [#to_s] name the name of the node
#
def self.register(name)
self.registered_name = name.to_s
self.registered_ns = GRXML_NAMESPACE
@@registrations[[self.registered_name, self.registered_ns]] = self
end

# Find the class to use given the name and namespace of a stanza
#
# @param [#to_s] name the name to lookup
#
# @return [Class, nil] the class appropriate for the name
def self.class_from_registration(name)
@@registrations[[name.to_s, GRXML_NAMESPACE]]
end

# Import an XML::Node to the appropriate class
#
# Looks up the class the node should be then creates it based on the
# elements of the XML::Node
# @param [XML::Node] node the node to import
# @return the appropriate object based on the node name and namespace
def self.import(node)
return node.content if node.is_a?(Nokogiri::XML::Text)
klass = class_from_registration(node.element_name)
if klass && klass != self
klass.import node
else
new.inherit node
end
end

# Inherit the attributes and children of an XML::Node
#
# @param [XML::Node] node the node to inherit
# @return [self]
def inherit(node)
inherit_attrs node.attributes
node.children.each do |c|
self << c.dup
end
self
end

def self.new(element_name, atts = {}, &block)
super(element_name) do |new_node|
atts.each_pair { |k, v| new_node.send :"#{k}=", v }
block_return = new_node.instance_eval &block if block_given?
new_node << new_node.encode_special_chars(block_return) if block_return.is_a?(String)
end
end

def children
super.map { |c| Element.import c }
end

def method_missing(method_name, *args, &block)
const_name = method_name.to_s.sub('ssml', '').titleize.gsub(' ', '')
const = GRXML.const_get const_name
if const && self.class::VALID_CHILD_TYPES.include?(const)
if const == String
self << encode_special_chars(args.first)
else
self << const.new(*args, &block)
end
else
super
end
end

def eql?(o, *args)
super o, :content, :children, *args
end
end # Element
end # GRXML
end # RubySpeech
71 changes: 71 additions & 0 deletions lib/ruby_speech/grxml/grammar.rb
@@ -0,0 +1,71 @@
module RubySpeech
module GRXML
##
# The Speech Recognition Grammar Language is an XML application. The root element is grammar.
#
# http://www.w3.org/TR/speech-grammar/#S4.3
#
class Grammar < Element
include XML::Language

register :grammar

#VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, DTMF].freeze
VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text].freeze

##
# Create a new GRXML grammar root element
#
# @param [Hash] atts Key-value pairs of options mapping to setter methods
#
# @return [Grammar] an element for use in an GRXML document
#
def self.new(atts = {}, &block)
new_node = super('grammar', atts)
new_node[:version] = '1.0'
new_node.namespace = GRXML_NAMESPACE
new_node.language ||= "en-US"
new_node.instance_eval &block if block_given?
new_node
end

##
# @return [String] the base URI to which relative URLs are resolved
#
def base_uri
read_attr :base
end

##
# @param [String] uri the base URI to which relative URLs are resolved
#
def base_uri=(uri)
write_attr 'xml:base', uri
end

def <<(arg)
#raise InvalidChildError, "A Grammar can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
raise InvalidChildError, "A Grammar can only accept DTMF as children" unless VALID_CHILD_TYPES.include? arg.class
super
end

def to_doc
Nokogiri::XML::Document.new.tap do |doc|
doc << self
end
end

def +(other)
self.class.new(:base_uri => base_uri).tap do |new_grammar|
(self.children + other.children).each do |child|
new_grammar << child
end
end
end

def eql?(o)
super o, :language, :base_uri
end
end # Grammar
end # GRXML
end # RubySpeech
37 changes: 37 additions & 0 deletions spec/ruby_speech/grxml_spec.rb
@@ -0,0 +1,37 @@
require 'spec_helper'

module RubySpeech
describe GRXML do
describe "#draw" do
it "should create an GRXML document" do
expected_doc = GRXML::Grammar.new
GRXML.draw.should == expected_doc
end
end
end
end

__END__
doc = RubySpeech::GRXML.draw do
rule :id => :digit do
one_of do
10.times do |i|
item i
end
end
end
end
<rule id="digit">
<one-of>
<item> 0 </item>
<item> 1 </item>
<item> 2 </item>
<item> 3 </item>
<item> 4 </item>
<item> 5 </item>
<item> 6 </item>
<item> 7 </item>
<item> 8 </item>
<item> 9 </item>
</one-of>
</rule>

0 comments on commit 6f18bae

Please sign in to comment.