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.
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: