feedtools 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +11 -0
- data/lib/feed_tools.rb +2496 -810
- data/lib/feed_tools/vendor/builder.rb +2 -0
- data/lib/feed_tools/vendor/builder/blankslate.rb +2 -0
- data/lib/feed_tools/vendor/builder/xmlbase.rb +2 -1
- data/lib/feed_tools/vendor/builder/xmlevents.rb +2 -0
- data/lib/feed_tools/vendor/builder/xmlmarkup.rb +4 -2
- data/lib/feed_tools/vendor/htree.rb +97 -0
- data/lib/feed_tools/vendor/htree/container.rb +10 -0
- data/lib/feed_tools/vendor/htree/context.rb +67 -0
- data/lib/feed_tools/vendor/htree/display.rb +27 -0
- data/lib/feed_tools/vendor/htree/doc.rb +149 -0
- data/lib/feed_tools/vendor/htree/elem.rb +262 -0
- data/lib/feed_tools/vendor/htree/encoder.rb +163 -0
- data/lib/feed_tools/vendor/htree/equality.rb +218 -0
- data/lib/feed_tools/vendor/htree/extract_text.rb +37 -0
- data/lib/feed_tools/vendor/htree/fstr.rb +33 -0
- data/lib/feed_tools/vendor/htree/gencode.rb +97 -0
- data/lib/feed_tools/vendor/htree/htmlinfo.rb +672 -0
- data/lib/feed_tools/vendor/htree/inspect.rb +108 -0
- data/lib/feed_tools/vendor/htree/leaf.rb +94 -0
- data/lib/feed_tools/vendor/htree/loc.rb +367 -0
- data/lib/feed_tools/vendor/htree/modules.rb +48 -0
- data/lib/feed_tools/vendor/htree/name.rb +124 -0
- data/lib/feed_tools/vendor/htree/output.rb +207 -0
- data/lib/feed_tools/vendor/htree/parse.rb +407 -0
- data/lib/feed_tools/vendor/htree/raw_string.rb +124 -0
- data/lib/feed_tools/vendor/htree/regexp-util.rb +15 -0
- data/lib/feed_tools/vendor/htree/rexml.rb +130 -0
- data/lib/feed_tools/vendor/htree/scan.rb +166 -0
- data/lib/feed_tools/vendor/htree/tag.rb +111 -0
- data/lib/feed_tools/vendor/htree/template.rb +909 -0
- data/lib/feed_tools/vendor/htree/text.rb +115 -0
- data/lib/feed_tools/vendor/htree/traverse.rb +465 -0
- data/rakefile +1 -1
- data/test/rss_test.rb +97 -0
- metadata +30 -1
@@ -0,0 +1,909 @@
|
|
1
|
+
# = Template Engine
|
2
|
+
#
|
3
|
+
# == Template Syntax
|
4
|
+
#
|
5
|
+
# The htree template engine converts HTML and some data to XHTML.
|
6
|
+
# A template directive is described as special HTML attribute which name
|
7
|
+
# begins with underscore.
|
8
|
+
#
|
9
|
+
# The template directives are listed as follows.
|
10
|
+
#
|
11
|
+
# - <elem \_attr_<i>name</i>="<i>expr</i>">content</elem>
|
12
|
+
# - <elem _text="<i>expr</i>">dummy-content</elem>
|
13
|
+
# - <elem _text><i>expr</i></elem>
|
14
|
+
# - <elem _if="<i>expr</i>" _else="<i>mod.name(args)</i>">then-content</elem>
|
15
|
+
# - <elem _iter="<i>expr.meth(args)//vars</i>">content</elem>
|
16
|
+
# - <elem _iter_content="<i>expr.meth(args)//vars</i>">content</elem>
|
17
|
+
# - <elem _call="<i>mod.name(args)</i>">dummy-content</elem>
|
18
|
+
# - <elem _template="<i>name(vars)</i>">body</elem>
|
19
|
+
#
|
20
|
+
# === Template Semantics
|
21
|
+
#
|
22
|
+
# - attribute substitution
|
23
|
+
# - <elem \_attr_<i>name</i>="<i>expr</i>">content</elem>
|
24
|
+
#
|
25
|
+
# \_attr_<i>name</i> is used for a dynamic attribute.
|
26
|
+
#
|
27
|
+
# <elem _attr_xxx="..."/>
|
28
|
+
# -> <elem xxx="..."/>
|
29
|
+
#
|
30
|
+
# It is expanded to <i>name</i>="content".
|
31
|
+
# The content is generated by evaluating _expr_.
|
32
|
+
# Usually you don't need to care escaping: &, <, > and " are automatically escaped.
|
33
|
+
# If you need to output character references,
|
34
|
+
# the value of _expr_ should be an object which have a +rcdata+ method such as an HTree::Text.
|
35
|
+
# If the value has a +rcdata+ method,
|
36
|
+
# it is called and the result is used as the content with escaping <, > and ".
|
37
|
+
#
|
38
|
+
# \_attr_<i>name</i> can be used multiple times in single element.
|
39
|
+
#
|
40
|
+
# - text substitution
|
41
|
+
# - <elem _text="<i>expr</i>">dummy-content</elem>
|
42
|
+
# - <elem _text><i>expr</i></elem>
|
43
|
+
#
|
44
|
+
# _text substitutes content of the element by the string
|
45
|
+
# evaluated from _expr_.
|
46
|
+
# _expr_ is described in the attribute value or the content of the element.
|
47
|
+
#
|
48
|
+
# If a result of _expr_ have &, < and/or >, they are automatically escaped.
|
49
|
+
# If you need to output character references,
|
50
|
+
# the value of _expr_ should be an object which have a +rcdata+ method such as an HTree::Text.
|
51
|
+
# If the value has a +rcdata+ method,
|
52
|
+
# it is called and the result is used as the content with escaping < and >.
|
53
|
+
#
|
54
|
+
# If the element is span or div, and there is no other attributes,
|
55
|
+
# no tags are produced.
|
56
|
+
#
|
57
|
+
# <elem _text="...">dummy-content</elem>
|
58
|
+
# -> <elem>...</elem>
|
59
|
+
#
|
60
|
+
# - conditional
|
61
|
+
# - <elem _if="<i>expr</i>">then-content</elem>
|
62
|
+
# - <elem _if="<i>expr</i>" _else="<i>name(args)</i>">then-content</elem>
|
63
|
+
#
|
64
|
+
# _if is used for conditional.
|
65
|
+
#
|
66
|
+
# If <i>expr</i> is evaluated to true, it expands as follows
|
67
|
+
# regardless of existence of _else.
|
68
|
+
#
|
69
|
+
# <elem _if="<i>expr</i>">then-content</elem>
|
70
|
+
# -> <elem>then-content</elem>
|
71
|
+
#
|
72
|
+
# If <i>expr</i> is evaluated to false, it expands using _else.
|
73
|
+
# If _else is not given, it expands to empty.
|
74
|
+
# If _else is given, it expands as follows.
|
75
|
+
#
|
76
|
+
# <elem _if="<i>expr</i>" _else="<i>name(args)</i>">then-content</elem>
|
77
|
+
# -> <elem _call="<i>name(args)</i>">then-content</elem>
|
78
|
+
# -> see _call for further expansion.
|
79
|
+
#
|
80
|
+
# It is expanded to <elem>then-content</elem> if _expr_ is evaluated to
|
81
|
+
# a true value.
|
82
|
+
# Otherwise, it is replaced by other template specified by _else attribute.
|
83
|
+
# If _else attribute is not given, it just replaced by empty.
|
84
|
+
#
|
85
|
+
# - iteration
|
86
|
+
# - <elem _iter="<i>expr.meth(args)//vars</i>">content</elem>
|
87
|
+
# - <elem _iter_content="<i>expr.meth(args)//vars</i>">content</elem>
|
88
|
+
#
|
89
|
+
# _iter and _iter_content is used for iteration.
|
90
|
+
# _iter iterates the element itself but _iter_content iterates the content.
|
91
|
+
#
|
92
|
+
# <outer _iter="..."><inner/></outer>
|
93
|
+
# -> <outer><inner/></outer><outer><inner/></outer>...
|
94
|
+
#
|
95
|
+
# <outer _iter_content="..."><inner/></outer>
|
96
|
+
# -> <outer><inner/><inner/>...</outer>
|
97
|
+
#
|
98
|
+
# <i>expr.meth(args)</i> specifies iterator method call.
|
99
|
+
# It is actually called with a block.
|
100
|
+
# The block have block parameters <i>vars</i>.
|
101
|
+
# <i>vars</i> must be variables separated by comma.
|
102
|
+
#
|
103
|
+
# - template call
|
104
|
+
# - <elem _call="<i>name(args)</i>">dummy-content</elem>
|
105
|
+
# - <elem _call="<i>mod.name(args)</i>">dummy-content</elem>
|
106
|
+
#
|
107
|
+
# _call is used to expand a template function.
|
108
|
+
# The template function is defined by _template.
|
109
|
+
#
|
110
|
+
# <d _template="m">...</d>
|
111
|
+
# <c _call="m">...</c>
|
112
|
+
# -> <d>...</d>
|
113
|
+
#
|
114
|
+
# A local template can be called as follows:
|
115
|
+
#
|
116
|
+
# HTree.expand_template{<<'End'}
|
117
|
+
# <a _template=ruby_talk(num)
|
118
|
+
# _attr_href='"http://ruby-talk.org/#{num}"'
|
119
|
+
# >[ruby-talk:<span _text=num>nnn</span>]</a>
|
120
|
+
# Ruby 1.8.0 is released at <span _call=ruby_talk(77946) />.
|
121
|
+
# Ruby 1.8.1 is released at <span _call=ruby_talk(88814) />.
|
122
|
+
# End
|
123
|
+
#
|
124
|
+
# <i>mod</i> should be the result of HTree.compile_template.
|
125
|
+
#
|
126
|
+
# M = HTree.compile_template(<<'End')
|
127
|
+
# <a _template=ruby_talk(num)
|
128
|
+
# _attr_href='"http://ruby-talk.org/#{num}"'
|
129
|
+
# >[ruby-talk:<span _text=num>nnn</span>]</a>
|
130
|
+
# End
|
131
|
+
# HTree.expand_template{<<'End'}
|
132
|
+
# <html>
|
133
|
+
# Ruby 1.8.0 is released at <span _call=M.ruby_talk(77946) />.
|
134
|
+
# Ruby 1.8.1 is released at <span _call=M.ruby_talk(88814) />.
|
135
|
+
# </html>
|
136
|
+
# End
|
137
|
+
#
|
138
|
+
# The module can included.
|
139
|
+
# In such case, the template function can be called without <i>mod.</i>
|
140
|
+
# prefix.
|
141
|
+
#
|
142
|
+
# include HTree.compile_template(<<'End')
|
143
|
+
# <a _template=ruby_talk(num)
|
144
|
+
# _attr_href='"http://ruby-talk.org/#{num}"'
|
145
|
+
# >[ruby-talk:<span _text=num>nnn</span>]</a>
|
146
|
+
# End
|
147
|
+
# HTree.expand_template{<<'End'}
|
148
|
+
# <html>
|
149
|
+
# Ruby 1.8.0 is released at <span _call=ruby_talk(77946) />.
|
150
|
+
# Ruby 1.8.1 is released at <span _call=ruby_talk(88814) />.
|
151
|
+
# </html>
|
152
|
+
# End
|
153
|
+
#
|
154
|
+
# - template definition
|
155
|
+
# - <elem _template="<i>name(vars)</i>">body</elem>
|
156
|
+
#
|
157
|
+
# _template defines a template function which is usable by _call.
|
158
|
+
#
|
159
|
+
# When a template is compiled to a module by HTree.compile_template,
|
160
|
+
# the module have a module function for each template function
|
161
|
+
# defined by outermost _template attribute.
|
162
|
+
#
|
163
|
+
# === White Space Handling
|
164
|
+
#
|
165
|
+
# The htree template engine strips whitespace text nodes in a template
|
166
|
+
# except under HTML pre element.
|
167
|
+
#
|
168
|
+
# For example the white space text node between two spans in following template is stripped.
|
169
|
+
#
|
170
|
+
# <span _text="'a'"/> <span _text="'b'"/> -> "ab"
|
171
|
+
#
|
172
|
+
# Character entity references are not stripped.
|
173
|
+
#
|
174
|
+
# <span _text="'a'"/> <span _text="'b'"/> -> "a b"
|
175
|
+
#
|
176
|
+
# Text nodes generated by _text is not stripped.
|
177
|
+
#
|
178
|
+
# <span _text="'a'"/><span _text="' '"> </span><span _text="'b'"/> -> "a b"
|
179
|
+
#
|
180
|
+
# == Method Summary
|
181
|
+
#
|
182
|
+
# - HTree.expand_template(<i>template_pathname</i>) -> $stdout
|
183
|
+
# - HTree.expand_template(<i>template_pathname</i>, <i>obj</i>) -> $stdout
|
184
|
+
# - HTree.expand_template(<i>template_pathname</i>, <i>obj</i>, <i>out</i>) -> <i>out</i>
|
185
|
+
# - HTree.expand_template(<i>template_pathname</i>, <i>obj</i>, <i>out</i>, <i>encoding</i>) -> <i>out</i>
|
186
|
+
#
|
187
|
+
# - HTree.expand_template{<i>template_string</i>} -> $stdout
|
188
|
+
# - HTree.expand_template(<i>out</i>) {<i>template_string</i>} -> <i>out</i>
|
189
|
+
# - HTree.expand_template(<i>out</i>, <i>encoding</i>) {<i>template_string</i>} -> <i>out</i>
|
190
|
+
#
|
191
|
+
# - HTree.compile_template(<i>template_string</i>) -> Module
|
192
|
+
# - HTree{<i>template_string</i>} -> HTree::Doc
|
193
|
+
# - HTree(<i>html_string</i>) -> HTree::Doc
|
194
|
+
#
|
195
|
+
# == Design Decision on Design/Logic Separation
|
196
|
+
#
|
197
|
+
# HTree template engine doesn't force you to separate design and logic.
|
198
|
+
# Any logic (Ruby code) can be embedded in design (HTML).
|
199
|
+
#
|
200
|
+
# However the template engine cares the separation by logic refactorings.
|
201
|
+
# The logic is easy to move between a template and an application.
|
202
|
+
# For example, following tangled template
|
203
|
+
#
|
204
|
+
# tmpl.html:
|
205
|
+
# <html>
|
206
|
+
# <head>
|
207
|
+
# <title _text="very-complex-ruby-code">dummy</title>
|
208
|
+
# </head>
|
209
|
+
# ...
|
210
|
+
# </html>
|
211
|
+
#
|
212
|
+
# app.rb:
|
213
|
+
# HTree.expand_template('tmpl.html', obj)
|
214
|
+
#
|
215
|
+
# can be refactored as follows.
|
216
|
+
#
|
217
|
+
# tmpl.html:
|
218
|
+
# <html>
|
219
|
+
# <head>
|
220
|
+
# <title _text="title">dummy</title>
|
221
|
+
# </head>
|
222
|
+
# ...
|
223
|
+
# </html>
|
224
|
+
#
|
225
|
+
# app.rb:
|
226
|
+
# def obj.title
|
227
|
+
# very-complex-ruby-code
|
228
|
+
# end
|
229
|
+
# HTree.expand_template('tmpl.html', obj)
|
230
|
+
#
|
231
|
+
# In general, any expression in a template can be refactored to an application
|
232
|
+
# by extracting it as a method.
|
233
|
+
# In JSP, this is difficult especially for a code fragment of an iteration.
|
234
|
+
#
|
235
|
+
# Also HTree encourages to separate business logic (Ruby code in an application)
|
236
|
+
# and presentation logic (Ruby code in a template).
|
237
|
+
# For example, presentation logic to color table rows stripe
|
238
|
+
# can be embedded in a template.
|
239
|
+
# It doesn't need to tangle an application.
|
240
|
+
#
|
241
|
+
|
242
|
+
# :stopdoc:
|
243
|
+
module HTree # :nodoc:
|
244
|
+
EmptyBindingObject = Object.new
|
245
|
+
end
|
246
|
+
def (HTree::EmptyBindingObject).empty_binding
|
247
|
+
binding
|
248
|
+
end
|
249
|
+
|
250
|
+
require 'htree/parse'
|
251
|
+
require 'htree/gencode'
|
252
|
+
require 'htree/equality'
|
253
|
+
require 'htree/traverse'
|
254
|
+
|
255
|
+
# call-seq:
|
256
|
+
# HTree.expand_template(template_pathname, obj=nil, out=$stdout, encoding=internal_encoding) -> out
|
257
|
+
# HTree.expand_template(out=$stdout, encoding=internal_encoding) { template_string } -> out
|
258
|
+
#
|
259
|
+
# <code>HTree.expand_template</code> expands a template.
|
260
|
+
#
|
261
|
+
# The arguments should be specified as follows.
|
262
|
+
# All argument except <i>pathname</i> are optional.
|
263
|
+
#
|
264
|
+
# - HTree.expand_template(<i>pathname</i>, <i>obj</i>, <i>out</i>, <i>encoding</i>) -> <i>out</i>
|
265
|
+
# - HTree.expand_template(<i>out</i>, <i>encoding</i>) {<i>template_string</i>} -> <i>out</i>
|
266
|
+
#
|
267
|
+
# The template is specified by a file or a string.
|
268
|
+
# If a block is not given, the first argument represent a template pathname.
|
269
|
+
# Otherwise, the block is yielded and its value is interpreted as a template
|
270
|
+
# string.
|
271
|
+
# So it can be called as follows in simplest case.
|
272
|
+
#
|
273
|
+
# - HTree.expand_template(<i>template_pathname</i>)
|
274
|
+
# - HTree.expand_template{<i>template_string</i>}
|
275
|
+
#
|
276
|
+
# Ruby expressions in the template file specified by _template_pathname_ are
|
277
|
+
# evaluated in the context of the optional second argument <i>obj</i> as follows.
|
278
|
+
# I.e. the pseudo variable self in the expressions is bound to <i>obj</i>.
|
279
|
+
#
|
280
|
+
# HTree.expand_template(template_pathname, obj)
|
281
|
+
#
|
282
|
+
# Ruby expressions in the template_string are evaluated
|
283
|
+
# in the context of the caller of HTree.expand_template.
|
284
|
+
# (binding information is specified by the block.)
|
285
|
+
# I.e. they can access local variables etc.
|
286
|
+
# We recommend to specify template_string as a literal string without
|
287
|
+
# interpolation because dynamically generated string may break lexical scope.
|
288
|
+
#
|
289
|
+
# HTree.expand_template has two more optional arguments:
|
290
|
+
# <i>out</i>, <i>encoding</i>.
|
291
|
+
#
|
292
|
+
# <i>out</i> specifies output target.
|
293
|
+
# It should have <tt><<</tt> method: IO and String for example.
|
294
|
+
# If it is not specified, $stdout is used.
|
295
|
+
#
|
296
|
+
# <i>encoding</i> specifies output character encoding.
|
297
|
+
# If it is not specified, internal encoding is used.
|
298
|
+
#
|
299
|
+
# HTree.expand_template returns <i>out</i> or $stdout if <i>out</i> is not
|
300
|
+
# specified.
|
301
|
+
#
|
302
|
+
def HTree.expand_template(*args, &block)
|
303
|
+
if block
|
304
|
+
template = block.call
|
305
|
+
binding = block
|
306
|
+
else
|
307
|
+
pathname = args.shift
|
308
|
+
obj = args.shift
|
309
|
+
if pathname.respond_to? :read
|
310
|
+
template = pathname.read.untaint
|
311
|
+
if template.respond_to? :charset
|
312
|
+
template = Iconv.conv(HTree::Encoder.internal_charset, template.charset, template)
|
313
|
+
end
|
314
|
+
else
|
315
|
+
template = File.read(pathname).untaint
|
316
|
+
end
|
317
|
+
Thread.current[:htree_expand_template_obj] = obj
|
318
|
+
binding = eval(<<-'End',
|
319
|
+
Thread.current[:htree_expand_template_obj].class.class_eval <<-'EE'
|
320
|
+
Thread.current[:htree_expand_template_obj].instance_eval { binding }
|
321
|
+
EE
|
322
|
+
End
|
323
|
+
HTree::EmptyBindingObject.empty_binding)
|
324
|
+
Thread.current[:htree_expand_template_obj] = nil
|
325
|
+
end
|
326
|
+
|
327
|
+
out = args.shift || $stdout
|
328
|
+
encoding = args.shift || HTree::Encoder.internal_charset
|
329
|
+
if !args.empty?
|
330
|
+
raise ArgumentError, "wrong number of arguments"
|
331
|
+
end
|
332
|
+
HTree::TemplateCompiler.new.expand_template(template, out, encoding, binding)
|
333
|
+
end
|
334
|
+
|
335
|
+
# call-seq:
|
336
|
+
# HTree(html_string) -> doc
|
337
|
+
# HTree{template_string} -> doc
|
338
|
+
#
|
339
|
+
# <code>HTree(<i>html_string</i>)</code> parses <i>html_string</i>.
|
340
|
+
# <code>HTree{<i>template_string</i>}</code> parses <i>template_string</i> and expand it as a template.
|
341
|
+
# Ruby expressions in <i>template_string</i> is evaluated in the scope of the caller.
|
342
|
+
#
|
343
|
+
# <code>HTree()</code> and <code>HTree{}</code> returns a tree as an instance of HTree::Doc.
|
344
|
+
def HTree(html_string=nil, &block)
|
345
|
+
if block_given?
|
346
|
+
raise ArgumentError, "both argument and block given." if html_string
|
347
|
+
template = block.call
|
348
|
+
HTree.parse(HTree::TemplateCompiler.new.expand_fragment_template(template, '', HTree::Encoder.internal_charset, block))
|
349
|
+
else
|
350
|
+
HTree.parse(html_string)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# call-seq:
|
355
|
+
# HTree.compile_template(template_string) -> module
|
356
|
+
#
|
357
|
+
# <code>HTree.compile_template(<i>template_string</i>)</code> compiles
|
358
|
+
# <i>template_string</i> as a template.
|
359
|
+
#
|
360
|
+
# HTree.compile_template returns a module.
|
361
|
+
# The module has module functions for each templates defined in
|
362
|
+
# <i>template_string</i>.
|
363
|
+
# The returned module can be used for +include+.
|
364
|
+
#
|
365
|
+
# M = HTree.compile_template(<<'End')
|
366
|
+
# <p _template=birthday(subj,t)>
|
367
|
+
# <span _text=subj />'s birthday is <span _text="t.strftime('%B %dth %Y')"/>.
|
368
|
+
# </p>
|
369
|
+
# End
|
370
|
+
# M.birthday('Ruby', Time.utc(1993, 2, 24)).display_xml
|
371
|
+
# # <p>Ruby's birthday is February 24th 1993.</p>
|
372
|
+
#
|
373
|
+
# The module function takes arguments specifies by a <code>_template</code>
|
374
|
+
# attribute and returns a tree represented as HTree::Node.
|
375
|
+
#
|
376
|
+
def HTree.compile_template(template_string)
|
377
|
+
code = HTree::TemplateCompiler.new.compile_template(template_string)
|
378
|
+
Thread.current[:htree_compile_template_code] = code
|
379
|
+
mod = eval(<<-'End',
|
380
|
+
eval(Thread.current[:htree_compile_template_code])
|
381
|
+
End
|
382
|
+
HTree::EmptyBindingObject.empty_binding)
|
383
|
+
Thread.current[:htree_compile_template_code] = nil
|
384
|
+
mod
|
385
|
+
end
|
386
|
+
|
387
|
+
class HTree::TemplateCompiler # :nodoc:
|
388
|
+
IGNORABLE_ELEMENTS = {
|
389
|
+
'span' => true,
|
390
|
+
'div' => true,
|
391
|
+
'{http://www.w3.org/1999/xhtml}span' => true,
|
392
|
+
'{http://www.w3.org/1999/xhtml}div' => true,
|
393
|
+
}
|
394
|
+
|
395
|
+
def initialize
|
396
|
+
@gensym_id = 0
|
397
|
+
end
|
398
|
+
|
399
|
+
def gensym(suffix='')
|
400
|
+
@gensym_id += 1
|
401
|
+
"g#{@gensym_id}#{suffix}"
|
402
|
+
end
|
403
|
+
|
404
|
+
def parse_template(template)
|
405
|
+
strip_whitespaces(HTree.parse(template))
|
406
|
+
end
|
407
|
+
|
408
|
+
WhiteSpacePreservingElements = {
|
409
|
+
'{http://www.w3.org/1999/xhtml}pre' => true
|
410
|
+
}
|
411
|
+
|
412
|
+
def strip_whitespaces(template)
|
413
|
+
case template
|
414
|
+
when HTree::Doc
|
415
|
+
HTree::Doc.new(*template.children.map {|c| strip_whitespaces(c) }.compact)
|
416
|
+
when HTree::Elem, HTree::Doc
|
417
|
+
return template if WhiteSpacePreservingElements[template.name]
|
418
|
+
subst = {}
|
419
|
+
template.children.each_with_index {|c, i|
|
420
|
+
subst[i] = strip_whitespaces(c)
|
421
|
+
}
|
422
|
+
template.subst_subnode(subst)
|
423
|
+
when HTree::Text
|
424
|
+
if /\A[ \t\r\n]*\z/ =~ template.rcdata
|
425
|
+
nil
|
426
|
+
else
|
427
|
+
template
|
428
|
+
end
|
429
|
+
else
|
430
|
+
template
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def expand_template(template, out, encoding, binding)
|
435
|
+
template = parse_template(template)
|
436
|
+
outvar = gensym('out')
|
437
|
+
contextvar = gensym('top_context')
|
438
|
+
code = <<"End"
|
439
|
+
#{outvar} = HTree::Encoder.new(#{encoding.dump})
|
440
|
+
#{contextvar} = HTree::DefaultContext
|
441
|
+
#{compile_body(outvar, contextvar, template, false)}\
|
442
|
+
#{outvar}.finish_with_xmldecl
|
443
|
+
End
|
444
|
+
result = eval(code, binding)
|
445
|
+
out << result
|
446
|
+
out
|
447
|
+
end
|
448
|
+
|
449
|
+
def expand_fragment_template(template, out, encoding, binding)
|
450
|
+
template = parse_template(template)
|
451
|
+
outvar = gensym('out')
|
452
|
+
contextvar = gensym('top_context')
|
453
|
+
code = <<"End"
|
454
|
+
#{outvar} = HTree::Encoder.new(#{encoding.dump})
|
455
|
+
#{contextvar} = HTree::DefaultContext
|
456
|
+
#{compile_body(outvar, contextvar, template, false)}\
|
457
|
+
#{outvar}.finish
|
458
|
+
End
|
459
|
+
result = eval(code, binding)
|
460
|
+
out << result
|
461
|
+
out
|
462
|
+
end
|
463
|
+
|
464
|
+
def compile_template(src)
|
465
|
+
srcdoc = parse_template(src)
|
466
|
+
templates = []
|
467
|
+
body = extract_templates(srcdoc, templates, true)
|
468
|
+
methods = []
|
469
|
+
templates.each {|name_args, node|
|
470
|
+
methods << compile_global_template(name_args, node)
|
471
|
+
}
|
472
|
+
<<"End"
|
473
|
+
require 'htree/encoder'
|
474
|
+
require 'htree/context'
|
475
|
+
Module.new.module_eval <<'EE'
|
476
|
+
module_function
|
477
|
+
#{methods.join('').chomp}
|
478
|
+
self
|
479
|
+
EE
|
480
|
+
End
|
481
|
+
end
|
482
|
+
|
483
|
+
def template_attribute?(name)
|
484
|
+
/\A_/ =~ name.local_name
|
485
|
+
end
|
486
|
+
|
487
|
+
def extract_templates(node, templates, is_toplevel)
|
488
|
+
case node
|
489
|
+
when HTree::Doc
|
490
|
+
subst = {}
|
491
|
+
node.children.each_with_index {|n, i|
|
492
|
+
subst[i] = extract_templates(n, templates, is_toplevel)
|
493
|
+
}
|
494
|
+
node.subst_subnode(subst)
|
495
|
+
when HTree::Elem
|
496
|
+
ht_attrs, rest_attrs = node.attributes.partition {|name, text| template_attribute? name }
|
497
|
+
if ht_attrs.empty?
|
498
|
+
subst = {}
|
499
|
+
node.children.each_with_index {|n, i|
|
500
|
+
subst[i] = extract_templates(n, templates, is_toplevel)
|
501
|
+
}
|
502
|
+
node.subst_subnode(subst)
|
503
|
+
else
|
504
|
+
ht_attrs.each {|htname, text|
|
505
|
+
if htname.universal_name == '_template'
|
506
|
+
name_fargs = text.to_s
|
507
|
+
templates << [name_fargs, node.subst_subnode('_template' => nil)]
|
508
|
+
return nil
|
509
|
+
end
|
510
|
+
}
|
511
|
+
if is_toplevel
|
512
|
+
raise HTree::Error, "unexpected template attributes in toplevel: #{ht_attrs.inspect}"
|
513
|
+
else
|
514
|
+
node
|
515
|
+
end
|
516
|
+
end
|
517
|
+
else
|
518
|
+
node
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
ID_PAT = /[a-z][a-z0-9_]*/
|
523
|
+
NAME_FARGS_PAT = /(#{ID_PAT})(?:\(\s*(|#{ID_PAT}\s*(?:,\s*#{ID_PAT}\s*)*)\))?/
|
524
|
+
def compile_global_template(name_fargs, node)
|
525
|
+
unless /\A#{NAME_FARGS_PAT}\z/o =~ name_fargs
|
526
|
+
raise HTree::Error, "invalid template declaration: #{name_fargs}"
|
527
|
+
end
|
528
|
+
name = $1
|
529
|
+
fargs = $2 ? $2.scan(ID_PAT) : []
|
530
|
+
|
531
|
+
outvar = gensym('out')
|
532
|
+
contextvar = gensym('top_context')
|
533
|
+
args2 = [outvar, contextvar, *fargs]
|
534
|
+
|
535
|
+
<<"End"
|
536
|
+
def #{name}(#{fargs.join(',')})
|
537
|
+
HTree.parse(_xml_#{name}(#{fargs.join(',')}))
|
538
|
+
end
|
539
|
+
def _xml_#{name}(#{fargs.join(',')})
|
540
|
+
#{outvar} = HTree::Encoder.new(HTree::Encoder.internal_charset)
|
541
|
+
#{contextvar} = HTree::DefaultContext
|
542
|
+
_ht_#{name}(#{args2.join(',')})
|
543
|
+
#{outvar}.finish
|
544
|
+
end
|
545
|
+
def _ht_#{name}(#{args2.join(',')})
|
546
|
+
#{compile_body(outvar, contextvar, node, false)}\
|
547
|
+
end
|
548
|
+
public :_ht_#{name}
|
549
|
+
End
|
550
|
+
end
|
551
|
+
|
552
|
+
def compile_local_template(name_fargs, node, local_templates)
|
553
|
+
unless /\A#{NAME_FARGS_PAT}\z/o =~ name_fargs
|
554
|
+
raise HTree::Error, "invalid template declaration: #{name_fargs}"
|
555
|
+
end
|
556
|
+
name = $1
|
557
|
+
fargs = $2 ? $2.scan(ID_PAT) : []
|
558
|
+
|
559
|
+
outvar = gensym('out')
|
560
|
+
contextvar = gensym('top_context')
|
561
|
+
args2 = [outvar, contextvar, *fargs]
|
562
|
+
|
563
|
+
<<"End"
|
564
|
+
#{name} = lambda {|#{args2.join(',')}|
|
565
|
+
#{compile_body(outvar, contextvar, node, false, local_templates)}\
|
566
|
+
}
|
567
|
+
End
|
568
|
+
end
|
569
|
+
|
570
|
+
def compile_body(outvar, contextvar, node, is_toplevel, local_templates={})
|
571
|
+
if node.elem? && IGNORABLE_ELEMENTS[node.name] && node.attributes.empty?
|
572
|
+
node = TemplateNode.new(node.children)
|
573
|
+
else
|
574
|
+
node = TemplateNode.new(node)
|
575
|
+
end
|
576
|
+
generate_logic_node([:content], node, local_templates).generate_xml_output_code(outvar, contextvar)
|
577
|
+
end
|
578
|
+
|
579
|
+
def compile_node(node, local_templates)
|
580
|
+
case node
|
581
|
+
when HTree::Doc
|
582
|
+
TemplateNode.new(node.children.map {|n| compile_node(n, local_templates) })
|
583
|
+
when HTree::Elem
|
584
|
+
ht_attrs = node.attributes.find_all {|name, text| template_attribute? name }
|
585
|
+
ht_attrs = ht_attrs.sort_by {|htname, text| htname.universal_name }
|
586
|
+
ignore_tag = false
|
587
|
+
unless ht_attrs.empty?
|
588
|
+
attr_mod = {}
|
589
|
+
ht_attrs.each {|htname, text|
|
590
|
+
attr_mod[htname] = nil
|
591
|
+
if /\A_attr_/ =~ htname.local_name
|
592
|
+
attr_mod[TemplateAttrName.new(htname.namespace_prefix, htname.namespace_uri, $')] = text
|
593
|
+
end
|
594
|
+
}
|
595
|
+
ht_attrs.reject! {|htname, text| /\A_attr_/ =~ htname.local_name }
|
596
|
+
node = node.subst_subnode(attr_mod)
|
597
|
+
ignore_tag = IGNORABLE_ELEMENTS[node.name] && node.attributes.empty?
|
598
|
+
end
|
599
|
+
ht_names = ht_attrs.map {|htname, text| htname.universal_name }
|
600
|
+
ht_vals = ht_attrs.map {|htname, text| text.to_s }
|
601
|
+
case ht_names
|
602
|
+
when []
|
603
|
+
generate_logic_node([:tag, [:content]], node, local_templates)
|
604
|
+
when ['_text'] # <n _text="expr" /> or <n _text>expr</n>
|
605
|
+
if ht_vals[0] != '_text' # xxx: attribute value is really omitted?
|
606
|
+
expr = ht_vals[0]
|
607
|
+
else
|
608
|
+
children = node.children
|
609
|
+
if children.length != 1
|
610
|
+
raise HTree::Error, "_text expression has #{children.length} nodes"
|
611
|
+
end
|
612
|
+
if !children[0].text?
|
613
|
+
raise HTree::Error, "_text expression is not text: #{children[0].class}"
|
614
|
+
end
|
615
|
+
expr = children[0].to_s
|
616
|
+
end
|
617
|
+
if /\A\s*'((?:[^'\\]|\\[\0-\377])*)'\s*\z/ =~ expr
|
618
|
+
# if expr is just a constant string literal, use it as a literal text.
|
619
|
+
# This saves dynamic evaluation of <span _text="' '"/>
|
620
|
+
# xxx: handle "..." as well if it has no #{}.
|
621
|
+
HTree::Text.new($1.gsub(/\\([\0-\377])/, '\1'))
|
622
|
+
else
|
623
|
+
generate_logic_node(compile_dynamic_text(ignore_tag, expr), node, local_templates)
|
624
|
+
end
|
625
|
+
when ['_if'] # <n _if="expr" >...</n>
|
626
|
+
generate_logic_node(compile_if(ignore_tag, ht_vals[0], nil), node, local_templates)
|
627
|
+
when ['_else', '_if'] # <n _if="expr" _else="expr.meth(args)" >...</n>
|
628
|
+
generate_logic_node(compile_if(ignore_tag, ht_vals[1], ht_vals[0]), node, local_templates)
|
629
|
+
when ['_call'] # <n _call="recv.meth(args)" />
|
630
|
+
generate_logic_node(compile_call(ignore_tag, ht_vals[0]), node, local_templates)
|
631
|
+
when ['_iter'] # <n _iter="expr.meth(args)//fargs" >...</n>
|
632
|
+
generate_logic_node(compile_iter(ignore_tag, ht_vals[0]), node, local_templates)
|
633
|
+
when ['_iter_content'] # <n _iter_content="expr.meth(args)//fargs" >...</n>
|
634
|
+
generate_logic_node(compile_iter_content(ignore_tag, ht_vals[0]), node, local_templates)
|
635
|
+
else
|
636
|
+
raise HTree::Error, "unexpected template attributes: #{ht_attrs.inspect}"
|
637
|
+
end
|
638
|
+
else
|
639
|
+
return node
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
def valid_syntax?(code)
|
644
|
+
begin
|
645
|
+
eval("BEGIN {return true}\n#{code.untaint}")
|
646
|
+
rescue SyntaxError
|
647
|
+
raise SyntaxError, "invalid code: #{code}"
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def check_syntax(code)
|
652
|
+
unless valid_syntax?(code)
|
653
|
+
raise HTree::Error, "invalid ruby code: #{code}"
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
def compile_dynamic_text(ignore_tag, expr)
|
658
|
+
check_syntax(expr)
|
659
|
+
logic = [:text, expr]
|
660
|
+
logic = [:tag, logic] unless ignore_tag
|
661
|
+
logic
|
662
|
+
end
|
663
|
+
|
664
|
+
def compile_if(ignore_tag, expr, else_call)
|
665
|
+
check_syntax(expr)
|
666
|
+
then_logic = [:content]
|
667
|
+
unless ignore_tag
|
668
|
+
then_logic = [:tag, then_logic]
|
669
|
+
end
|
670
|
+
else_logic = nil
|
671
|
+
if else_call
|
672
|
+
else_logic = compile_call(true, else_call)
|
673
|
+
end
|
674
|
+
[:if, expr, then_logic, else_logic]
|
675
|
+
end
|
676
|
+
|
677
|
+
def split_args(spec)
|
678
|
+
return spec, '' if /\)\z/ !~ spec
|
679
|
+
i = spec.length - 1
|
680
|
+
nest = 0
|
681
|
+
begin
|
682
|
+
raise HTree::Error, "unmatched paren: #{spec}" if i < 0
|
683
|
+
case spec[i]
|
684
|
+
when ?\)
|
685
|
+
nest += 1
|
686
|
+
when ?\(
|
687
|
+
nest -= 1
|
688
|
+
end
|
689
|
+
i -= 1
|
690
|
+
end while nest != 0
|
691
|
+
i += 1
|
692
|
+
return spec[0, i], spec[(i+1)...-1]
|
693
|
+
end
|
694
|
+
|
695
|
+
def compile_call(ignore_tag, spec)
|
696
|
+
# spec : [recv.]meth[(args)]
|
697
|
+
spec = spec.strip
|
698
|
+
spec, args = split_args(spec)
|
699
|
+
unless /#{ID_PAT}\z/o =~ spec
|
700
|
+
raise HTree::Error, "invalid _call: #{spec}"
|
701
|
+
end
|
702
|
+
meth = $&
|
703
|
+
spec = $`
|
704
|
+
if /\A\s*\z/ =~ spec
|
705
|
+
recv = nil
|
706
|
+
elsif /\A\s*(.*)\.\z/ =~ spec
|
707
|
+
recv = $1
|
708
|
+
else
|
709
|
+
raise HTree::Error, "invalid _call: #{spec}"
|
710
|
+
end
|
711
|
+
if recv
|
712
|
+
check_syntax(recv)
|
713
|
+
check_syntax("#{recv}.#{meth}(#{args})")
|
714
|
+
end
|
715
|
+
check_syntax("#{meth}(#{args})")
|
716
|
+
[:call, recv, meth, args]
|
717
|
+
end
|
718
|
+
|
719
|
+
def compile_iter(ignore_tag, spec)
|
720
|
+
# spec: <n _iter="expr.meth[(args)]//fargs" >...</n>
|
721
|
+
spec = spec.strip
|
722
|
+
unless %r{\s*//\s*(#{ID_PAT}\s*(?:,\s*#{ID_PAT}\s*)*)?\z}o =~ spec
|
723
|
+
raise HTree::Error, "invalid block arguments for _iter: #{spec}"
|
724
|
+
end
|
725
|
+
call = $`.strip
|
726
|
+
fargs = $1 ? $1.strip : ''
|
727
|
+
check_syntax("#{call} {|#{fargs}| }")
|
728
|
+
logic = [:content]
|
729
|
+
unless ignore_tag
|
730
|
+
logic = [:tag, logic]
|
731
|
+
end
|
732
|
+
[:iter, call, fargs, logic]
|
733
|
+
end
|
734
|
+
|
735
|
+
def compile_iter_content(ignore_tag, spec)
|
736
|
+
# spec: <n _iter_content="expr.meth[(args)]//fargs" >...</n>
|
737
|
+
spec = spec.strip
|
738
|
+
unless %r{\s*//\s*(#{ID_PAT}\s*(?:,\s*#{ID_PAT}\s*)*)?\z}o =~ spec
|
739
|
+
raise HTree::Error, "invalid block arguments for _iter: #{spec}"
|
740
|
+
end
|
741
|
+
call = $`.strip
|
742
|
+
fargs = $1 ? $1.strip : ''
|
743
|
+
check_syntax("#{call} {|#{fargs}| }")
|
744
|
+
logic = [:content]
|
745
|
+
logic = [:iter, call, fargs, logic]
|
746
|
+
unless ignore_tag
|
747
|
+
logic = [:tag, logic]
|
748
|
+
end
|
749
|
+
logic
|
750
|
+
end
|
751
|
+
|
752
|
+
def generate_logic_node(logic, node, local_templates)
|
753
|
+
# logic ::= [:if, expr, then_logic, else_logic]
|
754
|
+
# | [:iter, call, fargs, logic]
|
755
|
+
# | [:tag, logic]
|
756
|
+
# | [:text, expr]
|
757
|
+
# | [:call, expr, meth, args]
|
758
|
+
# | [:content]
|
759
|
+
# | [:empty]
|
760
|
+
case logic.first
|
761
|
+
when :empty
|
762
|
+
nil
|
763
|
+
when :content
|
764
|
+
subtemplates = []
|
765
|
+
children = []
|
766
|
+
node.children.each {|c|
|
767
|
+
children << extract_templates(c, subtemplates, false)
|
768
|
+
}
|
769
|
+
if subtemplates.empty?
|
770
|
+
TemplateNode.new(node.children.map {|n|
|
771
|
+
compile_node(n, local_templates)
|
772
|
+
})
|
773
|
+
else
|
774
|
+
local_templates = local_templates.dup
|
775
|
+
decl = ''
|
776
|
+
subtemplates.each {|sub_name_args, sub_node|
|
777
|
+
sub_name = sub_name_args[ID_PAT]
|
778
|
+
local_templates[sub_name] = sub_name
|
779
|
+
decl << "#{sub_name} = "
|
780
|
+
}
|
781
|
+
decl << "nil\n"
|
782
|
+
defs = []
|
783
|
+
subtemplates.each {|sub_name_args, sub_node|
|
784
|
+
defs << lambda {|out, context|
|
785
|
+
out.output_logic_line compile_local_template(sub_name_args, sub_node, local_templates)
|
786
|
+
}
|
787
|
+
}
|
788
|
+
TemplateNode.new(
|
789
|
+
lambda {|out, context| out.output_logic_line decl },
|
790
|
+
defs,
|
791
|
+
children.map {|n| compile_node(n, local_templates) }
|
792
|
+
)
|
793
|
+
end
|
794
|
+
when :text
|
795
|
+
_, expr = logic
|
796
|
+
TemplateNode.new(lambda {|out, context| out.output_dynamic_text expr })
|
797
|
+
when :tag
|
798
|
+
_, rest_logic = logic
|
799
|
+
if rest_logic == [:content] && node.empty_element?
|
800
|
+
node
|
801
|
+
else
|
802
|
+
subst = {}
|
803
|
+
node.children.each_index {|i| subst[i] = nil }
|
804
|
+
subst[0] = TemplateNode.new(generate_logic_node(rest_logic, node, local_templates))
|
805
|
+
node.subst_subnode(subst)
|
806
|
+
end
|
807
|
+
when :if
|
808
|
+
_, expr, then_logic, else_logic = logic
|
809
|
+
children = [
|
810
|
+
lambda {|out, context| out.output_logic_line "if (#{expr})" },
|
811
|
+
generate_logic_node(then_logic, node, local_templates)
|
812
|
+
]
|
813
|
+
if else_logic
|
814
|
+
children.concat [
|
815
|
+
lambda {|out, context| out.output_logic_line "else" },
|
816
|
+
generate_logic_node(else_logic, node, local_templates)
|
817
|
+
]
|
818
|
+
end
|
819
|
+
children <<
|
820
|
+
lambda {|out, context| out.output_logic_line "end" }
|
821
|
+
TemplateNode.new(*children)
|
822
|
+
when :iter
|
823
|
+
_, call, fargs, rest_logic = logic
|
824
|
+
TemplateNode.new(
|
825
|
+
lambda {|out, context| out.output_logic_line "#{call} {|#{fargs}|" },
|
826
|
+
generate_logic_node(rest_logic, node, local_templates),
|
827
|
+
lambda {|out, context| out.output_logic_line "}" }
|
828
|
+
)
|
829
|
+
when :call
|
830
|
+
_, recv, meth, args = logic
|
831
|
+
TemplateNode.new(
|
832
|
+
lambda {|out, context|
|
833
|
+
as = [out.outvar, ", "]
|
834
|
+
ns = context.namespaces.reject {|k, v| HTree::Context::DefaultNamespaces[k] == v }
|
835
|
+
if ns.empty?
|
836
|
+
as << out.contextvar
|
837
|
+
else
|
838
|
+
as << "#{out.contextvar}.subst_namespaces("
|
839
|
+
sep = ''
|
840
|
+
ns.each {|k, v|
|
841
|
+
as << sep << (k ? k.dump : "nil") << '=>' << v.dump
|
842
|
+
sep = ', '
|
843
|
+
}
|
844
|
+
as << ")"
|
845
|
+
end
|
846
|
+
unless args.empty?
|
847
|
+
as << ", " << args
|
848
|
+
end
|
849
|
+
if recv
|
850
|
+
out.output_logic_line "(#{recv})._ht_#{meth}(#{as.join('')})"
|
851
|
+
elsif local_templates.include? meth
|
852
|
+
out.output_logic_line "#{meth}.call(#{as.join('')})"
|
853
|
+
else
|
854
|
+
out.output_logic_line "_ht_#{meth}(#{as.join('')})"
|
855
|
+
end
|
856
|
+
}
|
857
|
+
)
|
858
|
+
else
|
859
|
+
raise Exception, "[bug] invalid logic: #{logic.inspect}"
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
class HTree::GenCode # :nodoc:
|
864
|
+
def output_dynamic_text(expr)
|
865
|
+
flush_buffer
|
866
|
+
@code << "#{@outvar}.output_dynamic_text((#{expr}))\n"
|
867
|
+
end
|
868
|
+
|
869
|
+
def output_dynamic_attvalue(expr)
|
870
|
+
flush_buffer
|
871
|
+
@code << "#{@outvar}.output_dynamic_attvalue((#{expr}))\n"
|
872
|
+
end
|
873
|
+
|
874
|
+
def output_logic_line(line)
|
875
|
+
flush_buffer
|
876
|
+
@code << line << "\n"
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
class TemplateNode # :nodoc:
|
881
|
+
include HTree::Node
|
882
|
+
|
883
|
+
def initialize(*children)
|
884
|
+
@children = children.flatten.compact
|
885
|
+
end
|
886
|
+
attr_reader :children
|
887
|
+
|
888
|
+
def output(out, context)
|
889
|
+
@children.each {|c|
|
890
|
+
if c.respond_to? :call
|
891
|
+
c.call(out, context)
|
892
|
+
else
|
893
|
+
c.output(out, context)
|
894
|
+
end
|
895
|
+
}
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
class TemplateAttrName < HTree::Name # :nodoc:
|
900
|
+
def output_attribute(text, out, context)
|
901
|
+
output(out, context)
|
902
|
+
out.output_string '="'
|
903
|
+
out.output_dynamic_attvalue(text.to_s)
|
904
|
+
out.output_string '"'
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
end
|
909
|
+
# :startdoc:
|