#!/usr/local/bin/ruby # ruby-tmpl: Templating ala (mod_)ruby # Copyright 2001 # Sean Chittenden . All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgment: # # This product includes software developed by Sean Chittenden # and ruby-tmpl's contributors. # # 4. Neither the name of the software nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # Please see the LICENSE file for more information regarding the use # and distribution of this software. # # $Id: ruby-tmpl.rb,v 1.40 2002/02/21 19:12:03 thetitan Exp $ class Template attr_accessor :munge_ml_comments, :munge_whitespace, :max_num_iteration, :out_file, :out_file_mode, :session_param, :use_mod_ruby attr_reader :docroot, :file, :path, :is_compiled, :mod_ruby, :r, :session, :time_compile public def file= (file_name) if file_name != @file reset_output @file = file_name end end def munge_output return(@want_munged) end def munge_output= (val) raise RuntimeError, 'Content already munged' if (@is_munged) @want_munged = val end def path= (path) self.path_clear() self.path_append(path) end def path_append (path) path.to_a.each { |dir| @path.push(dir.split(/:/)) } @path.flatten! end def path_clear () @path = ['.'] end def print () compile unless (@is_compiled) munge unless (@is_munged) if @use_mod_ruby @r.headers_out.set('Content-Length', size_of.to_s) @r.send_http_header out(@output) else out(@output) end end def out (*args) if @use_mod_ruby @r.print(args.join('')) else if @out_file.length > 0 of = File.open(@out_file, @out_file_mode) of.print(args.join('')) of.close else Kernel.print(args.join('')) end end end def session= (val) raise RuntimeError, 'Session already set' if (@session && val != @session) @session = val end def set (key, val) ns, key = key.split(':', 2) if !key key = ns ns = '' end @blocks[ns] = {} unless @blocks.has_key?(ns) @blocks[ns][key] = val return([key, ns]) end def size_of () compile unless (@is_compiled) munge unless (@is_munged) return(@output.length) end def to_s () compile unless (@is_compiled) munge unless (@is_munged) return(@output.dup) end private def initialize @blocks = {} @file = '' @is_compiled = false @is_munged = false @max_num_iteration = 1000 @mod_ruby = false @multiline_tags = ['if', 'unless'] @munge_whitespace = true @munge_ml_comments = false @out_file = '' @out_file_mode = 'w' @output = '' @path = ['.'] @session = nil @session_param = 'session' @session_pattern_a = '(<[Aa].*?[Hh][Rr][Ee][Ff]\s*=\s*)(([\'\"])((?:[^#]|[Mm][Aa][Ii][Ll][Tt][Oo]).*?[^\?]*?){0,1}(\?.*?){0,1}\3)(.*?>)' @session_pattern_form = '(<[Ff][Oo][Rr][Mm].*?[Aa][Cc][Tt][Ii][Oo][Nn]\s*=\s*)(([\'"])([^\?]*?){0,1}(\?.*?){0,1}\3)(.*?>)' @session_pattern_img = '(<[Ii][Mm][Gg].*?[Ss][Rr][Cc]\s*=\s*)(([\'"])([^\?]*?){0,1}(\?.*?){0,1}\3)(.*?>)' @use_mod_ruby = false @tag_pattern = '\<\?tmpl\.([^\s\?]*)\s*(.*?)\s*\?\>' @time_compile = Time.at(0) @want_munged = true if ENV.has_key?('MOD_RUBY') @mod_ruby = true @use_mod_ruby = true @r = Apache.request @r.content_type = 'text/html' @docroot = r.server.document_root elsif ENV.has_key?('DOCUMENT_ROOT') @docroot = ENV['DOCUMENT_ROOT'] elsif ENV.has_key?('PWD') @docroot = ENV['PWD'] else @docroot = nil end end # def initialize() def compile () return(false) if (@is_compiled) t = Time.now tmpl = parse_string(tmpl_file({'name' => @file})) @output <<= tmpl @is_compiled = true @time_compile = Time.now() - t end def escape_html (val) val.gsub!(/&/, '&') val.gsub!(/\"/, '"') val.gsub!(//, '>') return(val) end def find_end_tag (tmpl) unparsed_tmpl = String.new(tmpl) nest_level = 0 end_char = 0 end_tag = 0 tag_re = Regexp.new(@tag_pattern) md = tag_re.match(unparsed_tmpl) unparsed_tmpl.slice!(0,md.end(0)) first_tag_len = md.end(0) while 1 md = tag_re.match(unparsed_tmpl) tag = md.to_a[0] cmd = md.to_a[1] if tag case cmd when 'else' && nest_level == 0 # do something when 'end' if nest_level == 0 unparsed_tmpl.slice!(-tag.length, tag.length) end_tag = tag end_char += md.begin(0) break else end_char += md.end(0) unparsed_tmpl.slice!(0, md.end(0)) nest_level -= 1 end else end_char += md.end(0) unparsed_tmpl.slice!(0,md.end(0)) nest_level += 1 if @multiline_tags.include?(cmd) end # case cmd else break end end # while nested_level if nest_level > 0 raise(RuntimeError, 'Invalid template: multiline tags do not match up') end return([tmpl.slice(first_tag_len, end_char), end_tag]) end # def find_end_tag() def munge () return(false) if (!@want_munged || @is_munged) compile unless (@is_compiled) if @munge_whitespace @output.gsub!(/\>\s+\<') @output.gsub!(/\n\s*/, ' ') end if @munge_ml_comments @output.gsub!(/\<\!\-\-.*?\-\-\>/, '') end @munged = true end def parse_string (unparsed_tmpl, parent_args = {}) tmpl = '' args = {} tag_re = Regexp.new(@tag_pattern) loop_count = 0 while md = tag_re.match(unparsed_tmpl) if loop_count > @max_num_iteration raise(RuntimeError, 'Too many iterations') else loop_count += 1 end tmpl <<= md.pre_match unparsed_tmpl = md.to_a[0] + md.post_match tag = md.to_a[0] cmd = md.to_a[1] args = {} args.update(parent_args) ['name'].each { |safe| args.delete(safe) } args.update(parse_tag_args(md.to_a[2])) val = '' case cmd when 'docroot_include' val = tmpl_docroot_include(args) when 'env' val = tmpl_env(args) when 'eval' val = tmpl_eval(args) when 'file' val = tmpl_file(args) when 'http_headers_in' val = tmpl_http_headers_in(args) when 'http_headers_out' val = tmpl_http_headers_out(args) when 'if' tag_info = find_end_tag(unparsed_tmpl) val = tmpl_if(tag_info[0], args) tag <<= tag_info.to_s when 'path' val = tmpl_path(args) when 'session' val = tmpl_session(args) when 'style' val = tmpl_style(args) when 'unless' tag_info = find_end_tag(unparsed_tmpl) val = tmpl_unless(tag_info[0], args) tag <<= tag_info.to_s when 'var' val = tmpl_var(args) else val = "Invalid command: \"#{cmd}\"" end if args.has_key?('escape') if args['escape'] == 'html' val = escape_html(val) end end unparsed_tmpl.gsub!(/#{Regexp.escape(tag)}/, val) end tmpl <<= unparsed_tmpl sessionize_html(tmpl) if @session return(tmpl) end # def parse_string() def parse_tag_args (arg_str) args = {} arg_re = Regexp.new('\s*([^\s\=\"\'\>]*?)\s*\=\s*([\"\'])([^\2]*?)\2\s*') while md = arg_re.match(arg_str) args[md.to_a[1]] = md.to_a[3] arg_str = md.post_match end return(args) end def reset_output () @is_compiled = false @is_munged = false @output = '' @time_compile = Time.at(0) end def sessionize_html (tmpl) sessionize_tags(tmpl, @session_pattern_a) sessionize_tags(tmpl, @session_pattern_form) sessionize_tags(tmpl, @session_pattern_img) end def sessionize_tags(tmpl, pattern) tmpl_orig = String.new(tmpl) session_regexp = Regexp.new(pattern) while (md = session_regexp.match(tmpl_orig)) do tag = md.to_a[1] + md.to_a[3] + md.to_a[4] if md.to_a[4].index('mailto') == 0 tag = md.to_a[0] elsif md.to_a[5] if md.to_a[5].index(/[\?\+]#{@session_param}=/) tag <<= md.to_a[5] + md.to_a[3] + md.to_a[6] else tag <<= md.to_a[5] + '+' + @session_param + '=' + @session + md.to_a[3] + md.to_a[6] end else tag <<= '?' + @session_param + '=' + @session + md.to_a[3] + md.to_a[6] end tmpl.gsub!(/#{Regexp.escape(md.to_a[0])}/, tag) tmpl_orig = md.post_match end # while loop return(tmpl) end # def sessionize_tags() def test_block (block_name, args) ns = '' ns = args['ns'] if args.has_key?(args['ns']) if @blocks[ns].has_key?(block_name) return(true) elsif @blocks[''].has_key?(block_name) return(true) else return(false) end end ################## # Begin tag libs # ################## # def tmpl_docroot_include (args) val = '' return('tmpl.docroot_include: Unable to determine the root of the request') unless @docroot filename = (@mod_ruby ? @r.filename : $0) if args.has_key?('order') && (args['order'] == 'prepend' || args['order'] == 'append') order = args['order'] else order = 'append' end spacer = (args.has_key?('spacer') ? args['spacer'] : ' ') uri = filename.sub(/\A#{Regexp.escape(@docroot)}/, '') if (uri == filename) return('tmpl.docroot_include: Unable to create a URI relative to the document root') end dirs = uri.split(/\//) dirs.compact! dirs.shift # Remove residual leading / dirs.pop # Remove trailing filename args_root_orig = args['root'] includes = [] (0 .. dirs.nitems).each do |n| args['root'] = File.join(@docroot, dirs[n]) if order == 'append' includes.unshift(tmpl_file(args)) else includes.push(tmpl_file(args)) end dirs.pop end val = includes.join(spacer) args['root'] = args_root_orig return(val.to_s) end # def tmpl_docroot_include () # def tmpl_env (args) val = '' if args.has_key?('name') if ENV.has_key?(args['name']) val = ENV[args['name']] elsif args.has_key?('default') val = args['default'] else val = "ENV[\'#{args['name']}\'] has no value" end else val = "ENV[\'#{args['name']}\'] has no value" end return(val.to_s) end # def tmpl_env () # def tmpl_eval (args) val = '' if args.has_key?('code') begin val = eval(args['code'].to_s) rescue ScriptError if args.has_key?('error_msg') val = args['error_msg'] else val = "eval of \"#{args['code']}\" failed" end end end return(val.to_s) end # tmpl_eval () # def tmpl_file (args) path = [] val = '' if args.has_key?('name') include_file = '' if args.has_key?('root') if args['root'] == 'docroot' path.unshift(@docroot) else path.unshift(args['root']) end else path = @path end dir = '' path.each do |d| begin file = File.join(d, args['name']).untaint if File.stat(file).file? and File.stat(file).readable? dir = d break end rescue Errno::ENOENT next end end include_file = File.join(dir, args['name']) begin file = File.open(include_file.untaint) include = file.readlines val = include.join('') file.close() rescue Errno::ENOENT if args.has_key?('error_msg') if args['error_msg'] == 'yes' val = "File \"#{args['name']}\" does not exist in the path \"#{path.join(':')}\"" else val = args['error_msg'] end else val = "File \"#{args['name']}\" does not exist in the path \"#{path.join(':')}\"" end end # begin end return(parse_string(val.to_s, args)) end # def tmpl_file () # def tmpl_http_headers_in (args) val = '' return(val) unless @mod_ruby if args.has_key?('name') if @r.headers_in.get(args['name']) val = @r.headers_in.get(args['name']) elsif args.has_key?('default') val = args['default'] else val = "Incoming HTTP Header \"#{args['name']}\" has no value" end else val = "Incoming HTTP Header \"#{args['name']}\" has no value" end return(val.to_s) end # def tmpl_http_headers_in () # def tmpl_http_headers_out (args) val = '' return(val) unless @mod_ruby if args.has_key?('name') if @r.headers_out.get(args['name']) val = @r.headers_out.get(args['name']) elsif args.has_key?('default') val = args['default'] else val = "Outgoing HTTP Header \"#{args['name']}\" has no value" end else val = "Outgoing HTTP Header \"#{args['name']}\" has no value" end return(val.to_s) end # def tmpl_http_headers_out () # ... def tmpl_if (unparsed_tmpl, args) val = '' type = args['type'] condition_true = false type = (args.has_key?('type') ? args['type'] : 'block') if args.has_key?('name') case type when 'block' condition_true = test_block(args['name'], args) else val <<= "Invalid if type type: \"#{args['type']}\"" end end return(condition_true ? parse_string(unparsed_tmpl, args) : val) end # def tmpl_if () # .. # ... ... # tmpl.loop('stats',['stat1','stat2','stat3','stat4']) def tmpl_loop (unparsed_tmpl, args) end # def tmpl_loop () # def tmpl_path (args) val = '' spacer = ':' if args.has_key?('path') if (!args.has_key?('clear')) or (args.has_key?('clear') and args['clear'] != 'no') path_append(args['path']) else path=(args['path']) end end if args.has_key?('show') and args['show'] != 'no' if args.has_key?('spacer') spacer = args['spacer'] end val = @path.join(spacer) end return(val.to_s) end # def tmpl_path () # def tmpl_session (args) if args.has_key?('name') self.session=(args['name']) end if (!args.has_key?('show')) || (args.has_key?('show') && args['show'] != 'no') val = @session else val = '' end return((val ? val.to_s : '')) end # def tmpl_session () # def tmpl_style (args) val = '' spacer = ':' if args.has_key?('name') file=(args['name']) end if args.has_key?('show') and args['show'] != 'no' val = @file end return(val.to_s) end # def tmpl_style () # ... def tmpl_unless (unparsed_tmpl, args) val = '' type = args['type'] condition_true = false type = (args.has_key?('type') ? args['type'] : 'block') if args.has_key?('name') case type when 'block' condition_true = test_block(args['name'], args) else val <<= "Invalid unless type type: \"#{args['type']}\"" end end return(condition_true ? val : parse_string(unparsed_tmpl, args)) end # def tmpl_unless () # def tmpl_var (args) val = '' ns = '' if args.has_key?('name') ns = args['ns'] if args.has_key?('ns') if @blocks[ns].has_key?(args['name']) val = @blocks[ns][args['name']] elsif @blocks[''].has_key?(args['name']) val = @blocks[''][args['name']] elsif args.has_key?('default') val = args['default'] else val = "variable \"#{args['name']}\" has no value" end end return(val.to_s) end # def tmpl_var () end # class Template