module HTree
Template Engine¶ ↑
The htree template engine converts HTML and some data to HTML or XML.
Template Method Summary¶ ↑
-
::expand_template(template_pathname) -> $stdout
-
::expand_template(template_pathname, obj) -> $stdout
-
::expand_template(template_pathname, obj, out) -> out
-
::expand_template(template_pathname, obj, out, encoding) -> out
-
::expand_template{template_string} -> $stdout
-
::expand_template(out) {template_string} -> out
-
::expand_template(out, encoding) {template_string} -> out
-
::compile_template(template_string) -> Module
-
HTree{template_string} -> HTree::Doc
Note that the following method, HTree(), is not a template method.
-
HTree(html_string) -> HTree::Doc
Template Directives.¶ ↑
A template directive is described as a special HTML attribute which name begins with underscore.
The template directives are listed as follows.
-
<elem _attr_name=“expr”>content</elem>
-
<elem _text=“expr”>dummy-content</elem>
-
<elem _text>expr</elem>
-
<elem _tree=“expr”>dummy-content</elem>
-
<elem _tree>expr</elem>
-
<elem _if=“expr” _else=“mod.name(args)”>then-content</elem>
-
<elem _iter=“expr.meth(args)//vars”>content</elem>
-
<elem _iter_content=“expr.meth(args)//vars”>content</elem>
-
<elem _call=“mod.name(args)”>dummy-content</elem>
-
<elem _template=“name(vars)”>body</elem>
Template Semantics¶ ↑
-
attribute substitution
-
<elem _attr_name=“expr”>content</elem>
_attr_name is used for a dynamic attribute.
<elem _attr_xxx="..."/> -> <elem xxx="..."/>
It is expanded to name=“content”. The content is generated by evaluating expr. Usually you don't need to care escaping: &, <, > and “ are automatically escaped. If you need to output character references, the value of expr should be an object which have a
rcdata
method such as an HTree::Text. If the value has arcdata
method, it is called and the result is used as the content with escaping <, > and ”._attr_name can be used multiple times in single element.
-
-
text substitution
-
<elem _text=“expr”>dummy-content</elem>
-
<elem _text>expr</elem>
_text substitutes the content of the element by the string evaluated from expr. expr is described in the attribute value or the content of the element.
If a result of expr have &, < and/or >, they are automatically escaped. If you need to output character references, the value of expr should be an object which have a
rcdata
method such as an HTree::Text. If the value has arcdata
method, it is called and the result is used as the content with escaping < and >.If the element is span or div, and there is no other attributes, no tags are produced.
<elem _text="...">dummy-content</elem> -> <elem>...</elem>
-
-
tree substitution
-
<elem _tree=“expr”>dummy-content</elem>
-
<elem _tree>expr</elem>
_tree substitutes the content of the element by the htree object evaluated from expr. expr is described in the attribute value or the content of the element.
If the element is span or div, and there is no other attributes, no tags are produced.
<elem _tree="...">dummy-content</elem> -> <elem>...</elem>
-
-
conditional
-
<elem _if=“expr”>then-content</elem>
-
<elem _if=“expr” _else=“name(args)”>then-content</elem>
_if is used for conditional.
If expr is evaluated to true, it expands as follows regardless of existence of _else.
<elem _if="<i>expr</i>">then-content</elem> -> <elem>then-content</elem>
If expr is evaluated to false, it expands using _else. If _else is not given, it expands to empty. If _else is given, it expands as follows.
<elem _if="<i>expr</i>" _else="<i>name(args)</i>">then-content</elem> -> <elem _call="<i>name(args)</i>">then-content</elem> -> see _call for further expansion.
It is expanded to <elem>then-content</elem> if expr is evaluated to a true value. Otherwise, it is replaced by other template specified by _else attribute. If _else attribute is not given, it just replaced by empty.
-
-
iteration
-
<elem _iter=“expr.meth(args)//vars”>content</elem>
-
<elem _iter_content=“expr.meth(args)//vars”>content</elem>
_iter and _iter_content is used for iteration. _iter iterates the element itself but _iter_content iterates the content.
<outer _iter="..."><inner/></outer> -> <outer><inner/></outer><outer><inner/></outer>... <outer _iter_content="..."><inner/></outer> -> <outer><inner/><inner/>...</outer>
expr.meth(args) specifies iterator method call. It is actually called with a block. The block have block parameters vars. vars must be variables separated by comma.
-
-
template call
-
<elem _call=“name(args)”>dummy-content</elem>
-
<elem _call=“mod.name(args)”>dummy-content</elem>
_call is used to expand a template function. The template function is defined by _template.
<d _template="m">...</d> <c _call="m">...</c> -> <d>...</d>
A local template can be called as follows:
HTree.expand_template{"<a _template=ruby_talk(num) _attr_href='"http://ruby-talk.org/#{num}"' >[ruby-talk:<span _text=num>nnn</span>]</a> Ruby 1.8.0 is released at <span _call=ruby_talk(77946) />. Ruby 1.8.1 is released at <span _call=ruby_talk(88814) />. "}
mod should be the result of ::compile_template.
M = HTree.compile_template("<a _template=ruby_talk(num) _attr_href='"http://ruby-talk.org/#{num}"' >[ruby-talk:<span _text=num>nnn</span>]</a> ") HTree.expand_template{"<html> Ruby 1.8.0 is released at <span _call=M.ruby_talk(77946) />. Ruby 1.8.1 is released at <span _call=M.ruby_talk(88814) />. </html> "}
The module can included. In such case, the template function can be called without mod. prefix.
include HTree.compile_template("<a _template=ruby_talk(num) _attr_href='"http://ruby-talk.org/#{num}"' >[ruby-talk:<span _text=num>nnn</span>]</a> ") HTree.expand_template{"<html> Ruby 1.8.0 is released at <span _call=ruby_talk(77946) />. Ruby 1.8.1 is released at <span _call=ruby_talk(88814) />. </html> "}
-
-
template definition
-
<elem _template=“name(vars)”>body</elem>
_template defines a template function which is usable by _call.
When a template is compiled to a module by ::compile_template, the module have a module function for each template function defined by outermost _template attribute.
-
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'"/> <span _text="'b'"/> -> "a 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.
-
XML declaration is (re-)generated for XML.
-
empty elements ends with a slash for XML.
-
script and style element is escaped for XML.
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
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
HTree.expand_template
expands a template.
The arguments should be specified as follows. All argument except pathname are optional.
-
::expand_template(pathname, obj, out, encoding) -> out
-
::expand_template(out, encoding) {template_string} -> out
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.
-
::expand_template(template_pathname)
-
::expand_template{template_string}
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 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 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
compare tree structures.
# File htree/equality.rb, line 10 def ==(other) check_equality(self, other, :usual_equal_object) end
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