class RagTag

RAGTAG - A Tag Attribute Language for Ruby

RubyTals is a Ruby variation on Zope Page Templates and it’s TAL specification. It differs from TAL in that it is specifically geared for use by Ruby.

Usage

s = %q{
  <html>
  <body>
    <h1 r:content="x">[X]</h1>
    <div r:each="animal" r:do="a">
      <b r:content="a">[ANIMAL]</b>
    </div>
    <div r:if="animal.size > 1">
      There are <b r:content="animal.size">[ANIMAL SIZE]</b> animals.
    </div>
  </body>
  </html>
}

x = 'Our Little Zoo'
animal = ['Zebra', 'Monkey', 'Tiger' ]

puts Ragtag.compile(s, binding)

Note

Presently RagTag clauses can run arbitraty Ruby code. Although upping the safety level before executing a compiled template should be sufficiently protective in most cases, perhaps it would be better to limit valid expressions to single object references, ie. ‘this.that’, and then use a substitution of ‘.’ for ‘/’. Not only would this be highly protective, it would also be more compatible with the original TAL spec; albeit this isn’t exacty how TALs interprets the ‘/’ divider.

On the other hand perhaps it is too much restraint. For instance it would require the if-clause in the above exmaple to be something like:

<div r:if="animal/plenty">

and have a definition in the evaluating code:

def animal.plenty
  size > 1
end

It’s a classic Saftey vs. Usability trade-off. Something to consider for the future.

Constants

VERSION

TODO: This is only here b/c of bug in Ruby 1.8.x.

Attributes

scope[R]
xml[R]

Public Class Methods

compile(xml, scope=nil) click to toggle source
# File lib/ragtag.rb, line 60
def self.compile(xml, scope=nil)
  new(xml).compile(scope)
end
const_missing(name) click to toggle source

Access to project metadata as constants.

Calls superclass method
# File lib/ragtag/version.rb, line 12
def self.const_missing(name)
  key = name.to_s.downcase
  metadata[key] || super(name)
end
metadata() click to toggle source

Access to project metadata.

# File lib/ragtag/version.rb, line 4
def self.metadata
  @metadata ||= (
    require 'yaml'
    YAML.load(File.new(File.dirname(__FILE__) + '/../ragtag.yml'))
  )
end
new(xml) click to toggle source
# File lib/ragtag.rb, line 77
def initialize(xml)
  case xml
  when String
    @xml = Nokogiri::XML(xml)
  else
    @xml = xml
  end
end

Public Instance Methods

compile(scope=nil) click to toggle source
# File lib/ragtag.rb, line 87
def compile(scope=nil)
  scope = scope || $TOPLEVEL_BINDING
  parse(@xml.root, scope)
  xml
end
parse(node, scope) click to toggle source
# File lib/ragtag.rb, line 103
def parse(node, scope)
  case node
  when Nokogiri::XML::Text
    # nothing
  when Nokogiri::XML::NodeSet
    parse_nodeset(node, scope)
  when Nokogiri::XML::Element
    if value = node['define']
      eval(value, scope)
    end

    if node['if']
      parse_if(node, scope)
    #elsif node['condition']
    #  parse_condition(node, scope)
    end

    if node['content']
      parse_content(node, scope)
    elsif node['replace']
      parse_replace(node, scope)
    end

    if node['attr'] || node['attributes']
      parse_attributes(node, scope)
    end

    if node['each']
      parse_each(node, scope)
      return
    elsif node['repeat']
      parse_repeat(node, scope)
      return
    end

    node.children.each do |child|
      parse(child, scope)
    end

    if node['omit'] && node['omit'] != 'false'
      parse_omit(node, scope)
    end
  else
    raise node.inspect
  end
  return node
end
parse_attributes(node, scope) click to toggle source
# File lib/ragtag.rb, line 174
def parse_attributes(node, scope)
  if attrs = node['attr']
    assoc = attrs.split(',').map{ |e| e.strip.split(':') }
    assoc.each do |(k,v)|
      node[k] = eval(v, scope).to_s
    end
    node.remove_attribute('attr')
  end
  if attrs = node['attributes']
    assoc = attrs.split(',').map{ |e| e.strip.split(':') }
    assoc.each do |(k,v)|
      node[k] = eval(v, scope).to_s
    end
    node.remove_attribute('attributes')
  end
  node
end
parse_content(node, scope) click to toggle source
# File lib/ragtag.rb, line 160
def parse_content(node, scope)
  value = node['content']
  node.content = eval(value, scope)
  node.remove_attribute('content')
end
parse_each(node, scope) click to toggle source
# File lib/ragtag.rb, line 219
def parse_each(node, scope)
  value = node['each']
  args  = node['do'] || 'x'
  copy  = node.dup
  node.children.remove
  bindings = eval("#{value}.map{ |#{args}| binding }", scope)
  bindings.each do |each_scope|
    sect = parse(copy.dup.children, each_scope)
    sect.each do |x|
      node << x
    end
  end
  node.remove_attribute('each')
  node.remove_attribute('do')
  value
end
parse_if(node, scope) click to toggle source
# File lib/ragtag.rb, line 193
def parse_if(node, scope)
  value = node['if']
  if eval(value, scope)
    node.remove_attribute('if')
    parse(node.children, scope)
  else
    node.unlink
  end
  node
end
parse_nodeset(nodeset, scope) click to toggle source
# File lib/ragtag.rb, line 152
def parse_nodeset(nodeset, scope)
  nodeset.each do |node|
    parse(node, scope)
  end
  nodeset
end
parse_omit(node, scope) click to toggle source
# File lib/ragtag.rb, line 254
def parse_omit(node, scope)
  #parse(node.children, scope).each do |x|
  node.children.each do |x|
    x.unlink
    node.add_previous_sibling(x) 
  end
  node.unlink
end
parse_repeat(node, scope) click to toggle source
# File lib/ragtag.rb, line 237
def parse_repeat(node, scope)
  value = node['repeat']
  args  = node['do'] || 'x'
  copy  = node.dup
  copy.remove_attribute('repeat')
  copy.remove_attribute('do')
  bindings = eval("#{value}.map{ |#{args}| binding }", scope)
  bindings.each do |each_scope|
    sect = parse(copy.dup, each_scope)
    node.add_previous_sibling(sect)
    # parse_omit(sect, scope) if sect['omit'] && sect['omit'] != 'false'
  end
  node.unlink
  value
end
parse_replace(node, scope) click to toggle source
# File lib/ragtag.rb, line 167
def parse_replace(node, scope)
  value = node['replace']
  node.before(eval(value, scope).to_s)
  node.unlink
end