amrita2 1.9.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/README +112 -0
  2. data/init.rb +6 -0
  3. data/lib/amrita2/gettext.rb +116 -0
  4. data/lib/amrita2/macro.rb +153 -0
  5. data/lib/amrita2/rails_bridge.rb +172 -26
  6. data/lib/amrita2/template.rb +2634 -234
  7. data/lib/amrita2/testsupport.rb +171 -0
  8. data/lib/amrita2/version.rb +3 -3
  9. data/lib/amrita2.rb +1 -0
  10. data/sample/depot/app/controllers/admin_controller.rb +59 -0
  11. data/sample/depot/app/controllers/application.rb +20 -0
  12. data/sample/depot/app/controllers/info_controller.rb +19 -0
  13. data/sample/depot/app/controllers/login_controller.rb +85 -0
  14. data/sample/depot/app/controllers/store_controller.rb +68 -0
  15. data/sample/depot/app/helpers/admin_helper.rb +7 -0
  16. data/sample/depot/app/helpers/application_helper.rb +10 -0
  17. data/sample/depot/app/helpers/ar_form.rb +169 -0
  18. data/sample/depot/app/helpers/form_tag.rb +24 -0
  19. data/sample/depot/app/helpers/info_helper.rb +7 -0
  20. data/sample/depot/app/helpers/standard_form.rb +73 -0
  21. data/sample/depot/app/helpers/store_helper.rb +14 -0
  22. data/sample/depot/app/models/cart.rb +36 -0
  23. data/sample/depot/app/models/cart_item.rb +26 -0
  24. data/sample/depot/app/models/line_item.rb +34 -0
  25. data/sample/depot/app/models/order.rb +57 -0
  26. data/sample/depot/app/models/product.rb +41 -0
  27. data/sample/depot/app/models/user.rb +83 -0
  28. data/sample/depot/config/boot.rb +49 -0
  29. data/sample/depot/config/environment.rb +83 -0
  30. data/sample/depot/config/environments/development.rb +24 -0
  31. data/sample/depot/config/environments/production.rb +24 -0
  32. data/sample/depot/config/environments/test.rb +24 -0
  33. data/sample/depot/config/routes.rb +10 -0
  34. data/sample/depot/db/migrate/001_create_products.rb +18 -0
  35. data/sample/depot/db/migrate/002_add_price.rb +14 -0
  36. data/sample/depot/db/migrate/003_add_test_data.rb +68 -0
  37. data/sample/depot/db/migrate/004_add_sessions.rb +20 -0
  38. data/sample/depot/db/migrate/005_create_orders.rb +21 -0
  39. data/sample/depot/db/migrate/006_create_line_items.rb +27 -0
  40. data/sample/depot/db/migrate/007_create_users.rb +18 -0
  41. data/sample/depot/db/schema.rb +45 -0
  42. data/sample/depot/public/dispatch.rb +15 -0
  43. data/sample/depot/test/functional/admin_controller_test.rb +54 -0
  44. data/sample/depot/test/functional/info_controller_test.rb +23 -0
  45. data/sample/depot/test/functional/login_controller_test.rb +74 -0
  46. data/sample/depot/test/functional/store_controller_test.rb +57 -0
  47. data/sample/depot/test/integration/dsl_user_stories_test.rb +126 -0
  48. data/sample/depot/test/integration/user_stories_test.rb +70 -0
  49. data/sample/depot/test/performance/order_speed_test.rb +58 -0
  50. data/sample/depot/test/test_helper.rb +16 -0
  51. data/sample/depot/test/unit/cart_test.rb +39 -0
  52. data/sample/depot/test/unit/cart_test1.rb +31 -0
  53. data/sample/depot/test/unit/line_item_test.rb +15 -0
  54. data/sample/depot/test/unit/order_test.rb +15 -0
  55. data/sample/depot/test/unit/product_test.rb +98 -0
  56. data/sample/depot/vendor/plugins/amrita2/init.rb +6 -0
  57. data/sample/hello/hello.rb +22 -0
  58. data/sample/login_engine/app/controllers/application.rb +16 -0
  59. data/sample/login_engine/app/controllers/user_controller.rb +265 -0
  60. data/sample/login_engine/app/helpers/application_helper.rb +3 -0
  61. data/sample/login_engine/app/helpers/form_tag.rb +16 -0
  62. data/sample/login_engine/app/helpers/two_columns.rb +24 -0
  63. data/sample/login_engine/app/helpers/two_columns_form.rb +47 -0
  64. data/sample/login_engine/app/helpers/user_helper.rb +88 -0
  65. data/sample/login_engine/app/models/user.rb +7 -0
  66. data/sample/login_engine/app/models/user_notify.rb +75 -0
  67. data/sample/login_engine/config/boot.rb +45 -0
  68. data/sample/login_engine/config/environment.rb +140 -0
  69. data/sample/login_engine/config/environments/development.rb +21 -0
  70. data/sample/login_engine/config/environments/production.rb +18 -0
  71. data/sample/login_engine/config/environments/test.rb +19 -0
  72. data/sample/login_engine/config/routes.rb +23 -0
  73. data/sample/login_engine/db/migrate/001_create_users.rb +25 -0
  74. data/sample/login_engine/db/schema.rb +25 -0
  75. data/sample/login_engine/lib/config.rb +113 -0
  76. data/sample/login_engine/lib/hpricot_test_extension.rb +80 -0
  77. data/sample/login_engine/lib/login_engine/authenticated_system.rb +113 -0
  78. data/sample/login_engine/lib/login_engine/authenticated_user.rb +155 -0
  79. data/sample/login_engine/lib/login_engine.rb +62 -0
  80. data/sample/login_engine/public/dispatch.rb +10 -0
  81. data/sample/login_engine/test/functional/amrita2_test.rb +267 -0
  82. data/sample/login_engine/test/functional/user_controller_test.rb +544 -0
  83. data/sample/login_engine/test/mocks/mail.rb +14 -0
  84. data/sample/login_engine/test/mocks/time.rb +19 -0
  85. data/sample/login_engine/test/test_helper.rb +31 -0
  86. data/sample/login_engine/test/unit/user_test.rb +116 -0
  87. data/sample/login_engine/vendor/plugins/amrita2/init.rb +6 -0
  88. data/specs/attribute.rb +201 -0
  89. data/specs/datatypes.rb +231 -0
  90. data/specs/dictionary.rb +68 -0
  91. data/specs/erb_cdata.rb +187 -0
  92. data/specs/filters.rb +513 -0
  93. data/specs/gettext/erb_gettext.rb +42 -0
  94. data/specs/gettext/gettext_util.rb +65 -0
  95. data/specs/gettext/static_text.rb +103 -0
  96. data/specs/impl/code_generator.rb +87 -0
  97. data/specs/impl/dynamic_element.rb +92 -0
  98. data/specs/impl/hash_delegator.rb +57 -0
  99. data/specs/impl/parse_opt.rb +34 -0
  100. data/specs/impl/preprocess.rb +823 -0
  101. data/specs/impl/testsupport.rb +89 -0
  102. data/specs/inlineruby.rb +429 -0
  103. data/specs/intro.rb +654 -0
  104. data/specs/loop.rb +203 -0
  105. data/specs/macro.rb +532 -0
  106. data/specs/sample.rb +34 -0
  107. data/specs/sanitize.rb +110 -0
  108. data/specs/template.rb +189 -0
  109. data/specs/trace.rb +97 -0
  110. metadata +138 -19
  111. data/lib/amrita2/core.rb +0 -1897
  112. data/lib/amrita2/rd.rb +0 -314
@@ -1,304 +1,2704 @@
1
- require 'amrita2/core'
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'erb'
4
+ #require 'rexml/document'
5
+ require 'enumerator'
6
+
7
+
8
+ module Amrita2
9
+ module ScalarData
10
+ def amrita_value
11
+ Amrita2::Util::sanitize_text(to_s)
12
+ end
13
+ end
14
+
15
+ module DictionaryData
16
+ def amrita_value(name)
17
+ self.__send__(name)
18
+ end
19
+
20
+ def self.===(other)
21
+ if other == nil
22
+ true
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end
28
+
29
+ module Enum
30
+ end
31
+
32
+ module NullObject
33
+ def amrita_value(key=nil)
34
+ nil
35
+ end
36
+
37
+ def each
38
+ end
39
+ end
40
+
41
+ module ElementHelper
42
+ end
43
+ end
44
+
45
+ class NilClass
46
+ include Amrita2::NullObject
47
+ end
48
+
49
+ class FalseClass
50
+ include Amrita2::NullObject
51
+ end
52
+
53
+ class String
54
+ include Amrita2::ScalarData
55
+ def amrita_value
56
+ Amrita2::Util::sanitize_text(self)
57
+ end
58
+ end
59
+
60
+ class Integer
61
+ include Amrita2::ScalarData
62
+ end
63
+
64
+ class BicDecimal
65
+ include Amrita2::ScalarData
66
+ end
67
+
68
+ class Float
69
+ include Amrita2::ScalarData
70
+ end
71
+
72
+ class Time
73
+ include Amrita2::ScalarData
74
+ end
75
+
76
+ # class REXML::Element
77
+ # include Amrita2::ScalarData
78
+ # def amrita_value
79
+ # Amrita2::SanitizedString[to_s]
80
+ # end
81
+
82
+ # def as_amrita_dictionary(*args)
83
+ # Amrita2::Util::REXMLConverter.new(*args).convert(self)
84
+ # end
85
+ # end
86
+
87
+
88
+ class Array
89
+ include Amrita2::Enum
90
+ end
91
+
92
+ class Range
93
+ include Amrita2::Enum
94
+ end
95
+
96
+ class Hash
97
+ include Amrita2::DictionaryData
98
+ def amrita_value(name)
99
+ self[name]
100
+ end
101
+ end
102
+
103
+ class Struct
104
+ include Amrita2::DictionaryData
105
+ end
106
+
107
+ class Binding
108
+ include Amrita2::DictionaryData
109
+
110
+ def amrita_value(name)
111
+ eval "begin; #{name}; rescue(NameError); @#{name};end;", self
112
+ end
113
+ end
114
+
115
+ class Hpricot::Elem
116
+ include Amrita2::ElementHelper
117
+ end
2
118
 
3
119
  module Amrita2
4
- class Template
120
+ FilterMethods = [
121
+ :filter_element,
122
+ :parse_element,
123
+ :parse_node,
124
+ :setup_type_renderer,
125
+ :generate_dynamic_element,
126
+ :define_element_method,
127
+ :loop_check_code,
128
+ :method_body_code,
129
+ :value_filter_code,
130
+ :renderer_code,
131
+ :element_render_code
132
+ ]
133
+
134
+ module Core
135
+ end
136
+
137
+ module Util
138
+ end
139
+
140
+ module Runtime
5
141
  include Amrita2
142
+ include Util
143
+ end
144
+
145
+ module Filters
146
+ class Base
147
+ end
148
+ end
149
+
150
+ module Renderers #:nodoc: all
151
+ class Base
152
+ end
153
+ end
154
+
155
+ module Macro #:nodoc: all
156
+ module Standard
157
+ end
158
+ include Standard
159
+
160
+ class Base
161
+ end
162
+ end
163
+
164
+ module CompileTimeContext #:nodoc: all
165
+ include Filters
166
+ include Renderers
167
+ include Macro
168
+ include Standard
169
+ end
170
+
171
+ class TemplateError < RuntimeError
172
+ end
173
+
174
+ module Util # :nodoc: all
175
+
176
+ # Amrita2 sanitize anything except for SanitizedString
177
+ # If you want to sanitize yourself and don't want to Amrita2 sanitize your object,
178
+ # pass SanitizedString[x] as model data.
179
+ class SanitizedString < String
180
+ def SanitizedString::[](s)
181
+ new(s.to_s).freeze
182
+ end
183
+
184
+ def amrita_value
185
+ self
186
+ end
187
+
188
+ def to_s
189
+ self
190
+ end
191
+
192
+ def *(n)
193
+ SanitizedString[super]
194
+ end
195
+
196
+ def inspect
197
+ %[SanitizedString[#{super}]]
198
+ end
199
+ end
200
+
201
+ # This module provide methods for avoid XSS vulnerability
202
+ # taken from IPA home page(Japanese)
203
+ # http://www.ipa.go.jp/security/awareness/vendor/programming/a01_02.html
204
+ NAMECHAR = '[-\w\d\.:]'
205
+ NAME = "([\\w:]#{NAMECHAR}*)"
206
+ NOT_REFERENCE = "(?!#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)" # borrowed from rexml
207
+ AMP_WITHOUT_REFRENCE = /&#{NOT_REFERENCE}/
208
+ # escape &<>
209
+
210
+ def self.sanitize_text(text)
211
+ return nil unless text
212
+ s = text.dup
213
+ s.gsub!(AMP_WITHOUT_REFRENCE, '&amp;')
214
+ s.gsub!("<", '&lt;')
215
+ s.gsub!(">", '&gt;')
216
+ s
217
+ end
218
+
219
+ # escape &<>"'
220
+ def self.sanitize_attribute_value(text)
221
+ return nil unless text
222
+ s = text.dup
223
+ s.gsub!(AMP_WITHOUT_REFRENCE, '&amp;')
224
+ s.gsub!("<", '&lt;')
225
+ s.gsub!(">", '&gt;')
226
+ s.gsub!('"', '&quot;')
227
+ #s.gsub!("'", '&#39;')
228
+ s
229
+ end
230
+
231
+ DefaultAllowedScheme = {
232
+ 'http' => true,
233
+ 'https' => true,
234
+ 'ftp' => true,
235
+ 'mailto' => true,
236
+ }
237
+
238
+ #UrlInvalidChar = Regexp.new(%q|[^;/?:@&=+$,A-Za-z0-9\-_.!~*'()%]|)
239
+ UrlInvalidChar = Regexp.new(%q|[^;/?:@&=+$,A-Za-z0-9\-_.!~*'()%#]|) #'
240
+
241
+ # +sanitize_url+ accepts only these characters
242
+ # --- http://www.ietf.org/rfc/rfc2396.txt ---
243
+ # uric = reserved | unreserved | escaped
244
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
245
+ # unreserved = alphanum | mark
246
+ # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
247
+ # escaped = "%" hex hex
248
+ #
249
+ # +sanitize_url+ accepts only schems specified by +allowd_scheme+
250
+ #
251
+ # The default is http: https: ftp: mailt:
252
+
253
+ def self.sanitize_url(text, allowd_scheme = DefaultAllowedScheme)
254
+ # return nil if text has characters not allowd for URL
255
+
256
+ return nil if text =~ UrlInvalidChar
257
+
258
+ # return '' if text has an unknown scheme
259
+ # --- http://www.ietf.org/rfc/rfc2396.txt ---
260
+ # scheme = alpha *( alpha | digit | "+" | "-" | "." )
261
+
262
+ if text =~ %r|^([A-Za-z][A-Za-z0-9+\-.]*):|
263
+ return nil unless allowd_scheme[$1]
264
+ end
265
+
266
+ # escape HTML
267
+ # special = "&" | "<" | ">" | '"' | "'"
268
+ # But I checked "<" | ">" | '"' before.
269
+ s = text.dup
270
+ #s.gsub!("&", '&amp;')
271
+ s.gsub!("'", '&#39;')
272
+
273
+ s
274
+ end
275
+
276
+ module OptionSupport # :nodoc: all
277
+ attr_reader :opt
278
+ def parse_opt(*args)
279
+ @opt = {}
280
+ hash = args.last
281
+ if hash.kind_of?(Hash)
282
+ @opt.merge!(hash)
283
+ args.pop
284
+ end
285
+
286
+ args.each do |key|
287
+ @opt[key] = key
288
+ end
289
+ @opt
290
+ end
291
+
292
+ def [](key)
293
+ @opt[key]
294
+ end
295
+
296
+ def method_missing(sym, *args, &block)
297
+ if @opt and @opt.has_key?(sym)
298
+ @opt[sym]
299
+ else
300
+ super
301
+ end
302
+ end
303
+ end
304
+ class Option
305
+ include OptionSupport
306
+ def initialize(*args)
307
+ parse_opt(*args)
308
+ end
309
+ end
310
+ class Tuple < Array # :nodoc: all
311
+ include ScalarData
312
+ def self.[](*args)
313
+ self.new(args)
314
+ end
315
+ end
316
+
317
+ class REXMLConverter # :nodoc: all
318
+ include Amrita2
319
+ include Util::OptionSupport
320
+
321
+ def initialize(*args)
322
+ parse_opt(*args)
323
+ end
324
+
325
+ def convert(e)
326
+ @noattrs = select_elements(e, :noattrs)
327
+ @use_child_contents = select_elements(e, :use_child_contents)
328
+ @use_child = select_elements(e, :use_child)
329
+ convert1(e)
330
+ end
331
+
332
+ def convert1(e)
333
+ h = {}
334
+ if use_attr?(e)
335
+ e.attributes.each do |key, value|
336
+ h[key.intern] = value
337
+ end
338
+ end
339
+
340
+ ctag = get_contents_tag(e)
341
+ if ctag
342
+ if e.elements.size > 0
343
+ s = e.collect { |cc| cc.to_s }
344
+ h[ctag] = SanitizedString[s]
345
+ else
346
+ h[ctag] = e.text
347
+ end
348
+ end
349
+
350
+ e.each_element do |c|
351
+ if use_child_contents?(c)
352
+ if c.elements.size > 0
353
+ s = c.collect { |cc| cc.to_s }
354
+ h[c.name.intern] = SanitizedString[s]
355
+ else
356
+ h[c.name.intern] = c.text
357
+ end
358
+ end
359
+
360
+ if use_child?(c)
361
+ ch = convert1(c)
362
+ case h[c.name.intern]
363
+ when nil
364
+ h.merge!(ch)
365
+ when Array
366
+ h[c.name.intern] << ch[c.name.intern]
367
+ else
368
+ h[c.name.intern] = [h[c.name.intern], ch[c.name.intern]]
369
+ end
370
+ end
371
+ end
372
+ if @opt[:use_tag]
373
+ { e.name.intern => h }
374
+ else
375
+ h
376
+ end
377
+ end
378
+
379
+ private
380
+
381
+ def select_elements(e, key)
382
+ case xpath = opt[key]
383
+ when String
384
+ REXML::XPath.match(e, xpath)
385
+ when nil,false
386
+ return []
387
+ else
388
+ REXML::XPath.match(e, "//*")
389
+ end
390
+ end
391
+
392
+ def get_contents_tag(e)
393
+ case o = opt[:use_contents]
394
+ when true, :use_contents
395
+ :contents
396
+ when Symbol
397
+ o
398
+ when nil
399
+ nil
400
+ else
401
+ raise "not implemented"
402
+ end
403
+ end
404
+
405
+ def use_attr?(e)
406
+ not @noattrs.include?(e)
407
+ end
408
+
409
+ def use_child_contents?(e)
410
+ @use_child_contents.include?(e)
411
+ end
412
+
413
+ def use_child?(e)
414
+ @use_child.include?(e)
415
+ end
416
+ end
417
+ end
418
+
419
+ module Runtime # :nodoc: all
420
+ def amrita_set_context_value(v)
421
+ Thread::current[:amrita_context_value] = v
422
+ end
423
+
424
+ def amrita_get_context_value
425
+ Thread::current[:amrita_context_value]
426
+ end
427
+
428
+ def new_element(tag, attrs={}, &block)
429
+ a = {}
430
+ attrs.each { |k,v| a[k.to_s] = v }
431
+ ret = Hpricot::Elem.new(Hpricot::STag.new(tag.to_s, a))
432
+ ret.instance_eval(&block) if block_given?
433
+ ret
434
+ end
435
+
436
+ def start_tag(e, out="")
437
+ out << "<#{e.stag.name}"
438
+ e.attributes.each do |k, v|
439
+ next unless v
440
+ out << " "
441
+ out << "#{k}=\"#{Util::sanitize_attribute_value(v.to_s)}\""
442
+ #attr.write(out)
443
+ end unless e.attributes.empty?
444
+ out << ">"
445
+ out
446
+ end
447
+
448
+ def end_tag(e, out="")
449
+ out << "</#{e.stag.name}>"
450
+ out
451
+ end
452
+ end
453
+
454
+ module ElementHelper # :nodoc: all
455
+ def elements
456
+ children.select do |c|
457
+ c.kind_of?(Hpricot::Elem)
458
+ end
459
+ end
460
+
461
+ def contents
462
+ if children.size > 0
463
+ SanitizedString[children.to_s]
464
+ else
465
+ nil
466
+ end
467
+ end
468
+
469
+ def set_attribute(key, val)
470
+ case val
471
+ when nil, ""
472
+ delete_attribute(key)
473
+ else
474
+ super
475
+ end
476
+ end
477
+
478
+ def delete_attribute(key)
479
+ raw_attributes.delete(key)
480
+ end
481
+
482
+ def add(elem)
483
+ insert_after(elem, nil)
484
+ end
485
+
486
+ def add_text(text)
487
+ insert_after(Hpricot::Text.new(text), nil)
488
+ end
489
+
490
+ def as_amrita_dictionary(opt={})
491
+ ret = {}
492
+ attributes.each do |k, v|
493
+ ret[k.intern] = v
494
+ end
495
+ if opt[:use_contents]
496
+ ret[opt[:use_contents]] =
497
+ Amrita2::SanitizedString[
498
+ children.collect do |c|
499
+ c.to_s
500
+ end.join
501
+ ]
502
+ end
503
+
504
+ if opt[:use_tag]
505
+ { name.intern => ret }
506
+ else
507
+ ret
508
+ end
509
+ end
510
+ end
511
+
512
+ module Core
513
+ class CodeGenerator # :nodoc: all
514
+ def initialize
515
+ @iehack = true
516
+ @lines = []
517
+ @indent = 0
518
+ init_strbuf
519
+ @module_stack = [CGModule.new('')]
520
+ @current_stream = "__stream__"
521
+ end
522
+
523
+ def init_strbuf
524
+ @strbuf = ""
525
+ end
526
+
527
+ def code(line)
528
+ flush
529
+ @lines << [@indent, line]
530
+ end
531
+
532
+ def comment(comments)
533
+ comments.each do |c|
534
+ @lines << [@indent, "# #{c}"]
535
+ end
536
+ end
537
+
538
+ def result
539
+ flush
540
+ m = @module_stack.shift
541
+ if m
542
+ m.constants.each do |name, defs|
543
+ code("#{name} = #{defs}")
544
+ end
545
+ end
546
+ @lines.collect do |indent, l|
547
+ " " * indent + l
548
+ end.join("\n")
549
+ end
550
+
551
+ def put_constant(c)
552
+ @strbuf.concat c.to_s
553
+ end
554
+
555
+ def flush
556
+ if @strbuf.size > 0
557
+ @lines << [@indent, "#{@current_stream}.concat(#{@strbuf.inspect})"]
558
+ init_strbuf
559
+ end
560
+ end
561
+
562
+ def put_hpricot_node(node)
563
+ s = ""
564
+ node.output(s)
565
+ put_constant s
566
+ end
567
+
568
+ def level_up
569
+ @indent += 1
570
+ yield
571
+ flush
572
+ @indent -= 1
573
+ end
574
+
575
+ def if_(cond, &block)
576
+ code("if #{cond}")
577
+ level_up do
578
+ yield
579
+ end
580
+ code("end")
581
+ end
582
+
583
+ def case_(obj, &block)
584
+ code("case #{obj}")
585
+ yield
586
+ code("end")
587
+ end
588
+
589
+ def when_(obj, &block)
590
+ code("when #{obj}")
591
+ level_up do
592
+ yield
593
+ end
594
+ end
595
+
596
+ def else_(&block)
597
+ code("else")
598
+ level_up do
599
+ yield
600
+ end
601
+ end
602
+
603
+ def with_stream(new_stream_name=nil)
604
+ if new_stream_name
605
+ old_stream = @current_stream
606
+ code("before_#{new_stream_name} = #@current_stream")
607
+ @current_stream = new_stream_name.to_s
608
+ end
609
+ code("#@current_stream = '' ")
610
+ yield
611
+ flush
612
+ ensure
613
+ if new_stream_name
614
+ @current_stream = old_stream
615
+ code %[Thread::current[:amrita_stream] ||= {} ]
616
+ code %[Thread::current[:amrita_stream][#{new_stream_name.inspect}] = #{new_stream_name}]
617
+ code("#@current_stream = before_#{new_stream_name}")
618
+ end
619
+ code("#@current_stream")
620
+ end
621
+
622
+ def put_stream(name)
623
+ code %[#@current_stream.concat(Thread::current[:amrita_stream][#{name.inspect}])]
624
+ end
625
+
626
+ def def_method(name, *args)
627
+ if args.size > 0
628
+ code("def #{name}(#{args.join(',')})")
629
+ else
630
+ code("def #{name}")
631
+ end
632
+ level_up do
633
+ with_stream do
634
+ yield
635
+ flush
636
+ end
637
+ end
638
+ code("end")
639
+ end
640
+
641
+ def put_static_element(e)
642
+ attr = e.attributes.collect do |name, value|
643
+ "#{name} = #{value.inspect}"
644
+ end
645
+ if attr.size > 0
646
+ put_constant("<#{e.name} #{attr.join(' ')}>")
647
+ else
648
+ put_constant("<#{e.name}>")
649
+ end
650
+ yield
651
+ put_constant("</#{e.name}>")
652
+ end
653
+
654
+ def put_expression(exp)
655
+ code("#{@current_stream}.concat((#{exp}).to_s)")
656
+ end
657
+
658
+ def put_string_expression(exp)
659
+ code("#{@current_stream}.concat(#{exp})")
660
+ end
661
+
662
+ def define_class(name, &block)
663
+ define_module_or_class("class", name, &block)
664
+ end
665
+
666
+ def define_module_or_class(typ, name, &block)
667
+ @module_stack.unshift CGModule.new(name)
668
+ code("#{typ} #{name}")
669
+ level_up do
670
+ yield
671
+ m = @module_stack.shift
672
+ m.end_of_module(self)
673
+ end
674
+ code("end")
675
+ end
676
+
677
+ def define_constant(const_def)
678
+ @module_stack.first.define_constant(const_def)
679
+ end
680
+
681
+ def eval(src)
682
+ code "eval(#{src}, __binding__)"
683
+ end
684
+
685
+ def eval_and_print(src)
686
+ put_string_expression "eval(#{src}, __binding__)"
687
+ end
688
+
689
+ class CGModule # :nodoc:
690
+ attr_reader :name
691
+ attr_reader :constants
692
+ attr_reader :methods
693
+
694
+ def initialize(name)
695
+ @name = name
696
+ @constants = []
697
+ @methods = []
698
+ end
699
+
700
+ def define_constant(const_def)
701
+ name = nil
702
+ @constants.each do |n, cdef|
703
+ if cdef == const_def
704
+ name = n
705
+ break
706
+ end
707
+ end
708
+ unless name
709
+ name = "C#{sprintf("%03d", @constants.size)}"
710
+ @constants << [name, const_def]
711
+ end
712
+ name
713
+ end
714
+
715
+ def define_method(vars, &block)
716
+ name = "m#{sprintf("%03d", @methods.size)}"
717
+ @methods << [name, vars, block]
718
+ name
719
+ end
720
+
721
+ def end_of_module(cg)
722
+ @constants.each do |name, defs|
723
+ cg.code("#{name} = #{defs}")
724
+ end
725
+ @methods.each do |name, vars, body|
726
+ cg.define_method(name, vars) do
727
+ body.call
728
+ end
729
+ end
730
+ end
731
+ end
732
+ end
733
+
734
+ class BaseNode # :nodoc: all
735
+ attr_reader :parent
736
+
737
+ def initialize(parent)
738
+ @parent = parent
739
+ end
740
+
741
+ def render_me(cg)
742
+ end
743
+
744
+ def module_src(cg)
745
+ end
746
+
747
+ def root
748
+ parent.root
749
+ end
750
+
751
+ def parent_de
752
+ parent
753
+ end
754
+
755
+ def dynamic?
756
+ false
757
+ end
758
+
759
+ def has_ruby?
760
+ false
761
+ end
762
+ end
763
+
764
+ class StaticNode < BaseNode # :nodoc: all
765
+ def initialize(parent, node)
766
+ super(parent)
767
+ @node = node
768
+ end
769
+
770
+ def render_me(cg)
771
+ #cg.put_string_expression(cg.define_constant(@node.to_s.inspect))
772
+
773
+ # to keep &nbsp;
774
+ s = ""
775
+ @node.output(s, :preserve=>true)
776
+ cg.put_string_expression(cg.define_constant(s.inspect))
777
+ end
778
+ end
779
+
780
+ class CommentNode < BaseNode # :nodoc: all
781
+ def initialize(parent, node)
782
+ super(parent)
783
+ @node = node
784
+ end
785
+
786
+ def render_me(cg)
787
+ #cg.put_string_expression(cg.define_constant(@node.to_s.inspect))
788
+ # to keep &nbsp;
789
+ s = ""
790
+ @node.output(s, :preserve=>true)
791
+ cg.put_string_expression(cg.define_constant(s.inspect))
792
+ end
793
+ end
794
+
795
+ class ErbNode < BaseNode # :nodoc: all
796
+ include Util
797
+ attr_reader :node
798
+ def initialize(parent, node)
799
+ super(parent)
800
+ @node = node
801
+ @erb_trim_mode = nil
802
+ end
803
+
804
+ def dynamic?
805
+ true
806
+ end
807
+
808
+ def render_me_old(cg)
809
+ src = @node.inner_text
810
+ src.each do |s|
811
+ cg.code("# #{s.chomp}")
812
+ end
813
+ erb = cg.define_constant %[ERB.new(#{with_context_value_erb(src).inspect}, nil, #{@erb_trim_mode.inspect})]
814
+ cg.code("amrita_set_context_value($_)")
815
+ cg.put_string_expression %[#{erb}.result(__binding__)]
816
+ cg.code("$_ = amrita_get_context_value")
817
+ end
818
+
819
+ def render_me(cg)
820
+ src = @node.inner_text
821
+ src.each do |s|
822
+ cg.code("# #{s.chomp}")
823
+ end
824
+ erb = cg.define_constant %[ERB.new(#{with_context_value_erb(src).inspect}, nil, #{@erb_trim_mode.inspect}).src]
825
+ cg.code("amrita_set_context_value($_)")
826
+ cg.eval_and_print(erb)
827
+ cg.code("$_ = amrita_get_context_value")
828
+ end
829
+
830
+ def has_ruby?
831
+ true
832
+ end
833
+
834
+ private
835
+ def with_context_value_erb(src)
836
+ [
837
+ "<% $_ = amrita_get_context_value %>",
838
+ src,
839
+ "<% amrita_set_context_value($_) %>",
840
+ ].join("")
841
+ end
842
+ end
843
+
844
+ class ParentNode < BaseNode # :nodoc: all
845
+ attr_reader :element, :parent, :children
846
+ def initialize(parent, element)
847
+ super(parent)
848
+ @element = element
849
+ @children = parent_de.parse_element(@element)
850
+ end
851
+
852
+ def dynamic?
853
+ children.any? { |c| c.dynamic? }
854
+ end
855
+
856
+ def has_dynamic?
857
+ children.any? { |c| c.dynamic? }
858
+ end
859
+
860
+ def has_ruby?
861
+ children.any? { |c| c.has_ruby? }
862
+ end
863
+
864
+ def each(&block)
865
+ children.each(&block)
866
+ end
867
+
868
+ def module_src(cg)
869
+ each do |c|
870
+ c.module_src(cg)
871
+ end
872
+ end
873
+
874
+ end
875
+
876
+ class CompoundElement < ParentNode # :nodoc: all
877
+ def render_me(cg)
878
+ if @element.empty?
879
+ cg.put_constant(@element.to_s)
880
+ else
881
+ @parent.element_render_code(cg, @element) do
882
+ each do |c|
883
+ c.render_me(cg)
884
+ end
885
+ end
886
+ end
887
+ end
888
+ end
889
+
890
+ class DynamicElement < ParentNode # :nodoc: all
891
+ include Util
892
+ include OptionSupport
893
+
894
+ attr_reader :parent, :name, :children, :filter, :renderers, :element, :attrs
895
+ def self.delegate_to_filter(name)
896
+ module_eval <<-END
897
+ def #{name}(*args,&block)
898
+ @filter.#{name}(self, *args,&block)
899
+ end
900
+ END
901
+ end
902
+
903
+ FilterMethods.each do |m|
904
+ delegate_to_filter(m)
905
+ end
906
+
907
+ def initialize(parent, name, element, filters=[])
908
+ @parent,@name,@element = parent, name, element
909
+ parse_opt(root.opt)
910
+ @attrs = {}
911
+ delete_am_attrs
912
+ setup_filter(filters)
913
+ @element = filter_element(@element)
914
+ @renderers = []
915
+ super(parent, element)
916
+
917
+ setup_type_renderer
918
+ end
919
+
920
+ def [](key)
921
+ @attrs[key]
922
+ end
923
+
924
+ def parent_de
925
+ self
926
+ end
927
+
928
+ def dynamic?
929
+ true
930
+ end
931
+
932
+ def has_ruby?
933
+ super or am_for_value or am_skipif_value or am_v_value
934
+ end
935
+
936
+ def setup_filter(filters)
937
+ #default_filter = @parent ? @parent.filter : DefaultFilter.new
938
+ default_filter = DefaultFilter.new
939
+ default_filter.parse_opt(opt)
940
+ if filters.size > 0
941
+ @filter = current = filters.first
942
+ for f in filters do
943
+ current.next_ = f
944
+ current = f
945
+ end
946
+ current.next_ = default_filter
947
+ else
948
+ @filter = default_filter
949
+ end
950
+ end
951
+
952
+ def class_name
953
+ if name
954
+ "XX" + name.capitalize
955
+ else
956
+ "XX%d" % [object_id.abs]
957
+ end
958
+ end
959
+
960
+ def instance_name
961
+ class_name + "Instance"
962
+ end
963
+
964
+ def compile(cg = CodeGenerator.new)
965
+ define_element_method(cg) do
966
+ cg.code("$_ = value")
967
+ loop_check_code(cg)
968
+ method_body_code(cg)
969
+ end
970
+ end
971
+
972
+ def render_me(cg, n = name)
973
+ if (n)
974
+ cg.put_string_expression("#{instance_name}.render_with($_.amrita_value(:#{n}), __binding__)")
975
+ else
976
+ cg.put_string_expression("#{instance_name}.render_with($_, __binding__)")
977
+ end
978
+ end
979
+
980
+ def module_src(cg)
981
+ cg.define_class(class_name) do
982
+ cg.code <<-END
983
+ include Amrita2
984
+ include Amrita2::Runtime
985
+ END
986
+ compile(cg)
987
+ super
988
+ end
989
+ cg.code("#{instance_name} = #{class_name}.new")
990
+ end
991
+
992
+ def am_for_value
993
+ @attrs[am_for.intern]
994
+ end
995
+
996
+ def am_skipif_value
997
+ @attrs[am_skipif.intern]
998
+ end
999
+
1000
+ def am_v_value
1001
+ @attrs[am_v.intern]
1002
+ end
1003
+
1004
+ private
1005
+
1006
+ def delete_am_attrs
1007
+ [
1008
+ am_src,
1009
+ am_filter,
1010
+ am_skipif,
1011
+ am_for,
1012
+ am_v,
1013
+ ].each do |key_s|
1014
+ key = key_s.intern
1015
+ @attrs[key] = @element.attributes[key_s]
1016
+ @element.delete_attribute(key_s)
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ class RootElement < DynamicElement # :nodoc: all
1022
+ include Filters
1023
+
1024
+ attr_reader :xml_decl, :doctype
1025
+
1026
+ def initialize(elements, opt={}, &block)
1027
+ @xml_decl = @doctype = nil
1028
+ parse_opt(opt)
1029
+ if block_given?
1030
+ @filter_proc = block
1031
+ else
1032
+ @filter_proc = proc do |e, src, filters|
1033
+ end
1034
+ end
1035
+ e = Hpricot.make("<root />", :xml=>true).first
1036
+ elements.each do |c|
1037
+ case c
1038
+ when Hpricot::Elem, Hpricot::Text, Hpricot::Comment, Hpricot::BogusETag
1039
+ e.insert_after(c, nil)
1040
+ when Hpricot::XMLDecl
1041
+ @xml_decl = c
1042
+ when Hpricot::DocType
1043
+ @doctype = c
1044
+ else
1045
+ raise "#{c.class}"
1046
+ end
1047
+ end
1048
+ filters = []
1049
+ @filter_proc.call(e,nil, filters)
1050
+ super(nil, nil, e, filters)
1051
+ end
1052
+
1053
+ def root
1054
+ self
1055
+ end
1056
+
1057
+ def compile(cg)
1058
+ cg.code <<-END
1059
+ include Amrita2
1060
+ include Amrita2::Runtime
1061
+ END
1062
+ super(cg)
1063
+ @children.each do |c|
1064
+ c.module_src(cg)
1065
+ end
1066
+ end
1067
+
1068
+ def compile_filters(element, name, *args)
1069
+ src = args.compact.join('|')
1070
+ #filters = src.split('|').collect { |f| parse_filter(f) }
1071
+ filters = parse_filter(src)
1072
+ @filter_proc.call(element, name, filters)
1073
+ filters
1074
+ end
1075
+
1076
+ def parse_filter(src)
1077
+ src.gsub!("\n", " ")
1078
+ case src
1079
+ when "",nil
1080
+ []
1081
+ else
1082
+ case ret = eval(src, @opt[:compiletime_binding])
1083
+ when Class
1084
+ [ret.new]
1085
+ when Module
1086
+ [ModuleExtendFilter[ret]]
1087
+ when Filters::Base
1088
+ [ret]
1089
+ when Array
1090
+ case ret.first
1091
+ when Symbol
1092
+ [FunctionFilter[*ret]]
1093
+ else
1094
+ ret
1095
+ end
1096
+ when Symbol
1097
+ [FunctionFilter[ret]]
1098
+ when nil
1099
+ []
1100
+ else
1101
+ raise TemplateError, "unknown Filter type #{ret.class}"
1102
+ end
1103
+ end
1104
+ rescue ScriptError, NameError
1105
+ raise TemplateError, "error in filters #{src.inspect} #$!"
1106
+ end
1107
+ end
1108
+
1109
+ class DefaultFilter < Filters::Base
1110
+ include Renderers
1111
+ include Filters
1112
+ include Util::OptionSupport
1113
+
1114
+ def filter_element(de, element)
1115
+ element
1116
+ end
1117
+
1118
+ def parse_element(de, element)
1119
+ element.enum_for(:each_child).collect do |node|
1120
+ de.parse_node(node)
1121
+ end
1122
+ end
1123
+
1124
+ def parse_node(de, node)
1125
+ case node
1126
+ when Hpricot::Elem
1127
+ de.generate_dynamic_element(node) || CompoundElement.new(de, node)
1128
+ when Hpricot::CData
1129
+ ErbNode.new(de, node)
1130
+ when Hpricot::Text
1131
+ StaticNode.new(de, node)
1132
+ when Hpricot::Comment
1133
+ CommentNode.new(de, node)
1134
+ when Hpricot::BogusETag
1135
+ StaticNode.new(de, node)
1136
+ else
1137
+ raise "not implemented #{node.class}"
1138
+ end
1139
+ end
1140
+
1141
+ def generate_dynamic_element(de, e)
1142
+ src = e.attributes[de.am_src]
1143
+ filters_src = e.attributes[de.am_filter]
1144
+ if src
1145
+ name, fs = src.split('|', 2)
1146
+ name.strip!
1147
+ filters = de.root.compile_filters(e, name, fs, filters_src)
1148
+ DynamicElement.new(de, name , e, filters)
1149
+ elsif filters_src
1150
+ filters = de.root.compile_filters(e, nil, filters_src)
1151
+ DynamicElement.new(de, nil , e, filters)
1152
+ else
1153
+ nil
1154
+ end
1155
+ end
1156
+
1157
+ def setup_type_renderer(de)
1158
+ if de.has_dynamic?
1159
+ de.renderers << HashRenderer.new unless de.renderers.find {|r| r.kind_of?(HashRenderer) }
1160
+ else
1161
+ de.renderers << ScalarRenderer.new
1162
+ end
1163
+ de.renderers << ElseRenderer.new
1164
+ end
1165
+
1166
+ def define_element_method(de, cg, &block)
1167
+ cg.def_method("render_with", "value", "__binding__", "__cnt__ = -1 ", &block)
1168
+ end
1169
+
1170
+ def loop_check_code(de, cg)
1171
+ cg.if_ "$_.kind_of?(Amrita2::Enum) and not $_.kind_of?(ScalarData)" do
1172
+ cg.code("$_.each_with_index do |v, i| ")
1173
+ cg.level_up do
1174
+ cg.put_string_expression("render_with(v, __binding__, i) ")
1175
+ end
1176
+ cg.code("end")
1177
+ cg.code("return __stream__")
1178
+ end
1179
+ end
1180
+
1181
+ def method_body_code(de, cg)
1182
+ de.value_filter_code(cg, "value")
1183
+ #cg.code("p #{de.element.name.inspect}, $_, stream")
1184
+ de.renderer_code(cg, de.element)
1185
+ #cg.code("p #{de.element.name.inspect}, $_, stream")
1186
+ end
1187
+
1188
+ def value_filter_code(de, cg, value_name)
1189
+ end
1190
+
1191
+ def renderer_code(de, cg, element)
1192
+ r = de.renderers
1193
+ case r.size
1194
+ when 0
1195
+ when 1
1196
+ r.first.generate_body(cg, de, element)
1197
+ else
1198
+ cg.case_("$_") do
1199
+ r.each do |r1|
1200
+ r1.generate_when(cg) do
1201
+ r1.generate_body(cg, de, element)
1202
+ end
1203
+ end
1204
+ end
1205
+ end
1206
+ end
1207
+
1208
+ def element_render_code(de, cg, element, &block)
1209
+ if ((element.name == 'span' or element.name == '_' or element.name == 'root') and element.attributes.size == 0)
1210
+ yield
1211
+ else
1212
+ cg.put_static_element(element, &block)
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ #
1218
+ # = using Amrita2::Template
1219
+ #
1220
+ # require "amrita2/template"
1221
+ # include Amrita2
1222
+ # tmpl = Template.new <<-END
1223
+ # <<html<
1224
+ # <<body<
1225
+ # <<h1 :title>>
1226
+ # <<p :body<
1227
+ # <<:template>> is a html template libraly for <<:lang>>
1228
+ # END
1229
+ #
1230
+ # puts tmpl.render_with(:title=>"hello world", :body=>{ :template=>"Amrita2", :lang=>"Ruby" })
1231
+
1232
+ class Template
1233
+ include Amrita2
1234
+ include Util
1235
+ include OptionSupport
1236
+ include Filters
1237
+ include CompileTimeContext
1238
+ include Runtime
1239
+
1240
+ # Specify attribute prefix for dynamic element. Defaults to 'am:'.
1241
+ attr_accessor :amrita_prefix
1242
+ # Specify weather use inline ruby. Defaults to +true+
1243
+ attr_accessor :inline_ruby
1244
+
1245
+ attr_reader :tracer
1246
+ attr_reader :root
1247
+
1248
+ # Initialize Template object using +text+ and +opts+.
1249
+ # Optionaly specify block for setting filters.
1250
+ #
1251
+ # t = Amrita2::Template.new(text) do |e, src, filters|
1252
+ # if src == "aaa"
1253
+ # filters << Amrita2::Filters::Default[456]
1254
+ # end
1255
+ # end
1256
+
1257
+ def initialize(text, *opts, &block)
1258
+ parse_opt(*opts)
1259
+ @text = text.dup
1260
+ @setuped = false
1261
+ @filter_proc = block
1262
+ @text_domain = nil
1263
+ @inline_ruby = true
1264
+ @amrita_prefix = @opt[:amrita_prefix] || "am:"
1265
+ end
1266
+
1267
+ def amrita_src=(v)
1268
+ @opt[:amrita_src] = v
1269
+ end
1270
+
1271
+ def amrita_filter=(v)
1272
+ @opt[:amrita_filter] = v
1273
+ end
1274
+
1275
+ def compiletime_binding=(b)
1276
+ @opt[:compiletime_binding] = b
1277
+ end
1278
+
1279
+ # Print compiled ruby code and runtime trace to
1280
+ # +io_or_type+
1281
+ #
1282
+ # tmpl = Template.new "...."
1283
+ # tmpl.set_trace(STDOUT)
1284
+ # puts tmpl.render_with(...)
1285
+
1286
+ def set_trace(io_or_type, &block)
1287
+ @tracer = Tracer.new(io_or_type, &block)
1288
+ end
1289
+
1290
+ def setup
1291
+ setup_opt
1292
+ @preprocessor = PreProcessor.new(@opt)
1293
+ cg = CodeGenerator.new
1294
+ preprocessed_text = @preprocessor.process(@text)
1295
+
1296
+ if bc = @preprocessor.sections[:BeforeCompile]
1297
+ self.instance_eval do
1298
+ eval bc
1299
+ end
1300
+ end
1301
+
1302
+ filter_setup do |e, src, filters|
1303
+ filters.unshift Trace.new if @tracer and @tracer.auto_trace?
1304
+ filters.unshift InlineRuby.new if @inline_ruby
1305
+ end
1306
+
1307
+ compile(cg, preprocessed_text)
1308
+ @setuped = true
1309
+ cg.result
1310
+ ensure
1311
+ @tracer.code(cg) if @tracer and @tracer.code_trace?
1312
+ end
1313
+
1314
+ # render template with +value+ and +binding+.
1315
+ # use TOPLEVEL_BINDING if +binding+ is nil.
1316
+ #
1317
+ def render_with(value, binding_=nil)
1318
+ setup unless @setuped
1319
+ unless binding_
1320
+ if value.kind_of?(Binding)
1321
+ binding_ = value
1322
+ else
1323
+ binding_ = binding
1324
+ end
1325
+ end
1326
+
1327
+ ret = nil
1328
+
1329
+ if @opt[:process_xhtml]
1330
+ ret = ""
1331
+ ret << @root.xml_decl.to_s << "\n" if @root.xml_decl
1332
+ ret << @root.doctype.to_s << "\n" if @root.doctype
1333
+ ret << @cls.new.render_with(value, binding_)
1334
+ else
1335
+ ret = @cls.new.render_with(value, binding_)
1336
+ end
1337
+ SanitizedString[ret]
1338
+ end
1339
+
1340
+ private
1341
+ def setup_opt
1342
+ @opt[:am_src] = @opt[:amrita_src] || @amrita_prefix + "src"
1343
+ @opt[:am_filter] = @opt[:amrita_filter] || @amrita_prefix + "filter"
1344
+ @opt[:am_skipif] = @opt[:amrita_skipif] || @amrita_prefix + "skipif"
1345
+ @opt[:am_for] = @opt[:amrita_for] || @amrita_prefix + "for"
1346
+ @opt[:am_v] = @opt[:amrita_v] || @amrita_prefix + "v"
1347
+ @opt[:tracer] = @tracer
1348
+ @opt[:compiletime_binding] = @opt[:compiletime_binding] || binding
1349
+ end
1350
+
1351
+ def compile(cg, text)
1352
+ @elements = Hpricot.make(text, :xml=>true)
1353
+ @root = RootElement.new @elements, opt, &@filter_proc
1354
+ @root.text_domain = @text_domain if @text_domain
1355
+ @root.compile(cg)
1356
+
1357
+ @cls = Class.new
1358
+
1359
+ ##############################################################################
1360
+ # very dirty hack
1361
+ # ruby-gettext does not work well with class without a name
1362
+ #
1363
+ #const_name = "T%08x" % Thread.current.object_id
1364
+ const_name = "T%08x" % @cls.object_id
1365
+ Amrita2.module_eval { remove_const(const_name) } if Amrita2.const_defined?(const_name)
1366
+ Amrita2.const_set(const_name, @cls)
1367
+ # This should be modified later
1368
+ ##############################################################################
1369
+
1370
+ @cls.module_eval cg.result
1371
+ @cls.const_set(:Tracer, @tracer) if @tracer
1372
+ rescue SyntaxError
1373
+ raise RuntimeError,$!
1374
+ end
1375
+
1376
+ def setup_filter_proc
1377
+ old_proc = @filter_proc
1378
+ @filter_proc = proc do |e, name ,filters|
1379
+ filters.unshift Trace.new if @tracer and @tracer.auto_trace?
1380
+ filters.unshift InlineRuby.new if @inline_ruby
1381
+ old_proc.call(e, name ,filters) if old_proc
1382
+ end
1383
+ end
1384
+
1385
+ # ment to be used in macro.rb and BeforeCompile section
1386
+ def filter_setup(&block)
1387
+ old_proc = @filter_proc
1388
+ @filter_proc = proc do |e, name ,filters|
1389
+ block.call(e, name, filters)
1390
+ old_proc.call(e, name, filters) if old_proc
1391
+ end
1392
+ end
1393
+
1394
+ class Tracer
1395
+ def initialize(io_or_type, &block)
1396
+ @auto_trace = false
1397
+ @trace_proc = block
1398
+ case io_or_type
1399
+ when :code, :element, :all
1400
+ @type = io_or_type
1401
+ raise "need block" unless @trace_proc
1402
+ when :all_element
1403
+ @type = :element
1404
+ @auto_trace = true
1405
+ raise "need block" unless @trace_proc
1406
+ else
1407
+ if io_or_type.respond_to?(:<<)
1408
+ @type = :all
1409
+ @auto_trace = true
1410
+ @trace_proc = proc do |msg|
1411
+ io_or_type << msg << "\n"
1412
+ end
1413
+ else
1414
+ raise "unknown type for trace #{io_or_type.inspect}"
1415
+ end
1416
+ end
1417
+ end
1418
+
1419
+ def auto_trace?
1420
+ @auto_trace
1421
+ end
1422
+
1423
+ def code_trace?
1424
+ @type == :code or @type == :all
1425
+ end
1426
+
1427
+ def code(cg)
1428
+ @trace_proc.call(cg.result) if code_trace?
1429
+ end
1430
+
1431
+ def <<(msg)
1432
+ @trace_proc.call(msg) if @type == :all or @type == :element
1433
+ end
1434
+ end
1435
+ end
1436
+
1437
+ class PreProcessor # :nodoc: all
1438
+ attr_reader :sections
1439
+ CDataStart = %r[(.*?)(<!\[CDATA\[)(.*)]m
1440
+ CDataEnd = %r[(.*?)(\]\]>)(.*)]m
1441
+
1442
+ def initialize(opt={})
1443
+ @opt = opt
1444
+ init()
1445
+ @trace = false
1446
+ #@trace = true
1447
+ end
1448
+
1449
+ def process(s)
1450
+ init
1451
+ ret = process_block(s)
1452
+ ret = process_erb_and_inline_amxml(ret)
1453
+ ret << pop_tag(0)
1454
+ ret
1455
+ end
1456
+
1457
+ def process_erb_and_inline_amxml(s)
1458
+ if s =~ CDataStart
1459
+ out_of_cdata, cdata, in_cdata = $1, $2, $3
1460
+ process_out_of_cdata(out_of_cdata) + cdata + parse_in_cdata(in_cdata)
1461
+ else
1462
+ process_out_of_cdata(s)
1463
+ end
1464
+ end
1465
+
1466
+ def process_block(s)
1467
+ ret = ""
1468
+ lines = s.to_a
1469
+ while line = lines.shift
1470
+ next if line =~ /^\s*$/
1471
+ current_indent = line[/^ */].size
1472
+ ret << pop_tag(current_indent)
1473
+
1474
+ case line
1475
+ when %r[^\s*#]
1476
+ # It's a comment. do nothing
1477
+ when AmXml::R_AMXML_BLOCK_START
1478
+ amxml = $1
1479
+ while true
1480
+ l = lines.shift
1481
+ case l
1482
+ when %r[(.*)<(?: |-)*$]
1483
+ amxml << $1
1484
+ break
1485
+ when nil
1486
+ raise "incomplete amxml block start #{amxml}"
1487
+ else
1488
+ amxml << l
1489
+ end
1490
+ end
1491
+ a = AmXml.parse(amxml, @opt)
1492
+ raise "illeagal amxml multi line #{amxml.inspect}" unless a
1493
+ push_tag(current_indent, a.tag)
1494
+ ret << a.result(false)
1495
+ when AmXml::R_AMXML_ERB_LINE
1496
+ mark, src = $1,$2
1497
+ ret << "<![CDATA[<% "
1498
+ while true
1499
+ case mark
1500
+ when '='
1501
+ ret << "\n _erbout.concat(eval(%{#{src}}).to_s) "
1502
+ when String
1503
+ ret << "\n#{src}"
1504
+ else
1505
+ break
1506
+ end
1507
+ case l = lines.shift
1508
+ when AmXml::R_AMXML_ERB_LINE
1509
+ mark, src = $1,$2
1510
+ else
1511
+ mark = nil
1512
+ lines.unshift l
1513
+ end
1514
+ end
1515
+ ret << "\n%>]]>\n"
1516
+ when AmXml::R_AMXML_BLOCK_LINE
1517
+ a = AmXml.parse($1, {:trline=>$2.include?("-")}.merge(@opt))
1518
+ raise "illeagal amxml block line #{line.inspect}" unless a
1519
+ ret << a.result(false)
1520
+ push_tag(current_indent, a.tag)
1521
+ when %r[(.*)^\s*>>>\s*$]m
1522
+ ret << $1
1523
+ raise "unmatched >>> " unless @stack.size > 0
1524
+ ret << pop_tag(current_indent)
1525
+ when %r[^\s*\|\s*([\w:]*)\s*\|\|]
1526
+ lp = LineProcessor.new do |cell_text|
1527
+ PreProcessor.new.process_block(cell_text)
1528
+ end
1529
+ while lp.parse_line(line)
1530
+ line = lines.shift
1531
+ end
1532
+ lines.unshift(line) unless line =~ /^( |-)*$/
1533
+ ret << lp.get_result_xml
1534
+ ret
1535
+ else
1536
+ ret << line
1537
+ end
1538
+ end
1539
+ ret << pop_tag(0)
1540
+ ret
1541
+ end
1542
+
1543
+ private
1544
+
1545
+ def init
1546
+ @sections = {}
1547
+ @stack = []
1548
+ end
1549
+
1550
+ def process_out_of_cdata(s)
1551
+ erb_nest=0
1552
+ ret = ""
1553
+ while s.size > 0
1554
+ puts "process_out_of_cdata:#{erb_nest}:#{@stack.join('/')}:#{s}" if @trace
1555
+ case s
1556
+ when %r[\A(.*?)(<%|%>)((?=.?))]m
1557
+ case $2
1558
+ when "<%" ; erb_nest +=1
1559
+ when "%>" ; erb_nest -= 1
1560
+ end
1561
+ if $2 == "<%" and erb_nest == 1
1562
+ if Regexp.last_match.post_match[0] == "("[0] and
1563
+ s =~ %r[\A(.*?)(<%\((\w+?)\))]m
1564
+ ret << process_inline_amxml($1)
1565
+ s = process_section($3.intern, Regexp.last_match.post_match, 1)
1566
+ erb_nest -= 1
1567
+ else
1568
+ ret << process_inline_amxml($1) << "<![CDATA[<%"
1569
+ s = Regexp.last_match.post_match
1570
+ end
1571
+ elsif $2 == "%>" and erb_nest == 0
1572
+ ret << $1 << $2 << "]]>"
1573
+ s = Regexp.last_match.post_match
1574
+ else
1575
+ ret << $1 << $2
1576
+ s = Regexp.last_match.post_match
1577
+ end
1578
+ else
1579
+ ret << process_inline_amxml(s)
1580
+ s = ""
1581
+ end
1582
+ end
1583
+ ret
1584
+ end
1585
+
1586
+ def parse_in_cdata(s)
1587
+ if s =~ CDataEnd
1588
+ in_cdata, cdata_end, out_of_cdata = $1, $2, $3
1589
+ in_cdata + cdata_end + process_erb_and_inline_amxml(out_of_cdata)
1590
+ else
1591
+ raise "end of cdata not found in #{s}"
1592
+ end
1593
+ end
1594
+
1595
+ def process_section(section_id, s, nest)
1596
+ puts "%s:%d:%s" % [section_id, nest, s] if @trace
1597
+ @sections[section_id] ||= ""
1598
+ case s
1599
+ when %r[\A(.*?)(<%|%>)(.*)\Z]m
1600
+ case $2
1601
+ when "<%"
1602
+ @sections[section_id] << $1 << $2
1603
+ s = process_section(section_id, $3, nest+1)
1604
+ when "%>"
1605
+ @sections[section_id] << $1
1606
+ if nest == 1
1607
+ $3
1608
+ else
1609
+ @sections[section_id] << $2
1610
+ s = process_section(section_id, $3, nest-1)
1611
+ end
1612
+ else
1613
+ raise "can't happen"
1614
+ end
1615
+ else
1616
+ raise "can't happen"
1617
+ end
1618
+ end
1619
+
1620
+ def process_inline_amxml(s)
1621
+ puts "process_inline_amxml:%s" % s if @trace
1622
+ ret = ""
1623
+ s.to_s.scan(%r[(.*?)<<(.*?)>>]m) do |pre, amxml|
1624
+ ret << $1 << parse_inline_amxml(amxml)
1625
+ end
1626
+
1627
+ if Regexp.last_match
1628
+ ret + Regexp.last_match.post_match
1629
+ else
1630
+ s
1631
+ end
1632
+ end
1633
+
1634
+ def parse_inline_amxml(s)
1635
+ a = AmXml.parse(s, @opt)
1636
+ raise TemplateError, "illeagal inline amxml #{s}" unless a
1637
+ a.result(true)
1638
+ end
1639
+
1640
+
1641
+ def push_tag(indent, tag)
1642
+ @stack.unshift([indent, tag])
1643
+ end
1644
+
1645
+ def pop_tag(indent)
1646
+ ret = ""
1647
+ while @stack.size > 0 and @stack.first[0] >= indent
1648
+ tag = @stack.shift[1]
1649
+ if tag == :cdata
1650
+ ret << "]]>"
1651
+ else
1652
+ ret << "</#{tag}>"
1653
+ end
1654
+ end
1655
+ ret
1656
+ end
1657
+
1658
+ class AmXml
1659
+ attr_reader :tag, :attr, :src_name, :filters
1660
+ attr_reader :skipif, :skipunless, :for, :value
1661
+
1662
+ # Regexps borrowed from REXML rexml/baseparser.rb
1663
+ NCNAME_STR= '[\w:][\-\w\d.#]*?'
1664
+ NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
1665
+
1666
+ R_TAG_ATTR = %r[\s*(#{NAME_STR})((?:\s*#{NAME_STR}\s*=\s*(["'])(?:.*?)\3)*)]um #"
1667
+ S_NO_SRC = %[\s*]
1668
+ S_SRC_AND_FILTERS = %q[(:([\\w\\d]*))\\s*(\\|.*?)*]
1669
+ S_FOR = %q[( \\[(.+)\\] )]
1670
+ S_COND = %q[\\?(\\!)?\\[(.+)\\]]
1671
+ S_VALUE = %q[\\{(.+)\\}]
1672
+ S_TRAILER = %q[((?: |\\-)*)]
1673
+ S_AMVARS = %[(#{S_FOR}|#{S_COND}|#{S_VALUE})?]
1674
+ R_AMXML_WITHOUT_TAG = %r[\A\s*(?:\/)?\s*(?:#{S_NO_SRC}|#{S_SRC_AND_FILTERS})*\s*#{S_AMVARS}\s*\Z]um
1675
+ R_AMXML = %r[\A#{R_TAG_ATTR}\s*(?:\/)?\s*(?:#{S_NO_SRC}|#{S_SRC_AND_FILTERS})*\s*#{S_AMVARS}\s*\Z]um
1676
+ R_AMXML_BLOCK_LINE = %r[^\s*<<\s*?(.*)\s*?<#{S_TRAILER}$]
1677
+ R_AMXML_BLOCK_START = %r[^\s*<<\s*((#{R_TAG_ATTR})?[^<>]*)\s*$]
1678
+ R_AMXML_ERB_LINE = %r[^\s*\%([ =])(.*)$]
1679
+
1680
+ def self.parse(s, opt={})
1681
+ self.new(opt).parse(s, opt[:trline])
1682
+ end
1683
+
1684
+ def initialize(opt)
1685
+ @opt = opt
1686
+ @tag = "_"
1687
+ @attr = ""
1688
+ @src_name = @filters = nil
1689
+ @skipif = @skipunless = @for = @value = nil
1690
+ end
1691
+
1692
+ def has_amxml_attrs?
1693
+ @src_name or @filters or @skipif or @skipunless or @for or @value
1694
+ end
1695
+
1696
+ def parse(s, trline=false)
1697
+ case s
1698
+ when R_AMXML_WITHOUT_TAG
1699
+ if trline
1700
+ @tag = "tr"
1701
+ else
1702
+ @tag = "_"
1703
+ end
1704
+ @src_name = $2.strip if $2
1705
+ @filters = $3[1..-1].strip if $3
1706
+ parse_am_vars($4)
1707
+ when %r[\A\s*([\w]+):([\-\w\d.]*)\s*(/?)\s*\Z] # <<aaa:xxx>>
1708
+ if $3 == "/"
1709
+ @tag = "#$1:#$2"
1710
+ else
1711
+ @tag, @src_name = $1, $2
1712
+ end
1713
+ @tag = "_" if @tag == nil or @tag == ""
1714
+ @attr = ""
1715
+ when R_AMXML
1716
+ @tag, @attr = $1.to_s, $2.to_s
1717
+ src_start = $4
1718
+ @src_name = $5
1719
+ @filters = $6[1..-1].strip if $6
1720
+ parse_am_vars($7)
1721
+ when "%"
1722
+ @tag = :cdata
1723
+ when /=\s*(\w+)\s*/
1724
+ @tag = [:put_stream,$1.intern]
1725
+ when /\s*(\w+)\s*=/
1726
+ @tag = [:change_stream,$1.intern]
1727
+ else
1728
+ return nil
1729
+ end
1730
+ parse_css_selecter_like
1731
+ self
1732
+ end
1733
+
1734
+ def parse_css_selecter_like
1735
+ while true
1736
+ case @tag
1737
+ when /^(\w+)([#\.])([\w-]+)(.*)$/
1738
+ @tag, cls = $1 + $4, $3
1739
+ case $2
1740
+ when "."
1741
+ @attr += " class=\"#{$3}\""
1742
+ when "#"
1743
+ @attr += " id=\"#{$3}\""
1744
+ else
1745
+ break
1746
+ end
1747
+ else
1748
+ break
1749
+ end
1750
+ end
1751
+ end
1752
+
1753
+ def parse_am_vars(s)
1754
+ case s
1755
+ when nil
1756
+ # do nothing
1757
+ when %r[#{S_FOR}]m
1758
+ @for = $2.strip
1759
+ when %r[#{S_COND}]m
1760
+ if $1 == "!"
1761
+ @skipif = $2.strip
1762
+ else
1763
+ @skipunless = $2.strip
1764
+ end
1765
+ when %r[#{S_VALUE}]m
1766
+ @value = $1.strip
1767
+ else
1768
+ raise "can't happen #{s}"
1769
+ end
1770
+ end
1771
+
1772
+ def result(single_tag)
1773
+ case @tag
1774
+ when :cdata
1775
+ return "<![CDATA["
1776
+ when Array
1777
+ case @tag[0]
1778
+ when :change_stream
1779
+ ret = "<_ am:filter='ChangeStream[#{@tag[1].inspect}]'"
1780
+ when :put_stream
1781
+ ret = "<_ am:filter='PutStream[#{@tag[1].inspect}]'"
1782
+ else
1783
+ raise "can't happen #{@tag.inspect}"
1784
+ end
1785
+ if single_tag
1786
+ ret << " />"
1787
+ else
1788
+ ret << " >"
1789
+ @tag = "_"
1790
+ end
1791
+ return ret
1792
+ end
1793
+ ret = "<"
1794
+ if @tag
1795
+ ret << @tag
1796
+ else
1797
+ ret << "_"
1798
+ end
1799
+ if @src_name and @src_name.size > 0
1800
+ am_src = @opt[:am_src] || "am:src"
1801
+ if @filters
1802
+ ret << make_attr(am_src, "#@src_name|#@filters")
1803
+ else
1804
+ ret << make_attr(am_src, @src_name)
1805
+ end
1806
+ else
1807
+ if @filters
1808
+ am_filter = @opt[:am_filter] || "am:filter"
1809
+ ret << make_attr(am_filter, @filters)
1810
+ end
1811
+ end
1812
+ if @for
1813
+ am_for = @opt[:am_for] || "am:for"
1814
+ ret << make_attr(am_for, @for)
1815
+ end
1816
+ if @skipif
1817
+ am_skipif = @opt[:am_skipif] || "am:skipif"
1818
+ ret << make_attr(am_skipif, @skipif)
1819
+ end
1820
+ if @skipunless
1821
+ am_skipif = @opt[:am_skipif] || "am:skipif"
1822
+ ret << make_attr(am_skipif, "not(#@skipunless)")
1823
+ end
1824
+ if @value
1825
+ am_v = @opt[:am_v] || "am:v"
1826
+ ret << make_attr(am_v, "HashDelegator.new($_) { {#@value} }")
1827
+ end
1828
+ ret << @attr.to_s
1829
+ if single_tag
1830
+ ret << " />"
1831
+ else
1832
+ ret << " >"
1833
+ end
1834
+ ret
1835
+ end
1836
+
1837
+ def make_attr(key, val)
1838
+ if val.include?(?')
1839
+ " #{key}=\"#{val}\""
1840
+ else
1841
+ " #{key}='#{val}'"
1842
+ end
1843
+ end
1844
+ end
6
1845
 
7
- # Map amrita:type to element_option
8
- DefaultTypeMap = {
9
- :use_args => { :use_args => true},
10
- :use_original_element=> { :use_original_element => true},
11
- :expand_by_member => { :expand_by_member => true },
12
- :erb => { :erb => true, :use_args=>true },
13
- :link => { :link_attr => [:href, [/javascript:.*/]] },
14
- :input => { :value_attr => :value },
15
- :checkbox => { :value_attr => :checked },
16
- :encoding_euc => { :encoding=>'EUC-JP' }
17
- }
1846
+ class LineProcessor
1847
+ attr_reader :cells
18
1848
 
19
- # keep id attribute of output if set.
20
- attr_accessor :keep_id
21
- # The name of attribute that will be used for template expandion by amrita.
22
- # You will need to set this if you use _id_ attribute fom DOM.
23
- # For expample, if this was set to "amrita_id",
24
- # you can use amrita_id for amrita and id for DOM.
25
- attr_accessor :amrita_id
26
-
27
- attr_accessor :encoding, :spec, :ie_hack, :debug_source, :eval_cdata_as_erb # :nodoc:
28
-
29
- attr_accessor :type_map, :parts_id # :nodoc:
30
-
31
- attr_reader :mod, :element_option, :root # :nodoc:
32
-
33
-
34
- def initialize(&block)
35
- @keep_id = false
36
- @amrita_id = nil
37
- @setuped = false
38
- @spec = nil
39
- @spec_proc = block
40
- @type_map = DefaultTypeMap.clone
41
- @ie_hack = false
42
- @eval_cdata_as_erb = false
43
- @element_option = Hash.new
44
- @parts_template = Hash.new
45
- @parts_id = nil
46
- @encoding = $KCODE
47
- @debug_source = nil
48
- end
49
-
50
- def setup
51
- return if @setuped
52
- setup_document
53
- @spec = get_spec
54
- @mod = @spec.create_template_module(@rexml_doc, @rexml_doc)
55
- @expander = get_expander
56
- @setuped = true
57
- end
58
-
59
- def setup_document
60
- return if @root
61
- @rexml_doc =
62
- if @eval_cdata_as_erb
63
- t = get_document_text
64
- t.gsub!(/<amrita:erb *id='(\w+)'>/, "<span id='\\1'><![CDATA[" )
65
- t.gsub!('</amrita:erb>', ']]></span>')
66
- REXML::Document.new(t)
67
- else
68
- get_document
69
- end
70
- @root = @rexml_doc.root
71
- end
72
-
73
- def get_root_option
74
- ret = {
75
- :delete_id => (not @keep_id),
76
- :key_attr_name=>( @amrita_id or :id),
77
- :ie_hack => @ie_hack,
78
- :eval_cdata_as_erb => @eval_cdata_as_erb,
79
- :debug_source => @debug_source,
80
- }
81
- case @encoding
82
- when "EUC"
83
- ret[:encoding]='EUC-JP'
84
- when "SJIS"
85
- ret[:encoding]='SHIFT_JIS'
86
- when "UTF8"
87
- ret[:encoding]='UTF-8'
88
- when "NONE"
89
- else
90
- raise "unkonw encoding #{@encoding}"
1849
+ def initialize(&block)
1850
+ @cells = []
1851
+ @cell_proc = block
1852
+ end
1853
+
1854
+ def parse_line(line)
1855
+ case line
1856
+ when %r[^\s*#]
1857
+ true
1858
+ when %r[^\s*\|\s*([\w:]*)\s*\|\|]
1859
+ parse_cells($1.strip, Regexp.last_match.post_match)
1860
+ true
1861
+ else
1862
+ false
1863
+ end
1864
+ end
1865
+
1866
+ def parse_cells(attr_key, cells)
1867
+ attr_key = :text if attr_key == ""
1868
+ cnt = 0
1869
+ splited = ""
1870
+ cells.chomp.scan(%r[([^\|]+?)((?!r\|)\|(\|?)|$)]) do |s, th_flag|
1871
+ if s[-1] == ?\\
1872
+ splited = s[0..-2] + "|"
1873
+ next
1874
+ else
1875
+ s = splited.to_s + s
1876
+ splited = ""
1877
+ end
1878
+
1879
+ @cells[cnt] ||= {}
1880
+ cell = @cells[cnt]
1881
+ if cell[attr_key]
1882
+ cell[attr_key] << "\n" << s
1883
+ else
1884
+ cell[attr_key] = s
1885
+ end
1886
+
1887
+ cell[:tag] = (th_flag == "||" ? :th : :td)
1888
+ cnt += 1
1889
+ end
1890
+ end
1891
+
1892
+ def get_result_xml
1893
+ @cells.collect do |c|
1894
+ tag, text = c[:tag], @cell_proc.call(c[:text])
1895
+ c.delete(:tag)
1896
+ c.delete(:text)
1897
+ if c.size == 0
1898
+ if text == nil or text.strip == ""
1899
+ ""
1900
+ else
1901
+ "<#{tag}>#{text}</#{tag}>"
1902
+ end
1903
+ else
1904
+ a = attr = c.collect do |k, v|
1905
+ next unless v
1906
+ v.strip!
1907
+ next if v == ""
1908
+ if v.include?(?')
1909
+ "#{k}=\"#{v}\""
1910
+ else
1911
+ "#{k}='#{v}'"
1912
+ end
1913
+ end.join(" ")
1914
+ "<#{tag} #{a}>#{text}</#{tag}>"
1915
+ end
1916
+ end.join("")
1917
+ end
91
1918
  end
92
- ret
93
1919
  end
94
1920
 
95
- def get_spec
96
- if @spec
97
- @spec
98
- elsif @spec_proc
99
- @spec = @spec_proc.call
100
- else
101
- @spec = make_default_spec(@root, @type_map, get_root_option, @element_option)
1921
+ class Hook
1922
+ attr_reader :stream, :cnt, :element_s
1923
+
1924
+ def initialize(&block)
1925
+ @hook_proc = block
1926
+ end
1927
+
1928
+ def call(stream, cnt, element_s,&block)
1929
+ @stream = stream
1930
+ @cnt = cnt
1931
+ @element_s = element_s
1932
+ @render_me_proc = block
1933
+ instance_eval(&@hook_proc)
1934
+ end
1935
+
1936
+ def render_me_with(it)
1937
+ stream << @render_me_proc.call(nil, it)
1938
+ end
1939
+
1940
+ def render_child(name, it)
1941
+ stream << @render_me_proc.call(name, it)
1942
+ end
1943
+
1944
+ class Renderer < Renderers::Base # :nodoc: all
1945
+ def initialize
1946
+ super("Hook")
1947
+ end
1948
+
1949
+ def generate_body(cg, de, element)
1950
+ cg.code("e = #{ cg.define_constant(element.to_s.inspect)}")
1951
+ cg.code("$_.call(__stream__, __cnt__, e) do |name, it| ")
1952
+ cg.level_up do
1953
+ cg.case_('name') do
1954
+ cg.when_("nil") do
1955
+ cg.code("render_with(it, __binding__)")
1956
+ end
1957
+ de.children.each do |c|
1958
+ next unless c.kind_of?(DynamicElement)
1959
+ cg.when_(c.name.intern.inspect) do
1960
+ cg.code("#{c.instance_name}.render_with(it, __binding__)")
1961
+ end
1962
+ end
1963
+ end
1964
+ end
1965
+ cg.code("end")
1966
+ end
102
1967
  end
103
- @spec = @spec.clone unless @spec.cloned
104
- @spec
105
1968
  end
1969
+ end
1970
+
1971
+ module Renderers # :nodoc: all
1972
+ class Base
1973
+ def initialize(type_name)
1974
+ @type_name = type_name
1975
+ end
1976
+
1977
+ def generate_when(cg)
1978
+ cg.when_(@type_name) do
1979
+ yield
1980
+ end
1981
+ end
1982
+ end
1983
+
1984
+ class ScalarRenderer < Renderers::Base
1985
+ def initialize
1986
+ super("ScalarData")
1987
+ end
106
1988
 
107
- def get_expander
108
- @spec.generate_expander(@mod)
1989
+ def generate_body(cg, de, element)
1990
+ de.element_render_code(cg, element) do
1991
+ cg.put_string_expression("$_.amrita_value")
1992
+ end
1993
+ end
109
1994
  end
110
1995
 
111
- def use_erb(b=TOPLEVEL_BINDING)
112
- @eval_cdata_as_erb = b
113
- @simple_cdata = true
1996
+ class NilRenderer < Base
1997
+ def initialize
1998
+ super("nil")
1999
+ end
2000
+
2001
+ def generate_body(cg, de, element)
2002
+ end
114
2003
  end
115
2004
 
116
- def define_parts(*parts_ids)
117
- setup_document
118
- parts_ids.each do |pid|
119
- case pid
120
- when Array
121
- pid.each { |id| define_parts(id) }
2005
+ class TrueRenderer < Base
2006
+ def initialize
2007
+ super("true")
2008
+ end
2009
+
2010
+ def generate_body(cg, de, element)
2011
+ cg.put_hpricot_node(element)
2012
+ end
2013
+ end
2014
+
2015
+ class HashRenderer < Base
2016
+ def initialize
2017
+ super("DictionaryData")
2018
+ end
2019
+
2020
+
2021
+ def generate_body(cg, de, element)
2022
+ de.element_render_code(cg, element) do
2023
+ de.children.each do |c|
2024
+ c.render_me(cg)
2025
+ end
2026
+ end
2027
+ end
2028
+ end
2029
+
2030
+ class ElseRenderer < Base
2031
+ def initialize
2032
+ super("Object")
2033
+ end
2034
+
2035
+ def generate_when(cg)
2036
+ cg.else_ do
2037
+ yield
2038
+ end
2039
+ end
2040
+
2041
+ def generate_body_new(cg, de, element)
2042
+ de.element_render_code(cg, element) do
2043
+ de.children.each do |c|
2044
+ c.render_me(cg)
2045
+ end
2046
+ end
2047
+ end
2048
+
2049
+ def generate_body(cg, de, element)
2050
+ if de.has_ruby?
2051
+ de.element_render_code(cg, element) do
2052
+ de.children.each do |c|
2053
+ c.render_me(cg)
2054
+ end
2055
+ end
2056
+ else
2057
+ cg.put_expression('raise %[type mismatch, got (#{$_.inspect}) for ' + element.to_s.inspect + ' ]')
2058
+ end
2059
+ end
2060
+ end
2061
+ end
2062
+
2063
+ module Filters
2064
+ include Renderers
2065
+
2066
+ class FilterArray < Array # :nodoc: all
2067
+ def initialize(*args)
2068
+ args.each { |a| self << a }
2069
+ end
2070
+
2071
+ def |(other)
2072
+ case other
2073
+ when Class
2074
+ a = self + [other.new]
2075
+ FilterArray.new(*a)
2076
+ when Filters::Base
2077
+ a = self + [other]
2078
+ FilterArray.new(*a)
122
2079
  when Symbol
123
- make_parts(@root, pid)
124
- raise "parts element #{pid} not found" unless @parts_template[pid]
125
- when Hash
126
- pid.each do |k, v|
127
- define_parts(k)
128
- @parts_template[k].define_parts(v) unless v == true
2080
+ self + FilterArray.new(FunctionFilter.new(other))
2081
+ when Array
2082
+ case other.first
2083
+ when Symbol
2084
+ self + FilterArray.new(FunctionFilter.new(*other))
2085
+ else
2086
+ self + other
129
2087
  end
130
2088
  else
131
- raise "#{pid.class} not supported"
2089
+ raise "not filter #{other.inspect}"
132
2090
  end
133
2091
  end
134
2092
  end
135
2093
 
136
- def make_parts(element, parts_id)
137
- element.each_element do |e|
138
- if e.attributes['id'] == parts_id.to_s
139
- e.parent.delete(e)
140
- pt = @parts_template[parts_id] = TemplateElement.new(e)
141
- pt.element_option[parts_id]= element_option[parts_id]
142
- pt.parts_id = parts_id
143
- pt.debug_source = debug_source
144
- pt.use_erb(@eval_cdata_as_erb) if @eval_cdata_as_erb
2094
+ class Base # :nodoc: all
2095
+ attr_accessor :next_
2096
+ def self.filter_method(*m)
2097
+ m.each do |name|
2098
+ module_eval <<-END
2099
+ def #{name}(*args,&block)
2100
+ next_.#{name}(*args,&block)
2101
+ end
2102
+ END
2103
+ end
2104
+ end
2105
+
2106
+ FilterMethods.each do |m|
2107
+ filter_method(m)
2108
+ end
2109
+
2110
+ def self.inherited(cls)
2111
+ super
2112
+ def cls.[](*args, &block)
2113
+ new(*args, &block)
2114
+ end
2115
+
2116
+ def cls.|(other)
2117
+ FilterArray.new(self.new) | other
2118
+ end
2119
+ end
2120
+
2121
+ def |(other)
2122
+ FilterArray.new(self) | other
2123
+ end
2124
+
2125
+ def parse_filter_a(f)
2126
+ case f = eval(f)
2127
+ when Class
2128
+ f.new
145
2129
  else
146
- make_parts(e, parts_id)
2130
+ f
147
2131
  end
148
2132
  end
149
2133
  end
150
2134
 
151
- def [](x)
152
- case x
153
- when Symbol
154
- @parts_template[x]
155
- else
156
- x = { parts_id=>x} if parts_id
157
- expand(out="", x)
158
- Amrita2::SanitizedString[out]
2135
+ class Attr < Base
2136
+ class Renderer < Renderers::Base
2137
+ include Util
2138
+ include OptionSupport
2139
+ def initialize(*args)
2140
+ parse_opt(*args)
2141
+ end
2142
+
2143
+ def generate_body(cg, de, element)
2144
+ use_body = (not de.has_dynamic?) && @opt.has_key?(:body)
2145
+ body_key = nil
2146
+ if use_body
2147
+ body_key = @opt[:body]
2148
+ body_key = :body unless body_key.kind_of?(Symbol)
2149
+ cg.code("body = $_.amrita_value(:#{body_key})")
2150
+ end
2151
+ if @opt.size > 0
2152
+ cg.code("a = {}")
2153
+ @opt.each do |key, v|
2154
+ next if key == :body
2155
+ if element.get_attribute(key)
2156
+ cg.case_("v = $_.amrita_value(:#{v})") do
2157
+ cg.when_("true") do
2158
+ cg.code("a[#{key.inspect}] = #{element.get_attribute(key).inspect}")
2159
+ end
2160
+ cg.when_("false, nil") do
2161
+ cg.code("a.delete(#{key.inspect})")
2162
+ end
2163
+ cg.else_ do
2164
+ cg.code("a[#{key.inspect}] = $_.amrita_value(:#{v})")
2165
+ end
2166
+ end
2167
+ else
2168
+ cg.code("v = $_.amrita_value(:#{v})")
2169
+ cg.code("a[#{key.inspect}] = v if v")
2170
+ end
2171
+ end
2172
+ else
2173
+ cg.code("a = $_ ")
2174
+ end
2175
+
2176
+ a = { }
2177
+ element.attributes.each do |k, v|
2178
+ next if @opt[k.intern]
2179
+ a[k] = v
2180
+ end
2181
+ cg.code("e = new_element(#{element.name.inspect}, #{a.inspect}.merge(a))")
2182
+ if use_body
2183
+ body_for_static(cg)
2184
+ else
2185
+ body_for_dynamic(cg, de, element)
2186
+ end
2187
+ end
2188
+
2189
+ def body_for_static(cg)
2190
+ cg.if_("body") do
2191
+ cg.put_string_expression("start_tag(e)")
2192
+ cg.put_string_expression("body.amrita_value")
2193
+ cg.put_string_expression("end_tag(e)")
2194
+ cg.else_ do
2195
+ cg.put_string_expression("e.to_s")
2196
+ end
2197
+ end
2198
+ end
2199
+
2200
+ def body_for_dynamic(cg, de, element)
2201
+ cg.put_string_expression("start_tag(e)")
2202
+ de.children.each do |c|
2203
+ c.render_me(cg)
2204
+ end
2205
+ cg.put_string_expression("end_tag(e)")
2206
+ end
2207
+ end
2208
+
2209
+ def initialize(*args)
2210
+ @args = args
2211
+ end
2212
+
2213
+ def setup_type_renderer(de)
2214
+ de.renderers.clear
2215
+ de.renderers << Renderer.new(*@args)
159
2216
  end
160
2217
  end
161
2218
 
162
- def expand(out, data=nil, &block)
163
- setup unless @setuped
164
- set_binding(@eval_cdata_as_erb) if @eval_cdata_as_erb.kind_of?(Binding)
165
- if data
166
- @expander.expand_template(out, data)
167
- else
168
- @mod.expand_template(out, &block)
2219
+
2220
+ class Default < Base
2221
+ def initialize(default_value_src)
2222
+ @default_value_src = default_value_src
2223
+ end
2224
+
2225
+ def value_filter_code(de, cg, value)
2226
+ cg.if_("$_") do
2227
+ super
2228
+ cg.else_ do
2229
+ cg.code("$_ = (#{@default_value_src.inspect})")
2230
+ end
2231
+ end
2232
+ end
2233
+
2234
+ def value_filter_code_old(de, cg, value)
2235
+ cg.code("$_ = $_ || (#{@default_value_src.inspect})")
2236
+ super
2237
+ end
2238
+ end
2239
+
2240
+ class Repeat < Base
2241
+ def initialize(cnt)
2242
+ @cnt = cnt.to_i
2243
+ end
2244
+
2245
+ def value_filter_code(de, cg, value)
2246
+ cg.code("$_ = $_ * #@cnt ")
2247
+ super
2248
+ end
2249
+ end
2250
+
2251
+ class Format < Base
2252
+ def initialize(format)
2253
+ @format = format
2254
+ end
2255
+ def value_filter_code(de, cg, value)
2256
+ cg.code("$_ = #{@format.inspect} % $_")
2257
+ super
169
2258
  end
170
2259
  end
171
2260
 
172
- def prettyprint=(f)
173
- warn 'prettyprint is not supported'
2261
+ class AcceptData < Base
2262
+ include Renderers
2263
+ def initialize(*types)
2264
+ @types = types
2265
+ end
2266
+
2267
+ def setup_type_renderer(de)
2268
+ nil_processed = true_processed = hook_processed = false
2269
+ @types.each do |t|
2270
+ case t
2271
+ when nil, NilClass
2272
+ de.renderers.unshift NilRenderer.new unless nil_processed
2273
+ nil_processed = true
2274
+ when true, TrueClass
2275
+ de.renderers << TrueRenderer.new unless true_processed
2276
+ true_processed = true
2277
+ when :hook
2278
+ de.renderers << Core::Hook::Renderer.new unless hook_processed
2279
+ hook_processed = true
2280
+ else
2281
+ raise ArgumentError,"unknown type for AcceptData #{t.inspect}"
2282
+ end
2283
+ end
2284
+ super
2285
+ end
174
2286
  end
175
2287
 
176
- def compact_space=(f)
177
- warn 'compact_space is not supported'
2288
+ class NoSanitize < Base
2289
+ def value_filter_code(de, cg, value)
2290
+ super
2291
+ cg.code("$_ = SanitizedString[$_]")
2292
+ end
178
2293
  end
179
2294
 
180
- def escaped_id=(f)
181
- warn 'escaped_id is not supported'
2295
+ class Each < Base
2296
+ include Util::OptionSupport
2297
+ def initialize(*args)
2298
+ parse_opt(*args)
2299
+ end
2300
+
2301
+ def value_filter_code(de, cg, value)
2302
+ var = "__cnt__#{de.class_name}"
2303
+ @opt.each do |k,va|
2304
+ cg.code("#{var} = __cnt__%#{va.size}")
2305
+ va.each_with_index do |v, n|
2306
+ case v
2307
+ when Symbol
2308
+ cg.code("$_[#{k.inspect}] = $_[#{v.inspect}] if #{var} == #{n}")
2309
+ when String
2310
+ cg.code("$_[#{k.inspect}] = #{v.inspect} if #{var} == #{n}")
2311
+ else
2312
+ raise("unknown Each type #{v.inspect}")
2313
+ end
2314
+ end
2315
+ end
2316
+ super
2317
+ end
182
2318
  end
183
2319
 
184
- def xml=(f)
185
- warn 'xml option is not supported'
2320
+ class ToHash < Base
2321
+ include Util::OptionSupport
2322
+ def initialize(key)
2323
+ @key = key
2324
+ end
2325
+
2326
+ def value_filter_code(de, cg, value)
2327
+ case @key
2328
+ when Symbol
2329
+ cg.code("$_ = { #{@key.inspect} => $_ }")
2330
+ when Hash
2331
+ cg.code("$_ = { ")
2332
+ @key.each do |k,v|
2333
+ cg.code(" #{k.inspect} => $_.amrita_value(#{v.inspect}),")
2334
+ end
2335
+ cg.code("}")
2336
+ end
2337
+
2338
+ super
2339
+ end
186
2340
  end
187
2341
 
188
- def asxml=(f)
189
- warn 'asxml option is not supported'
2342
+ class InlineRuby < Base # :nodoc: all
2343
+ include Runtime
2344
+ include Renderers
2345
+ include Util
2346
+
2347
+ def setup_type_renderer(de)
2348
+ de.renderers << HashRenderer.new unless de.renderers.find {|r| r.kind_of?(HashRenderer) }
2349
+ super
2350
+ end
2351
+
2352
+ def generate_dynamic_element(de, e)
2353
+ a = e.attributes
2354
+ ret = super
2355
+ return ret if ret
2356
+
2357
+ # check if element has any of inlineruby attributes
2358
+ if %w(am_skipif am_for am_v).any? { |k| a[de.__send__(k)] }
2359
+ filters_src = e.attributes[de.am_filter]
2360
+ filters = de.root.compile_filters(e, nil, filters_src)
2361
+ Core::DynamicElement.new(de, nil , e, filters)
2362
+ else
2363
+ nil
2364
+ end
2365
+ end
2366
+
2367
+ def loop_check_code(de, cg)
2368
+ foreach_ = de.am_for_value
2369
+ if foreach_
2370
+ cg.if_("__cnt__ < 0") do
2371
+ #cg.code("$_ = eval(#{foreach_.inspect}, __binding__)")
2372
+ cg.code("$_ = ")
2373
+ cg.eval(foreach_.inspect)
2374
+ super
2375
+ end
2376
+ else
2377
+ super
2378
+ end
2379
+ end
2380
+
2381
+ def value_filter_code(de, cg, value)
2382
+ cond = de.am_skipif_value
2383
+ value = de.am_v_value
2384
+
2385
+ if cond
2386
+ cg.code("amrita_set_context_value($_)")
2387
+ #cg.code("return '' if eval(#{with_context_value_expression(cond).inspect},__binding__)")
2388
+ cg.code("return '' if \\")
2389
+ cg.eval(with_context_value_expression(cond).inspect)
2390
+ end
2391
+
2392
+ if value
2393
+ cg.code("amrita_set_context_value($_)")
2394
+ #cg.code("$_ = eval(#{with_context_value_expression(value).inspect},__binding__)")
2395
+ cg.code("$_ = ")
2396
+ cg.eval(with_context_value_expression(value).inspect)
2397
+ end
2398
+ super
2399
+ end
2400
+
2401
+ private
2402
+ def with_context_value_expression(src)
2403
+ [
2404
+ "$_ = amrita_get_context_value",
2405
+ "ret = (#{src})",
2406
+ "amrita_set_context_value($_)",
2407
+ "ret"
2408
+ ].join(";")
2409
+ end
190
2410
  end
191
2411
 
192
- def pre_format=(f)
193
- warn 'pre_format option is not supported'
2412
+ module NVarMixin
2413
+ private
2414
+
2415
+ def make_tupple_code(cg)
2416
+ init_code = @names.collect do |n|
2417
+ "$_.amrita_value(:#{n})"
2418
+ end.join(",")
2419
+ cg.code("$_ = Tuple[#{init_code}]")
2420
+ end
2421
+
2422
+ def replace_args(element)
2423
+ element.attributes.each do|k,v|
2424
+ element.set_attribute(k, replace_attr_args(v))
2425
+ end
2426
+ children = element.children.collect do |c|
2427
+ case c
2428
+ when Hpricot::Elem
2429
+ replace_args(c)
2430
+ when Hpricot::Text
2431
+ Hpricot::Text.new(replace_text_args(c.to_s))
2432
+ else
2433
+ raise "not implemented #{c.class}"
2434
+ end
2435
+ end
2436
+ element.children.clear
2437
+ children.each do |c|
2438
+ element.children << c
2439
+ end
2440
+ element
2441
+ end
2442
+
2443
+ def replace_attr_args(s)
2444
+ s.gsub(/(.)?\$(\d)/) do |ss|
2445
+ if $1 == "$"
2446
+ "$#$2"
2447
+ else
2448
+ $1.to_s + '#{Util::sanitize_attribute_value($_[' + ($2.to_i-1).to_s + '].to_s)}'
2449
+ end
2450
+ end
2451
+ end
2452
+
2453
+ def replace_text_args(s)
2454
+ s.gsub(/(.)?\$(\d)/) do |ss|
2455
+ if $1 == "$"
2456
+ "$#$2"
2457
+ else
2458
+ $1.to_s + '#{$_[' + ($2.to_i-1).to_s + '].amrita_value}'
2459
+ end
2460
+ end
2461
+ end
194
2462
  end
195
2463
 
196
- def use_compiler=(f)
197
- warn 'use_compiler option is not supported'
2464
+ class NVar < Filters::Base
2465
+ include NVarMixin
2466
+ def initialize(*names)
2467
+ @names = names
2468
+ end
2469
+
2470
+ def renderer_code(de, cg, element)
2471
+ make_tupple_code(cg) if @names.size > 0
2472
+ s = replace_args(element).to_s
2473
+ cg.put_string_expression(s.inspect.gsub(/\\#/, "#"))
2474
+ end
198
2475
  end
199
2476
 
200
- def get_document
201
- nil
2477
+ class Eval < NVar
2478
+ def initialize(*names)
2479
+ @names = names
2480
+ end
2481
+
2482
+ private
2483
+
2484
+ def make_tupple_code(cg)
2485
+ cg.code("amrita_set_context_value($_)")
2486
+ init_code = @names.collect do |n|
2487
+ case n
2488
+ when Symbol
2489
+ "$_.amrita_value(:#{n})"
2490
+ else
2491
+ "eval(#{with_context_value_expression(n).inspect},__binding__)"
2492
+ end
2493
+ end.join(",")
2494
+ cg.code("$_ = Tuple[#{init_code}]")
2495
+ end
2496
+
2497
+ def with_context_value_expression(src)
2498
+ [
2499
+ "$_ = amrita_get_context_value",
2500
+ "ret = (#{src})",
2501
+ "amrita_set_context_value($_)",
2502
+ "ret"
2503
+ ].join(";")
2504
+ end
202
2505
  end
203
2506
 
204
- def make_default_spec(root, type_map, option, element_option)
205
- s = Core::RootSpec.new(option.clone)
206
- make_default_spec1(root, option, [s], type_map, element_option)
207
- s
208
- end
209
-
210
- def make_default_spec1(e, option, spec_stack, type_map, element_option)
211
- key_attr = option[:key_attr_name].to_s
212
- id_ = e.attributes[key_attr]
213
- if id_
214
- sym = id_.intern
215
- opt = get_option(e, option, type_map, element_option, sym)
216
- s = Core::DynamicElementSpec.new(sym, nil, opt)
217
- s.parent = spec_stack.first
218
- spec_stack.first.children << s
219
- spec_stack.unshift(s)
220
- e.elements.each do |c|
221
- make_default_spec1(c, option, spec_stack, type_map, element_option)
222
- end
223
- spec_stack.shift
224
- spec_stack.first.check_double_id if option[:check_double_id]
225
- else
226
- e.elements.each do |c|
227
- make_default_spec1(c, option, spec_stack, type_map, element_option)
228
- end
229
- end
230
- end
231
-
232
- def get_option(e, option, type_map, element_option, sym)
233
- ret = option.clone
234
- a = e.attributes
235
- typ = (a['amrita:type'] || element_option[sym])
236
- case typ
237
- when Symbol, String
238
- default_opt = type_map[typ.intern] || { typ.intern=>true }
239
- ret.merge!(default_opt) if default_opt
240
- a.delete('amrita:type')
241
- when Hash
242
- default_opt = typ
243
- ret.merge!(default_opt) if default_opt
244
- a.delete('amrita:type')
245
- end
246
- if opt = a['amrita:option']
247
- ret.merge!(eval("{#{opt} }"))
248
- a.delete('amrita:option')
249
- end
250
- ret
251
- end
252
- end
2507
+ class NVarForAttr < Filters::Base
2508
+ def initialize(*names)
2509
+ @names = names
2510
+ end
253
2511
 
254
- class TemplateFile < Template
255
- include Amrita2
256
- def initialize(fname, &block)
257
- super(&block)
258
- @fname = fname
2512
+ def renderer_code(de, cg, element)
2513
+ make_tupple_code(cg) if @names.size > 0
2514
+ super
2515
+ end
2516
+
2517
+ def element_render_code(de, cg, element, &block)
2518
+ if (element.name == 'span' && element.attributes.size == 0)
2519
+ yield
2520
+ else
2521
+ replace_args(element)
2522
+ if element.children.size > 0
2523
+ cg.put_string_expression(element.stag.output('').inspect.gsub(/\\#/, "#"))
2524
+ yield
2525
+ cg.put_constant(element.etag.output(''))
2526
+ else
2527
+ cg.put_string_expression(%["#{element.to_s}"])
2528
+ end
2529
+ end
2530
+ end
2531
+
2532
+ private
2533
+ def make_tupple_code(cg)
2534
+ init_code = @names.collect do |n|
2535
+ "$_.amrita_value(:#{n})"
2536
+ end.join(",")
2537
+ cg.code("a = [#{init_code}]")
2538
+ end
2539
+
2540
+ def replace_args(element)
2541
+ element.attributes.each do|k,v|
2542
+ element.set_attribute(k, replace_attr_args(v))
2543
+ end
2544
+ end
2545
+ def replace_attr_args(s)
2546
+ s.gsub(/\$(\d)/) do |ss|
2547
+ '#{Util::sanitize_attribute_value(a[' + ($1.to_i-1).to_s + '].to_s)}'
2548
+ end
2549
+ end
259
2550
  end
260
2551
 
261
- def get_document
262
- REXML::Document.new(File.new(@fname))
2552
+ class FunctionFilter < Filters::Base
2553
+ def initialize(sym, *args)
2554
+ @sym, @args = sym, args
2555
+ end
2556
+
2557
+ def value_filter_code(de, cg, element)
2558
+ cg.code("$_ = $_.send(#{@sym.inspect}, *#{@args.inspect})")
2559
+ super
2560
+ end
263
2561
  end
264
2562
 
265
- def get_document_text
266
- File.new(@fname).read
2563
+ class CommandFilter < Filters::Base
2564
+ def initialize(*args)
2565
+ @args = args.collect do |a|
2566
+ a.inspect
2567
+ end.join(' ')
2568
+ end
2569
+
2570
+ def define_element_method(de, cg, &block)
2571
+ super do
2572
+ block.call
2573
+ cg.code "pipe = IO.popen(#@args, 'r+')"
2574
+ cg.code "pipe.write __stream__; pipe.close_write"
2575
+ cg.code "__stream__ = pipe.read ; pipe.close "
2576
+ end
2577
+ end
267
2578
  end
268
- end
269
2579
 
270
- class TemplateText < Template
271
- include Amrita2
272
- def initialize(text, &block)
273
- super(&block)
274
- @text = text
2580
+ class Trace < Filters::Base
2581
+ SEP = "\n" + "-" * 80 + "\n"
2582
+ def filter_element(de, element)
2583
+ if de.tracer
2584
+ old_s = element.to_s
2585
+ new_element = super
2586
+ new_s = new_element.to_s
2587
+ if old_s != new_s
2588
+ de.tracer << SEP << "before filter:\n" <<
2589
+ old_s << SEP << "after filter :\n" << new_s << SEP
2590
+ end
2591
+ new_element
2592
+ else
2593
+ super
2594
+ end
2595
+ end
2596
+
2597
+ def value_filter_code(de, cg, value)
2598
+ s = de.element.stag.output("")
2599
+ cg.code(%[Tracer << "%s:%s:%s" % [#{s.inspect},"in", $_.inspect] ])
2600
+ super
2601
+ end
2602
+
2603
+ def method_body_code(de, cg)
2604
+ super
2605
+ s = de.element.stag.output("")
2606
+ cg.code(%[Tracer << "%s:%s:%s" % [#{s.inspect},"out", __stream__] ])
2607
+ end
2608
+ end
2609
+
2610
+ class Join < Base
2611
+ def initialize(sep)
2612
+ @sep = case sep
2613
+ when :br ; "<br />"
2614
+ when :nbsp ; "&#160;"
2615
+ else ; sep
2616
+ end
2617
+ end
2618
+
2619
+ def filter_element(de, e)
2620
+ a = e.children.dup
2621
+ a.each do |n|
2622
+ case n
2623
+ when Hpricot::CData, Hpricot::Elem
2624
+ e.insert_after(Hpricot::Text.new(@sep), n) unless n == a.last
2625
+ when Hpricot::Text
2626
+ e.insert_after(Hpricot::Text.new(@sep), n) unless n == a.last
2627
+ n.content.strip!
2628
+ n.content.gsub!(/\s*\n\s*/, @sep)
2629
+ end
2630
+ end
2631
+ e
2632
+ end
2633
+ end
2634
+
2635
+ class ChangeStream < Base
2636
+ def initialize(name)
2637
+ @name = name
2638
+ end
2639
+
2640
+ def method_body_code(de, cg)
2641
+ cg.code("#ChangeStream")
2642
+ cg.with_stream(@name) do
2643
+ super
2644
+ end
2645
+ end
275
2646
  end
276
2647
 
277
- def get_document
278
- REXML::Document.new(@text)
2648
+ class PutStream < Base
2649
+ def initialize(name)
2650
+ @name = name
2651
+ end
2652
+
2653
+ def method_body_code(de, cg)
2654
+ cg.code("#PutStream")
2655
+ cg.put_stream(@name)
2656
+ end
279
2657
  end
280
2658
 
281
- def get_document_text
282
- @text
2659
+ class ModuleExtendFilter< Base
2660
+ def initialize(mod)
2661
+ @mod = mod
2662
+ end
2663
+
2664
+ def value_filter_code(de, cg, element)
2665
+ cg.code("$_.extend(#@mod)")
2666
+ super
2667
+ end
283
2668
  end
284
2669
  end
285
2670
 
286
- class TemplateElement < Template
287
- def initialize(e, &block)
288
- super(&block)
289
- @element = e
2671
+ class HashDelegator
2672
+ include DictionaryData
2673
+ def initialize(parent, &block)
2674
+ @parent = parent
2675
+ @added_data = block.call
2676
+ raise 'block did not return a Hash' unless @added_data.kind_of?(DictionaryData)
290
2677
  end
291
2678
 
292
- def get_document
293
- doc = REXML::Document.new
294
- doc << @element
295
- doc
2679
+ def [](key)
2680
+ self.send(key)
2681
+ rescue NoMethodError
2682
+ nil
2683
+ end
2684
+
2685
+ def id
2686
+ @parent.id
296
2687
  end
297
2688
 
298
- def get_document_text
299
- doc = REXML::Document.new
300
- doc << @element
301
- doc.to_s
2689
+ def method_missing(sym, *args, &block)
2690
+ if @added_data.has_key?(sym)
2691
+ @added_data[sym]
2692
+ else
2693
+ if @parent.kind_of?(DictionaryData)
2694
+ @parent.amrita_value(sym)
2695
+ else
2696
+ @parent.send(sym)
2697
+ end
2698
+ end
302
2699
  end
303
2700
  end
2701
+
2702
+ SanitizedString = Util::SanitizedString
2703
+ Template = Core::Template
304
2704
  end