feedtools 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG +11 -0
  2. data/lib/feed_tools.rb +2496 -810
  3. data/lib/feed_tools/vendor/builder.rb +2 -0
  4. data/lib/feed_tools/vendor/builder/blankslate.rb +2 -0
  5. data/lib/feed_tools/vendor/builder/xmlbase.rb +2 -1
  6. data/lib/feed_tools/vendor/builder/xmlevents.rb +2 -0
  7. data/lib/feed_tools/vendor/builder/xmlmarkup.rb +4 -2
  8. data/lib/feed_tools/vendor/htree.rb +97 -0
  9. data/lib/feed_tools/vendor/htree/container.rb +10 -0
  10. data/lib/feed_tools/vendor/htree/context.rb +67 -0
  11. data/lib/feed_tools/vendor/htree/display.rb +27 -0
  12. data/lib/feed_tools/vendor/htree/doc.rb +149 -0
  13. data/lib/feed_tools/vendor/htree/elem.rb +262 -0
  14. data/lib/feed_tools/vendor/htree/encoder.rb +163 -0
  15. data/lib/feed_tools/vendor/htree/equality.rb +218 -0
  16. data/lib/feed_tools/vendor/htree/extract_text.rb +37 -0
  17. data/lib/feed_tools/vendor/htree/fstr.rb +33 -0
  18. data/lib/feed_tools/vendor/htree/gencode.rb +97 -0
  19. data/lib/feed_tools/vendor/htree/htmlinfo.rb +672 -0
  20. data/lib/feed_tools/vendor/htree/inspect.rb +108 -0
  21. data/lib/feed_tools/vendor/htree/leaf.rb +94 -0
  22. data/lib/feed_tools/vendor/htree/loc.rb +367 -0
  23. data/lib/feed_tools/vendor/htree/modules.rb +48 -0
  24. data/lib/feed_tools/vendor/htree/name.rb +124 -0
  25. data/lib/feed_tools/vendor/htree/output.rb +207 -0
  26. data/lib/feed_tools/vendor/htree/parse.rb +407 -0
  27. data/lib/feed_tools/vendor/htree/raw_string.rb +124 -0
  28. data/lib/feed_tools/vendor/htree/regexp-util.rb +15 -0
  29. data/lib/feed_tools/vendor/htree/rexml.rb +130 -0
  30. data/lib/feed_tools/vendor/htree/scan.rb +166 -0
  31. data/lib/feed_tools/vendor/htree/tag.rb +111 -0
  32. data/lib/feed_tools/vendor/htree/template.rb +909 -0
  33. data/lib/feed_tools/vendor/htree/text.rb +115 -0
  34. data/lib/feed_tools/vendor/htree/traverse.rb +465 -0
  35. data/rakefile +1 -1
  36. data/test/rss_test.rb +97 -0
  37. 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'"/>&#32;<span _text="'b'"/> -> "a&#32;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: