module HTree

Template Engine

The htree template engine converts HTML and some data to HTML or XML.

Template Method Summary

Note that the following method, HTree(), is not a template method.

Template Directives.

A template directive is described as a special HTML attribute which name begins with underscore.

The template directives are listed as follows.

Template Semantics

White Space Handling

The htree template engine strips whitespace text nodes in a template except under HTML pre element.

For example the white space text node between two spans in following template is stripped.

<span _text="'a'"/> <span _text="'b'"/> -> "ab"

Character entity references are not stripped.

<span _text="'a'"/>&#32;<span _text="'b'"/> -> "a&#32;b"

Text nodes generated by _text is not stripped.

<span _text="'a'"/><span _text="' '"> </span><span _text="'b'"/> -> "a b"

HTML and XML

The htree template engine outputs HTML or XML.

If a template has no XML declaration and the top element is HTML, the result is HTML. Otherwise the result is XML.

They differs as follows.

Design Decision on Design/Logic Separation

HTree template engine doesn't force you to separate design and logic. Any logic (Ruby code) can be embedded in design (HTML).

However the template engine cares the separation by logic refactorings. The logic is easy to move between a template and an application. For example, following tangled template

tmpl.html:
  <html>
    <head>
      <title _text="very-complex-ruby-code">dummy</title>
    </head>
    ...
  </html>

app.rb:
  HTree.expand_template('tmpl.html', obj)

can be refactored as follows.

tmpl.html:
  <html>
    <head>
      <title _text="title">dummy</title>
    </head>
    ...
  </html>

app.rb:
  def obj.title
    very-complex-ruby-code
  end
  HTree.expand_template('tmpl.html', obj)

In general, any expression in a template can be refactored to an application by extracting it as a method. In JSP, this is difficult especially for a code fragment of an iteration.

Also HTree encourages to separate business logic (Ruby code in an application) and presentation logic (Ruby code in a template). For example, presentation logic to color table rows stripe can be embedded in a template. It doesn't need to tangle an application.

Public Class Methods

compile_template(template_string) → module click to toggle source

HTree.compile_template(template_string) compiles template_string as a template.

::compile_template returns a module. The module has module functions for each templates defined in template_string. The returned module can be used for include.

M = HTree.compile_template("<p _template=birthday(subj,t)>
  <span _text=subj />'s birthday is <span _text="t.strftime('%B %dth %Y')"/>.
</p>
")
M.birthday('Ruby', Time.utc(1993, 2, 24)).display_xml
# <p>Ruby's birthday is February 24th 1993.</p>

The module function takes arguments specifies by a _template attribute and returns a tree represented as HTree::Node.

# File htree/template.rb, line 422
def HTree.compile_template(template_string)
  code = HTree::TemplateCompiler.new.compile_template(template_string)
  Thread.current[:htree_compile_template_code] = code
  mod = eval("      eval(Thread.current[:htree_compile_template_code])
",
    HTree::EmptyBindingObject.empty_binding, "(eval:#{__FILE__}:#{__LINE__})")
  Thread.current[:htree_compile_template_code] = nil
  mod
end
expand_template(template_pathname, obj=Object.new, out=$stdout, encoding=internal_encoding) → out click to toggle source
expand_template(out=$stdout, encoding=internal_encoding) { template_string } → out

HTree.expand_template expands a template.

The arguments should be specified as follows. All argument except pathname are optional.

The template is specified by a file or a string. If a block is not given, the first argument represent a template pathname. Otherwise, the block is yielded and its value is interpreted as a template string. So it can be called as follows in simplest case.

Ruby expressions in the template file specified by template_pathname are evaluated in the context of the optional second argument obj as follows. I.e. the pseudo variable self in the expressions is bound to obj.

HTree.expand_template(template_pathname, obj)

Ruby expressions in the template_string are evaluated in the context of the caller of ::expand_template. (binding information is specified by the block.) I.e. they can access local variables etc. We recommend to specify template_string as a literal string without interpolation because dynamically generated string may break lexical scope.

::expand_template has two more optional arguments: out, encoding.

out specifies output target. It should have << method: IO and String for example. If it is not specified, $stdout is used. If it has a method charset=, it is called to set the minimal charset of the result before << is called.

encoding specifies output character encoding. If it is not specified, internal encoding is used.

::expand_template returns out or $stdout if out is not specified.

# File htree/template.rb, line 342
def HTree.expand_template(*args, &block)
  if block
    template = block.call
    binding = block.binding
  else
    pathname = args.fetch(0) { raise ArgumentError, "pathname not given" }
    args.shift
    obj = args.fetch(0) { Object.new }
    args.shift
    if pathname.respond_to? :read
      template = pathname.read.untaint
      if template.respond_to? :charset
        if template.respond_to? :encode
          template = template.encode(HTree::Encoder.internal_charset, template.charset)
        else
          template = Iconv.conv(HTree::Encoder.internal_charset, template.charset, template)
        end
      end
    else
      template = File.read(pathname).untaint
    end
    Thread.current[:htree_expand_template_obj] = obj
    binding = eval("        Thread.current[:htree_expand_template_obj].class.class_eval <<-'EE'
          Thread.current[:htree_expand_template_obj].instance_eval { binding }
        EE
",
      HTree::EmptyBindingObject.empty_binding, "(eval:#{__FILE__}:#{__LINE__})")
    Thread.current[:htree_expand_template_obj] = nil
  end

  out = args.shift || $stdout
  encoding = args.shift || HTree::Encoder.internal_charset
  if !args.empty?
    raise ArgumentError, "wrong number of arguments" 
  end
  HTree::TemplateCompiler.new.expand_template(template, out, encoding, binding)
end
parse(input) click to toggle source

::parse parses input and return a document tree. represented by HTree::Doc.

input should be a String or an object which respond to read or open method. For example, IO, StringIO, Pathname, URI::HTTP and URI::FTP are acceptable. Note that the URIs need open-uri.

::parse guesses input is HTML or not and XML or not.

If it is guessed as HTML, the default namespace in the result is set to www.w3.org/1999/xhtml regardless of input has XML namespace declaration or not nor even it is pre-XML HTML.

If it is guessed as HTML and not XML, all element and attribute names are downcaseed.

If opened file or read content has charset method, ::parse decode it according to $KCODE before parsing. Otherwise ::parse assumes the character encoding of the content is compatible to $KCODE. Note that the charset method is provided by URI::HTTP with open-uri.

# File htree/parse.rb, line 34
def HTree.parse(input)
  HTree.with_frozen_string_hash {
    parse_as(input, false)
  }
end
parse_xml(input) click to toggle source

::parse_xml parses input as XML and return a document tree represented by HTree::Doc.

It behaves almost same as ::parse but it assumes input is XML even if no XML declaration. The assumption causes following differences.

  • doesn't downcase element name.

  • The content of <script> and <style> element is PCDATA, not CDATA.

# File htree/parse.rb, line 48
def HTree.parse_xml(input)
  HTree.with_frozen_string_hash {
    parse_as(input, true)
  }
end

Public Instance Methods

==(other) click to toggle source

compare tree structures.

# File htree/equality.rb, line 10
def ==(other)
  check_equality(self, other, :usual_equal_object)
end
Also aliased as: eql?
eql?(other)
Alias for: ==
hash() click to toggle source

hash value for the tree structure.

# File htree/equality.rb, line 16
def hash
  return @hash_code if defined? @hash_code
  @hash_code = usual_equal_object.hash
end