hermeneutics 1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,462 @@
1
+ #
2
+ # hermeneutics/html.rb -- smart HTML generation
3
+ #
4
+
5
+ require "hermeneutics/escape"
6
+ require "hermeneutics/contents"
7
+
8
+
9
+ module Hermeneutics
10
+
11
+ # = Example
12
+ #
13
+ # require "hermeneutics/color"
14
+ # require "hermeneutics/html"
15
+ #
16
+ # class MyHtml < Hermeneutics::Html
17
+ # def build
18
+ # html {
19
+ # head {
20
+ # title { "Example" }
21
+ # comment "created as an example, #{Time.now}"
22
+ # }
23
+ # body( :bgcolor => Hermeneutics::Color.from_s( "ffffef")) {
24
+ # h1 {
25
+ # pcdata "Ruby "
26
+ # a( :href => "www.w3.org") { "Html" }
27
+ # _ { " example" }
28
+ # }
29
+ # p { "Some text.\nBye." }
30
+ # p {
31
+ # self << "link"
32
+ # br
33
+ # a( :href => "www.w3.org") { "Html" }
34
+ # }
35
+ # }
36
+ # }
37
+ # end
38
+ # end
39
+ #
40
+ # Hermeneutics::Html.document
41
+ #
42
+ class Html
43
+
44
+ class <<self
45
+ attr_accessor :main
46
+ def inherited cls
47
+ Html.main = cls
48
+ end
49
+ def open out = nil
50
+ i = (@main||self).new
51
+ i.generate out do
52
+ yield i
53
+ end
54
+ end
55
+ def document *args, &block
56
+ open do |i|
57
+ i.document *args, &block
58
+ end
59
+ end
60
+ def write_file name = nil
61
+ name ||= (File.basename $0, ".rb") + ".html"
62
+ File.open name, "w" do |f|
63
+ open f do |i|
64
+ if block_given? then
65
+ yield i
66
+ else
67
+ i.document
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def generate out = nil
75
+ g = @generator
76
+ begin
77
+ @generator = Generator.new out||$stdout
78
+ yield
79
+ ensure
80
+ @generator = g
81
+ end
82
+ end
83
+
84
+ def document *args, &block
85
+ doctype_header
86
+ build *args, &block
87
+ end
88
+
89
+ def doctype_header
90
+ @generator.doctype "html"
91
+ end
92
+
93
+ def build
94
+ html { body { h1 { "It works." } } }
95
+ end
96
+
97
+
98
+ def language
99
+ if ENV[ "LANG"] =~ /\A\w{2,}/ then
100
+ r = $&
101
+ r.gsub! /_/, "-"
102
+ r
103
+ end
104
+ end
105
+
106
+
107
+ class Generator
108
+ attr_accessor :close_standalone, :assign_attributes, :cdata_block
109
+ def initialize out
110
+ @out = out
111
+ @ent = Entities.new
112
+ @nl, @ind = true, [ ""]
113
+ end
114
+ def encoding
115
+ case @out
116
+ when IO then @out.external_encoding||Encoding.default_external
117
+ else @out.encoding
118
+ end
119
+ end
120
+ def file_path
121
+ @out.path
122
+ rescue NoMethodError
123
+ end
124
+ def plain str
125
+ do_ind
126
+ @out << (@ent.encode str)
127
+ end
128
+ # nls
129
+ # 0 = no newline
130
+ # 1 = newline after
131
+ # 2 = newline after both
132
+ # 3 = and advance indent
133
+ # 4 = Block without any indent
134
+ def tag tag, type, attrs = nil
135
+ tag = tag.to_s
136
+ nls = type & 0xf
137
+ if (type & 0x10).nonzero? then
138
+ brace nls>0 do
139
+ @out << tag
140
+ mkattrs attrs
141
+ @out << " /" if @close_standalone
142
+ end
143
+ else
144
+ begin
145
+ brk if nls>1
146
+ brace nls>1 do
147
+ @out << tag
148
+ mkattrs attrs
149
+ end
150
+ if nls >3 then
151
+ verbose_block yield
152
+ else
153
+ indent_if nls>2 do
154
+ if block_given? then
155
+ r = yield
156
+ plain r if String === r
157
+ end
158
+ end
159
+ end
160
+ ensure
161
+ brk if nls>1
162
+ brace nls>0 do
163
+ @out << "/" << tag
164
+ end
165
+ end
166
+ end
167
+ nil
168
+ end
169
+ # Processing Instruction
170
+ def pi_tag tag, attrs = nil
171
+ tag = tag.to_s
172
+ brace true do
173
+ begin
174
+ @out << "?" << tag
175
+ mkattrs attrs
176
+ ensure
177
+ @out << " ?"
178
+ end
179
+ end
180
+ end
181
+ def doctype *args
182
+ brace true do
183
+ @out << "!DOCTYPE"
184
+ args.each { |x|
185
+ @out << " "
186
+ if x =~ /\W/ then
187
+ @out << '"' << (@ent.encode x) << '"'
188
+ else
189
+ @out << x
190
+ end
191
+ }
192
+ end
193
+ end
194
+ def comment str
195
+ if str =~ /\A.*\z/ then
196
+ brace_comment do
197
+ @out << " " << str << " "
198
+ end
199
+ else
200
+ brace_comment do
201
+ brk
202
+ out_brk str
203
+ do_ind
204
+ end
205
+ end
206
+ end
207
+ def verbose_block str
208
+ if @cdata_block then
209
+ @out << "/* "
210
+ brace false do
211
+ @out << "![CDATA["
212
+ @out << " */" << $/
213
+ @out << str
214
+ @out << $/ << "/* "
215
+ @out << "]]"
216
+ end
217
+ @out << " */"
218
+ else
219
+ out_brk str
220
+ end
221
+ end
222
+ private
223
+ def brk
224
+ unless @nl then
225
+ @nl = true
226
+ @out << $/
227
+ end
228
+ end
229
+ def out_brk str
230
+ @out << str
231
+ @nl = str !~ /.\z/
232
+ brk
233
+ end
234
+ def do_ind
235
+ if @nl then
236
+ @out << @ind.last
237
+ @nl = false
238
+ end
239
+ end
240
+ def brace nl
241
+ do_ind
242
+ @out << "<"
243
+ yield
244
+ nil
245
+ ensure
246
+ @out << ">"
247
+ brk if nl
248
+ end
249
+ def brace_comment
250
+ brace true do
251
+ @out << "!--"
252
+ yield
253
+ @out << "--"
254
+ end
255
+ end
256
+ def indent_if flag
257
+ if flag then
258
+ indent do yield end
259
+ else
260
+ yield
261
+ end
262
+ end
263
+ INDENT = 2
264
+ def indent
265
+ @ind.push @ind.last + " "*INDENT
266
+ yield
267
+ ensure
268
+ @ind.pop
269
+ end
270
+ def mkattrs attrs
271
+ attrs or return
272
+ attrs.each { |k,v|
273
+ if Symbol === k then k = k.new_string ; k.gsub! /_/, "-" end
274
+ v = case v
275
+ when Array then v.compact.join " "
276
+ when true then k.to_s if @assign_attributes
277
+ when nil, false then next
278
+ else v.to_s
279
+ end
280
+ @out << " " << k
281
+ @out << "=\"" << (@ent.encode v) << "\"" if v.notempty?
282
+ }
283
+ end
284
+ end
285
+
286
+ def file_path ; @generator.file_path ; end
287
+
288
+ NBSP = Entities::NAMES[ "nbsp"]
289
+
290
+ TAGS = {
291
+ a:0, abbr:0, address:1, article:3, aside:3, audio:3, b:0, bdi:0, bdo:2,
292
+ blockquote:3, body:2, button:3, canvas:1, caption:1, cite:0, code:0,
293
+ colgroup:3, data:0, datalist:3, dd:1, del:0, details:3, dfn:0, dialog:0,
294
+ div:3, dl:3, dt:1, em:0, fieldset:3, figcaption:1, figure:3, footer:3,
295
+ form:3, h1:1, h2:1, h3:1, h4:1, h5:1, h6:1, head:3, header:3, html:2,
296
+ i:0, iframe:3, ins:0, kbd:0, label:0, legend:1, li:1, main:3, map:3,
297
+ mark:0, meter:0, nav:3, noscript:3, object:3, ol:3, optgroup:3,
298
+ option:1, output:0, p:2, picture:3, pre:1, progress:0, q:0, rp:0, rt:0,
299
+ ruby:2, s:0, samp:0, section:3, select:3, small:0, span:0, strong:0,
300
+ sub:0, summary:1, sup:0, svg:3, table:3, tbody:3, td:1, template:3,
301
+ textarea:1, tfoot:3, th:1, thead:3, time:0, title:1, tr:3, u:0, ul:3,
302
+ var:0, video:3,
303
+
304
+ # tags containing foreign code blocks
305
+ script:4, style:4,
306
+
307
+ # void tags
308
+ area:0x11, base:0x11, br:0x11, col:0x11, embed:0x11, hr:0x11, img:0x10,
309
+ input:0x10, keygen:0x11, link:0x11, meta:0x11, param:0x11, source:0x11,
310
+ track:0x11, wbr:0x10,
311
+ }
312
+
313
+ # remove Kernel methods of the same name: :p, :select, :sub
314
+ (TAGS.keys & (private_instance_methods +
315
+ protected_instance_methods +
316
+ instance_methods)).each { |m| undef_method m }
317
+
318
+ def tag? name
319
+ TAGS[ name]
320
+ end
321
+
322
+ def method_missing name, *args, &block
323
+ t = tag? name
324
+ t or super
325
+ if String === args.last then
326
+ b = args.pop
327
+ @generator.tag name, t, *args do b end
328
+ else
329
+ @generator.tag name, t, *args, &block
330
+ end
331
+ end
332
+
333
+ def pcdata *strs
334
+ strs.each { |s|
335
+ s or next
336
+ @generator.plain s
337
+ }
338
+ nil
339
+ end
340
+
341
+ def << str
342
+ @generator.plain str if str
343
+ self
344
+ end
345
+
346
+ def _ str = nil
347
+ @generator.plain str||yield
348
+ nil
349
+ end
350
+
351
+ def comment str
352
+ @generator.comment str
353
+ end
354
+
355
+ def javascript str = nil, &block
356
+ mime = { type: "text/javascript" }
357
+ script mime, str, &block
358
+ end
359
+
360
+ def html **attrs
361
+ attrs[ :"lang"] ||= language
362
+ method_missing :html, **attrs do yield end
363
+ end
364
+
365
+ def head attrs = nil
366
+ method_missing :head, attrs do
367
+ meta charset: @generator.encoding
368
+ yield
369
+ end
370
+ end
371
+
372
+ def form attrs, &block
373
+ attrs[ :method] ||= if attrs[ :enctype] == "multipart/form-data" then
374
+ "post"
375
+ else
376
+ "get"
377
+ end
378
+ @tabindex = 0
379
+ method_missing :form, attrs, &block
380
+ ensure
381
+ @tabindex = nil
382
+ end
383
+
384
+ Field = Struct[ :type, :attrs]
385
+
386
+ def field type, attrs
387
+ attrs[ :id] ||= attrs[ :name]
388
+ @tabindex += 1
389
+ attrs[ :tabindex] ||= @tabindex
390
+ Field[ type, attrs]
391
+ end
392
+
393
+ def label field = nil, attrs = nil, &block
394
+ if String === attrs then
395
+ label field do attrs end
396
+ else
397
+ if Field === field or attrs then
398
+ attrs = attrs.merge for: field.attrs[ :id]
399
+ else
400
+ attrs = field
401
+ end
402
+ method_missing :label, attrs, &block
403
+ end
404
+ end
405
+
406
+ def input arg, &block
407
+ if Field === arg then
408
+ case arg.type
409
+ when /select/i then
410
+ method_missing :select, arg.attrs, &block
411
+ when /textarea/i then
412
+ block and
413
+ raise ArgumentError, "Field textarea: use the value attribute."
414
+ v = arg.attrs.delete :value
415
+ method_missing :textarea, arg.attrs do v end
416
+ else
417
+ arg.attrs[ :type] ||= arg.type
418
+ method_missing :input, arg.attrs, &block
419
+ end
420
+ else
421
+ method_missing :input, arg, &block
422
+ end
423
+ end
424
+
425
+ end
426
+
427
+ class XHtml < Html
428
+
429
+ def html **attrs
430
+ attrs[ :xmlns] ||= "http://www.w3.org/1999/xhtml"
431
+ attrs[ :"xml:lang"] = language
432
+ attrs[ :lang] = ""
433
+ super
434
+ end
435
+
436
+ def a attrs = nil
437
+ attrs[ :name] ||= attrs[ :id] if attrs
438
+ super
439
+ end
440
+
441
+ private
442
+
443
+ def generate out
444
+ super do
445
+ @generator.close_standalone = true
446
+ @generator.assign_attributes = true
447
+ @generator.cdata_block = true
448
+ yield
449
+ end
450
+ end
451
+
452
+ def doctype_header
453
+ prop = { version: "1.0", encoding: @generator.encoding }
454
+ @generator.pi_tag :xml, prop
455
+ @generator.doctype "html", "PUBLIC", "-//W3C//DTD XHTML 1.1//EN",
456
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
457
+ end
458
+
459
+ end
460
+
461
+ end
462
+