source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/rdoc/template.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 5.8 KB
Line 
1# Cheap-n-cheerful HTML page template system. You create a
2# template containing:
3#
4# * variable names between percent signs (<tt>%fred%</tt>)
5# * blocks of repeating stuff:
6#
7# START:key
8# ... stuff
9# END:key
10#
11# You feed the code a hash. For simple variables, the values
12# are resolved directly from the hash. For blocks, the hash entry
13# corresponding to +key+ will be an array of hashes. The block will
14# be generated once for each entry. Blocks can be nested arbitrarily
15# deeply.
16#
17# The template may also contain
18#
19# IF:key
20# ... stuff
21# ENDIF:key
22#
23# _stuff_ will only be included in the output if the corresponding
24# key is set in the value hash.
25#
26# Usage: Given a set of templates <tt>T1, T2,</tt> etc
27#
28# values = { "name" => "Dave", state => "TX" }
29#
30# t = TemplatePage.new(T1, T2, T3)
31# File.open(name, "w") {|f| t.write_html_on(f, values)}
32# or
33# res = ''
34# t.write_html_on(res, values)
35#
36#
37
38class TemplatePage
39
40 ##########
41 # A context holds a stack of key/value pairs (like a symbol
42 # table). When asked to resolve a key, it first searches the top of
43 # the stack, then the next level, and so on until it finds a match
44 # (or runs out of entries)
45
46 class Context
47 def initialize
48 @stack = []
49 end
50
51 def push(hash)
52 @stack.push(hash)
53 end
54
55 def pop
56 @stack.pop
57 end
58
59 # Find a scalar value, throwing an exception if not found. This
60 # method is used when substituting the %xxx% constructs
61
62 def find_scalar(key)
63 @stack.reverse_each do |level|
64 if val = level[key]
65 return val unless val.kind_of? Array
66 end
67 end
68 raise "Template error: can't find variable '#{key}'"
69 end
70
71 # Lookup any key in the stack of hashes
72
73 def lookup(key)
74 @stack.reverse_each do |level|
75 val = level[key]
76 return val if val
77 end
78 nil
79 end
80 end
81
82 #########
83 # Simple class to read lines out of a string
84
85 class LineReader
86 # we're initialized with an array of lines
87 def initialize(lines)
88 @lines = lines
89 end
90
91 # read the next line
92 def read
93 @lines.shift
94 end
95
96 # Return a list of lines up to the line that matches
97 # a pattern. That last line is discarded.
98 def read_up_to(pattern)
99 res = []
100 while line = read
101 if pattern.match(line)
102 return LineReader.new(res)
103 else
104 res << line
105 end
106 end
107 raise "Missing end tag in template: #{pattern.source}"
108 end
109
110 # Return a copy of ourselves that can be modified without
111 # affecting us
112 def dup
113 LineReader.new(@lines.dup)
114 end
115 end
116
117
118
119 # +templates+ is an array of strings containing the templates.
120 # We start at the first, and substitute in subsequent ones
121 # where the string <tt>!INCLUDE!</tt> occurs. For example,
122 # we could have the overall page template containing
123 #
124 # <html><body>
125 # <h1>Master</h1>
126 # !INCLUDE!
127 # </bost></html>
128 #
129 # and substitute subpages in to it by passing [master, sub_page].
130 # This gives us a cheap way of framing pages
131
132 def initialize(*templates)
133 result = "!INCLUDE!"
134 templates.each do |content|
135 result.sub!(/!INCLUDE!/, content)
136 end
137 @lines = LineReader.new(result.split($/))
138 end
139
140 # Render the templates into HTML, storing the result on +op+
141 # using the method <tt><<</tt>. The <tt>value_hash</tt> contains
142 # key/value pairs used to drive the substitution (as described above)
143
144 def write_html_on(op, value_hash)
145 @context = Context.new
146 op << substitute_into(@lines, value_hash).tr("\000", '\\')
147 end
148
149
150 # Substitute a set of key/value pairs into the given template.
151 # Keys with scalar values have them substituted directly into
152 # the page. Those with array values invoke <tt>substitute_array</tt>
153 # (below), which examples a block of the template once for each
154 # row in the array.
155 #
156 # This routine also copes with the <tt>IF:</tt>_key_ directive,
157 # removing chunks of the template if the corresponding key
158 # does not appear in the hash, and the START: directive, which
159 # loops its contents for each value in an array
160
161 def substitute_into(lines, values)
162 @context.push(values)
163 skip_to = nil
164 result = []
165
166 while line = lines.read
167
168 case line
169
170 when /^IF:(\w+)/
171 lines.read_up_to(/^ENDIF:#$1/) unless @context.lookup($1)
172
173 when /^IFNOT:(\w+)/
174 lines.read_up_to(/^ENDIF:#$1/) if @context.lookup($1)
175
176 when /^ENDIF:/
177 ;
178
179 when /^START:(\w+)/
180 tag = $1
181 body = lines.read_up_to(/^END:#{tag}/)
182 inner_values = @context.lookup(tag)
183 raise "unknown tag: #{tag}" unless inner_values
184 raise "not array: #{tag}" unless inner_values.kind_of?(Array)
185 inner_values.each do |vals|
186 result << substitute_into(body.dup, vals)
187 end
188 else
189 result << expand_line(line.dup)
190 end
191 end
192
193 @context.pop
194
195 result.join("\n")
196 end
197
198 # Given an individual line, we look for %xxx% constructs and
199 # HREF:ref:name: constructs, substituting for each.
200
201 def expand_line(line)
202 # Generate a cross reference if a reference is given,
203 # otherwise just fill in the name part
204
205 line.gsub!(/HREF:(\w+?):(\w+?):/) {
206 ref = @context.lookup($1)
207 name = @context.find_scalar($2)
208
209 if ref and !ref.kind_of?(Array)
210 "<a href=\"#{ref}\">#{name}</a>"
211 else
212 name
213 end
214 }
215
216 # Substitute in values for %xxx% constructs. This is made complex
217 # because the replacement string may contain characters that are
218 # meaningful to the regexp (like \1)
219
220 line = line.gsub(/%([a-zA-Z]\w*)%/) {
221 val = @context.find_scalar($1)
222 val.tr('\\', "\000")
223 }
224
225
226 line
227 rescue Exception => e
228 $stderr.puts "Error in template: #{e}"
229 $stderr.puts "Original line: #{line}"
230 exit
231 end
232
233end
234
Note: See TracBrowser for help on using the repository browser.