rxhp 0.0.1

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.
@@ -0,0 +1,116 @@
1
+ require 'rxhp/error'
2
+
3
+ module Rxhp
4
+ module AttributeValidator
5
+ class ValidationError < Rxhp::ScriptError
6
+ end
7
+
8
+ class UnacceptableAttributeError < ValidationError
9
+ attr_reader :element, :attribute, :value
10
+ def initialize element, attribute, value
11
+ @element, @attribute, @value = element, attribute, value
12
+ super "Class #{element.class.name} does not support #{attribute}=#{value.inspect}"
13
+ end
14
+ end
15
+
16
+ class MissingRequiredAttributeError < ValidationError
17
+ attr_reader :element, :attribute
18
+ def initialize element, attribute
19
+ @element, @attribute = element, attribute
20
+ super "Element #{element.inspect} is missing required attributes: #{attribute.inspect}"
21
+ end
22
+ end
23
+
24
+ def valid_attributes?
25
+ begin
26
+ self.validate_attributes!
27
+ true
28
+ rescue ValidationError
29
+ false
30
+ end
31
+ end
32
+
33
+ def validate_attributes!
34
+ # Check for required attributes
35
+ self.class.required_attributes.each do |matcher|
36
+ matched = self.attributes.any? do |key, value|
37
+ key = key.to_s
38
+ Rxhp::AttributeValidator.match? matcher, key, value
39
+ end
40
+ if !matched
41
+ raise MissingRequiredAttributeError.new(self, matcher)
42
+ end
43
+ end
44
+
45
+ # Check other attributes are acceptable
46
+ return true if self.attributes.empty?
47
+ self.attributes.each do |key, value|
48
+ key = key.to_s
49
+ matched = self.class.acceptable_attributes.any? do |matcher|
50
+ Rxhp::AttributeValidator.match? matcher, key, value
51
+ end
52
+
53
+ if !matched
54
+ raise UnacceptableAttributeError.new(self, key, value)
55
+ end
56
+ end
57
+ true
58
+ end
59
+
60
+ def self.match? matcher, name, value
61
+ case matcher
62
+ when Array
63
+ matcher.any? { |x| match? x, name, value }
64
+ when Hash
65
+ matcher.any? do |name_matcher, value_matcher|
66
+ name_match = match?(name_matcher, name, nil)
67
+ value_match = match?(value_matcher, value, nil)
68
+ name_match && value_match
69
+ end
70
+ when Symbol
71
+ matcher.to_s.gsub('_', '-') == name
72
+ else
73
+ matcher === name
74
+ end
75
+ end
76
+
77
+ def self.included(klass)
78
+ klass.extend(ClassMethods)
79
+ class << klass
80
+ attr_accessor :acceptable_attributes, :required_attributes
81
+ def accept_attributes matcher
82
+ acceptable_attributes.push matcher
83
+ end
84
+ alias :accept_attribute :accept_attributes
85
+
86
+ def require_attributes matcher
87
+ accept_attributes matcher
88
+ required_attributes.push matcher
89
+ end
90
+ alias :require_attribute :require_attributes
91
+
92
+ def accept_all_attributes
93
+ attribute_matches.push Object
94
+ end
95
+ end
96
+
97
+ klass.acceptable_attributes = []
98
+ klass.required_attributes = []
99
+ end
100
+
101
+ def self.inherited(subklass)
102
+ subklass.class_eval do
103
+ include Rxhp::AttributeValidator
104
+ end
105
+ subklass.acceptable_attributes = subklass.superclass.acceptable_attributes.dup
106
+ subklass.required_attributes = subklass.superclass.required_attributes.dup
107
+ end
108
+
109
+ module ClassMethods
110
+ def inherited(subklass)
111
+ Rxhp::AttributeValidator.inherited(subklass)
112
+ end
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,57 @@
1
+ require 'rxhp/attribute_validator'
2
+ require 'rxhp/element'
3
+ require 'rxhp/fragment'
4
+
5
+ module Rxhp
6
+ # This defines an element that is only a handy way to think of a
7
+ # tree of other elements - it has no real rendering by itself, just those
8
+ # of its' children.
9
+ #
10
+ # Most of the time, those children will either be ComposableElement's in
11
+ # turn, or subclasses of Rxhp::HtmlElement
12
+ class ComposableElement < Rxhp::Element
13
+ include Rxhp::AttributeValidator
14
+
15
+ def initialize *args
16
+ super *args
17
+ validate_attributes!
18
+ end
19
+
20
+ # You don't want to implement this function in your subclasses -
21
+ # just reimplement compose instead.
22
+ #
23
+ # This calls compose, provides the 'yield' magic, and callls render on
24
+ # the output.
25
+ def render options = {}
26
+ validate_attributes!
27
+ self.compose do
28
+ # Allow 'yield' to embed all children
29
+ self.children.each do |child|
30
+ fragment child
31
+ end
32
+ end.render(options)
33
+ end
34
+
35
+ # Implement this method - return an Rxhp::Element subclass.
36
+ def compose
37
+ raise NotImplementedError.new
38
+ end
39
+
40
+ # Automatically add a foo_bar method to Rxhp scopes when a FooBar
41
+ # subclass of this is created.
42
+ def self.inherited subclass
43
+ Rxhp::AttributeValidator.inherited(subclass)
44
+ full_name = subclass.name
45
+ parts = full_name.split('::')
46
+ klass_name = parts.pop
47
+ namespace = Kernel
48
+ parts.each do |part|
49
+ namespace = namespace.const_get(part)
50
+ end
51
+ # UpperCamelCase => under_scored
52
+ tag_name = klass_name.gsub(/(.)([A-Z])/, '\1_\2').downcase
53
+
54
+ Rxhp::Scope.define_element tag_name, subclass, namespace
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ module Rxhp
2
+ # Basic output formats
3
+ HTML_FORMAT = :html
4
+ TINY_HTML_FORMAT = :tiny_html
5
+ XHTML_FORMAT = :xhtml
6
+
7
+ # Doctypes
8
+ HTML_5 = "<!DOCTYPE html>\n"
9
+ HTML_4_01_TRANSITIONAL = <<EOF
10
+ <!DOCTYPE HTML PUBLIC
11
+ "-//W3C//DTD HTML 4.01 Transitional//EN"
12
+ "http://www.w3.org/TR/html4/loose.dtd">
13
+ EOF
14
+ XHTML_1_0_STRICT = <<EOF
15
+ <!DOCTYPE html PUBLIC
16
+ "-//W3C//DTD XHTML 1.0 Strict//EN"
17
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
18
+ EOF
19
+ end
@@ -0,0 +1,154 @@
1
+ require 'uri'
2
+
3
+ module Rxhp
4
+ module Html
5
+ # Given ['foo', 'bar', 'baz'], match:
6
+ # - 'foo'
7
+ # - 'bar baz'
8
+ # - 'foo bar baz'
9
+ # etc.
10
+ def self.token_list tokens
11
+ token = tokens.join('|')
12
+ /^#{token}( (#{token}) )*$/
13
+ end
14
+
15
+ # All the non-javascript attributes shared by every HTML element.
16
+ GLOBAL_ATTRIBUTES = {
17
+ /^data-[^:]+$/ => Object,
18
+
19
+ :accesskey => String,
20
+ :class => String,
21
+ :contenteditable => %w'true false inherit',
22
+ :contextmenu => String,
23
+ :dir => %w'ltr rtl auto',
24
+ :draggable => %w'true false',
25
+ :dropzone => String,
26
+ :hidden => [true, false],
27
+ :id => /^[^ ]+$/,
28
+ :lang => String,
29
+ :spellcheck => %w'true false',
30
+ :style => String,
31
+ :tabindex => Integer,
32
+ :title => String,
33
+ }
34
+
35
+ # Every HTML element has these
36
+ GLOBAL_EVENT_HANDLERS = {
37
+ :onabort => String,
38
+ :onblur => String,
39
+ :oncanplay => String,
40
+ :oncanplaythrough => String,
41
+ :onchange => String,
42
+ :onclick => String,
43
+ :oncontextmenu => String,
44
+ :oncuechange => String,
45
+ :ondblclick => String,
46
+ :ondrag => String,
47
+ :ondragend => String,
48
+ :ondragenter => String,
49
+ :ondragleave => String,
50
+ :ondragover => String,
51
+ :ondragstart => String,
52
+ :ondrop => String,
53
+ :ondurationchange => String,
54
+ :onemptied => String,
55
+ :onended => String,
56
+ :onerror => String,
57
+ :onfocus => String,
58
+ :oninput => String,
59
+ :oninvalid => String,
60
+ :onkeydown => String,
61
+ :onkeypress => String,
62
+ :onkeyup => String,
63
+ :onload => String,
64
+ :onloadeddata => String,
65
+ :onloadedmetadata => String,
66
+ :onloadstart => String,
67
+ :onmousedown => String,
68
+ :onmousemove => String,
69
+ :onmouseout => String,
70
+ :onmouseover => String,
71
+ :onmouseup => String,
72
+ :onmousewheel => String,
73
+ :onpause => String,
74
+ :onplay => String,
75
+ :onplaying => String,
76
+ :onprogress => String,
77
+ :onratechange => String,
78
+ :onreset => String,
79
+ :onscroll => String,
80
+ :onseeked => String,
81
+ :onseeking => String,
82
+ :onselect => String,
83
+ :onshow => String,
84
+ :onstalled => String,
85
+ :onsubmit => String,
86
+ :onsuspend => String,
87
+ :ontimeupdate => String,
88
+ :onvolumechange => String,
89
+ :onwaiting => String,
90
+ }
91
+
92
+ # Specific attributes used by multiple elements
93
+ HREF_ATTRIBUTE = { :href => [String, URI] }
94
+ MEDIA_ATTRIBUTE = { :media => String }
95
+ REL_ATTRIBUTE = {
96
+ :rel => Rxhp::Html.token_list(%w[
97
+ alternate
98
+ author
99
+ bookmark
100
+ help
101
+ icon
102
+ license
103
+ next
104
+ nofollow
105
+ noreferrer
106
+ prefetch
107
+ prev
108
+ search
109
+ stylesheet
110
+ tag
111
+ ]),
112
+ }
113
+ DATETIME_ATTRIBUTE = { :datetime => String }
114
+ CITE_ATTRIBUTE = { :cite => [String, URI] }
115
+ SRC_ATTRIBUTE = { :src => [String, URI] }
116
+ DIMENSION_ATTRIBUTES = {
117
+ :width => String,
118
+ :height => String,
119
+ }
120
+ CROSSORIGIN_ATTRIBUTE = {
121
+ :crossorigin => /^(anonymous|use-credentials)$/,
122
+ }
123
+ COMMON_MEDIA_ATTRIBUTES = {
124
+ :preload => /^(none|metadata|auto)/,
125
+ :autoplay => [true, false],
126
+ :mediagroup => String,
127
+ :loop => [true, false],
128
+ :muted => [true, false],
129
+ :controls => [true, false],
130
+ }
131
+ SPAN_ATTRIBUTE = { :span => Integer }
132
+ TABLE_CELL_ATTRIBUTES = {
133
+ :colspan => Integer,
134
+ :rowspan => Integer,
135
+ :headers => String,
136
+ },
137
+ AUTOCOMPLETE_ATTRIBUTE = { :autocomplete => ['on', 'off'] }
138
+
139
+ ENC_TYPES = %w[
140
+ application/x-www-form-urlencoded
141
+ multipart/form-data
142
+ text/plain
143
+ ]
144
+ INTEGER_LIST = /^\d+(,\d+)*$/
145
+ FORM_ATTRIBUTES = {
146
+ :form => String,
147
+ :formaction => [String, URI],
148
+ :formenctype => ENC_TYPES,
149
+ :formmethod => ['get', 'post'],
150
+ :formnovalidate => [true, false],
151
+ :formtarget => String,
152
+ }
153
+ end
154
+ end
@@ -0,0 +1,533 @@
1
+ require 'rxhp/html_element'
2
+ require 'rxhp/html_self_closing_element'
3
+ require 'rxhp/html_singleton_element'
4
+
5
+ require 'rxhp/data/html/attributes'
6
+
7
+ module Rxhp
8
+ # Definitions of all standard HTML 4.01 and HTML 5 elements.
9
+ #
10
+ # All the classes are created when this file is loaded, regardless
11
+ # of usage.
12
+ #
13
+ # There are three common superclasses:
14
+ # - HtmlElement - standard elements
15
+ # - HtmlSelfClosingElement - elements where in HTML, the closing tag is
16
+ # optional - for example, <p>, <li>, and <body>
17
+ # - HtmlSingletonElement - not only is the closing tag optional, but
18
+ # child elements are forbidden - for example, <br> and <img>
19
+ #
20
+ # 'Special' classes are defined in rxhp/tags/ - for example, the HTML
21
+ # element in Rxhp is defined to add a doctype, depending on the formatter
22
+ # settings.
23
+ module Html
24
+ TAGS = {
25
+ :a => {
26
+ :attributes => [
27
+ HREF_ATTRIBUTE,
28
+ MEDIA_ATTRIBUTE,
29
+ REL_ATTRIBUTE,
30
+ {
31
+ :target => String,
32
+ :hreflang => String,
33
+ :type => String,
34
+ },
35
+ ],
36
+ },
37
+ :abbr => {},
38
+ :acronym => {},
39
+ :address => {},
40
+ :applet => {},
41
+ :area => {
42
+ :is_a => HtmlSingletonElement,
43
+ :attributes => [
44
+ HREF_ATTRIBUTE,
45
+ REL_ATTRIBUTE,
46
+ MEDIA_ATTRIBUTE,
47
+ {
48
+ :alt => String,
49
+ :coords => INTEGER_LIST,
50
+ :shape => %w(circle default poly rect),
51
+ :target => String,
52
+ :media => String,
53
+ :hreflang => String,
54
+ :type => String,
55
+ },
56
+ ],
57
+ },
58
+ :article => {},
59
+ :aside => {},
60
+ :audio => {
61
+ :attributes => [
62
+ SRC_ATTRIBUTE,
63
+ CROSSORIGIN_ATTRIBUTE,
64
+ COMMON_MEDIA_ATTRIBUTES,
65
+ ],
66
+ },
67
+ :b => {},
68
+ :base => {
69
+ :is_a => HtmlSingletonElement,
70
+ :attributes => [
71
+ HREF_ATTRIBUTE,
72
+ { :target => String },
73
+ ],
74
+ },
75
+ :basefont => {},
76
+ :bdo => {},
77
+ :bdi => {},
78
+ :big => {},
79
+ :blockquote => { :attributes => CITE_ATTRIBUTE },
80
+ :body => {
81
+ :is_a => HtmlSelfClosingElement,
82
+ :attributes => %w[
83
+ onafterprint
84
+ onbeforeprint
85
+ onbeforeunload
86
+ onblur
87
+ onerror
88
+ onfocus
89
+ onhashchange
90
+ onload
91
+ onmessage
92
+ onoffline
93
+ ononline
94
+ onpagehide
95
+ onpageshow
96
+ onpopstate
97
+ onresize
98
+ onscroll
99
+ onstorage
100
+ onunload
101
+ ],
102
+ },
103
+ :br => {:is_a => HtmlSingletonElement},
104
+ :button => {
105
+ :attributes => [
106
+ FORM_ATTRIBUTES,
107
+ {
108
+ :autofocus => [true, false],
109
+ :disabled => [true, false],
110
+ :name => String,
111
+ :value => Object,
112
+ :type => %w[submit reset button],
113
+ },
114
+ ],
115
+ },
116
+ :canvas => {
117
+ :attributes => DIMENSION_ATTRIBUTES,
118
+ },
119
+ :caption => {},
120
+ :center => {},
121
+ :cite => {},
122
+ :code => {},
123
+ :col => {
124
+ :is_a => HtmlSingletonElement,
125
+ :attributes => SPAN_ATTRIBUTE,
126
+ },
127
+ :colgroup => {
128
+ :is_a => HtmlSelfClosingElement,
129
+ :attributes => SPAN_ATTRIBUTE,
130
+ },
131
+ :command => {
132
+ :is_a => HtmlSingletonElement,
133
+ :attributes => {
134
+ :type => %w[command checkbox radio],
135
+ :label => String,
136
+ :icon => [String, URI],
137
+ :disabled => [true, false],
138
+ :radiogroup => String,
139
+ },
140
+ },
141
+ :datalist => {},
142
+ :dd => {:is_a => HtmlSelfClosingElement},
143
+ :del => {
144
+ :attributes => [
145
+ CITE_ATTRIBUTE,
146
+ DATETIME_ATTRIBUTE,
147
+ ],
148
+ },
149
+ :details => { :attributes => { :open => [true, false] } },
150
+ :dfn => {},
151
+ :dir => {},
152
+ :div => {},
153
+ :dl => {},
154
+ :dt => {:is_a => HtmlSelfClosingElement},
155
+ :em => {},
156
+ :embed => {
157
+ :is_a => HtmlSingletonElement,
158
+ :attributes => {Object => Object }, # anything :/
159
+ },
160
+ :fieldset => {
161
+ :attributes => {
162
+ :disabled => [true, false],
163
+ :form => String,
164
+ :name => String,
165
+ },
166
+ },
167
+ :figcaption => {},
168
+ :figure => {},
169
+ :font => {},
170
+ :footer => {},
171
+ :form => {
172
+ :attributes => [
173
+ AUTOCOMPLETE_ATTRIBUTE,
174
+ {
175
+ 'accept-charset' => String,
176
+ :action => [String, URI],
177
+ :enctype => ENC_TYPES,
178
+ :method => ['get', 'post'],
179
+ :name => String,
180
+ :novalidate => [true, false],
181
+ :target => String,
182
+ },
183
+ ],
184
+ },
185
+ :frame => {},
186
+ :frameset => {},
187
+ :h1 => {},
188
+ :h2 => {},
189
+ :h3 => {},
190
+ :h4 => {},
191
+ :h5 => {},
192
+ :h6 => {},
193
+ :head => {:is_a => HtmlSelfClosingElement},
194
+ :header => {},
195
+ :hgroup => {},
196
+ :hr => {:is_a => HtmlSingletonElement},
197
+ :html => {:require => 'rxhp/tags/html_tag'},
198
+ :i => {},
199
+ :iframe => {
200
+ :attributes => [
201
+ SRC_ATTRIBUTE,
202
+ DIMENSION_ATTRIBUTES,
203
+ {
204
+ :srcdoc => String,
205
+ :name => String,
206
+ :sandbox => ['', token_list(%w[
207
+ allow-forms
208
+ allow-same-origin
209
+ allow-scripts
210
+ allow-top-navigation
211
+ ])],
212
+ :seamless => [true, false],
213
+ },
214
+ ],
215
+ },
216
+ :img => {
217
+ :is_a => HtmlSingletonElement,
218
+ :attributes => [
219
+ SRC_ATTRIBUTE,
220
+ DIMENSION_ATTRIBUTES,
221
+ CROSSORIGIN_ATTRIBUTE,
222
+ {
223
+ :alt => String,
224
+ :usemap => String,
225
+ :ismap => [true, false],
226
+ },
227
+ ],
228
+ },
229
+ :input => {
230
+ :is_a => HtmlSingletonElement,
231
+ :attributes => [
232
+ AUTOCOMPLETE_ATTRIBUTE,
233
+ DIMENSION_ATTRIBUTES,
234
+ SRC_ATTRIBUTE,
235
+ FORM_ATTRIBUTES,
236
+ {
237
+ :accept => String,
238
+ :alt => String,
239
+ :autofocus => [true, false],
240
+ :checked => [true, false],
241
+ :dirname => String,
242
+ :disabled => [true, false],
243
+ :list => String,
244
+ :max => [Numeric, String],
245
+ :maxlength => Integer,
246
+ :min => [Numeric, String],
247
+ :multiple => [true, false],
248
+ :name => String,
249
+ :pattern => String, # ECMAScript RE != RegExp.to_s
250
+ :placeholder => String,
251
+ :required => [true, false],
252
+ :step => [Numeric, String],
253
+ :value => Object,
254
+ :type => %w[
255
+ hidden
256
+ text
257
+ search
258
+ tel
259
+ url
260
+ email
261
+ password
262
+ datetime
263
+ date
264
+ month
265
+ week
266
+ time
267
+ datetime-local
268
+ number
269
+ range
270
+ color
271
+ checkbox
272
+ radio
273
+ file
274
+ submit
275
+ image
276
+ reset
277
+ button
278
+ ],
279
+ },
280
+ ],
281
+ },
282
+ :ins => {
283
+ :attributes => [
284
+ CITE_ATTRIBUTE,
285
+ DATETIME_ATTRIBUTE,
286
+ ],
287
+ },
288
+ :isindex => {},
289
+ :kbd => {},
290
+ :keygen => {
291
+ :is_a => HtmlSingletonElement,
292
+ :attributes => {
293
+ :autofocus => [true, false],
294
+ :challenge => String,
295
+ :disabled => [true, false],
296
+ :form => String,
297
+ :keytype => 'rsa',
298
+ :name => String,
299
+ },
300
+ },
301
+ :label => {
302
+ :attributes => {
303
+ :form => String,
304
+ :for => String,
305
+ },
306
+ },
307
+ :legend => {},
308
+ :li => {
309
+ :is_a => HtmlSelfClosingElement,
310
+ :attributes => {
311
+ :value => Integer,
312
+ },
313
+ },
314
+ :link => {
315
+ :is_a => HtmlSingletonElement,
316
+ :attributes => [
317
+ HREF_ATTRIBUTE,
318
+ REL_ATTRIBUTE,
319
+ {
320
+ :href_lang => String,
321
+ :type => String,
322
+ :sizes => [
323
+ 'any',
324
+ /^\d+x\d+( \d+x\d+)*/,
325
+ ],
326
+ },
327
+ ],
328
+ },
329
+ :map => { :attributes => { :name => String } },
330
+ :mark => {},
331
+ :menu => {
332
+ :attributes => {
333
+ :type => ['context', 'toolbar'],
334
+ :label => String,
335
+ },
336
+ },
337
+ :meta => {
338
+ :is_a => HtmlSingletonElement,
339
+ :attributes => {
340
+ :name => String,
341
+ :content => String,
342
+ :charset => String,
343
+ 'http-equiv' => %w(content-language content-type default-style refresh set-cookie),
344
+ },
345
+ },
346
+ :meter => {
347
+ :attributes => {
348
+ %w[value min max low high optimum] => [String, Numeric],
349
+ },
350
+ },
351
+ :nav => {},
352
+ :noframes => {},
353
+ :noscript => {},
354
+ :object => {
355
+ :attributes => [
356
+ DIMENSION_ATTRIBUTES,
357
+ {
358
+ :data => [String, URI],
359
+ :type => String,
360
+ :typemustmatch => [true, false],
361
+ :name => String,
362
+ :usemap => String,
363
+ :form => String,
364
+ },
365
+ ],
366
+ },
367
+ :ol => {
368
+ :attributes => {
369
+ :reversed => [true, false],
370
+ :start => Integer,
371
+ :type => %w{1 a A i I},
372
+ },
373
+ },
374
+ :optgroup => {
375
+ :is_a => HtmlSelfClosingElement,
376
+ :attributes => {
377
+ :disabled => [true, false],
378
+ :label => String,
379
+ },
380
+ },
381
+ :option => {
382
+ :is_a => HtmlSelfClosingElement,
383
+ :attributes => {
384
+ :disabled => [true, false],
385
+ :label => String,
386
+ :selected => [true, false],
387
+ :value => Object,
388
+ },
389
+ },
390
+ :output => {
391
+ :attributes => {
392
+ :for => String,
393
+ :form => String,
394
+ :name => String,
395
+ },
396
+ },
397
+ :p => {:is_a => HtmlSelfClosingElement},
398
+ :param => {
399
+ :is_a => HtmlSingletonElement,
400
+ :attributes => {
401
+ :name => String,
402
+ :value => String,
403
+ },
404
+ },
405
+ :pre => {},
406
+ :progress => {
407
+ :attributes => {
408
+ :value => Object,
409
+ :max => [Numeric, String],
410
+ },
411
+ },
412
+ :rb => {},
413
+ :rt => {},
414
+ :ruby => {},
415
+ :q => { :attributes => CITE_ATTRIBUTE },
416
+ :s => {},
417
+ :samp => {},
418
+ :script => {
419
+ :attributes => [
420
+ SRC_ATTRIBUTE,
421
+ {
422
+ :async => [true, false],
423
+ :defer => [true, false],
424
+ :type => String,
425
+ :charset => String,
426
+ },
427
+ ],
428
+ },
429
+ :section => {},
430
+ :select => {
431
+ :attributes => {
432
+ :autofocus => [true, false],
433
+ :disabled => [true, false],
434
+ :form => String,
435
+ :multiple => [true, false],
436
+ :name => String,
437
+ :required => [true, false],
438
+ :size => Integer,
439
+ },
440
+ },
441
+ :small => {},
442
+ :source => {
443
+ :is_a => HtmlSingletonElement,
444
+ :attributes => [
445
+ SRC_ATTRIBUTE,
446
+ MEDIA_ATTRIBUTE,
447
+ { :type => String },
448
+ ],
449
+ },
450
+ :span => {},
451
+ :strike => {},
452
+ :strong => {},
453
+ :style => {
454
+ :attributes => [
455
+ MEDIA_ATTRIBUTE,
456
+ {
457
+ :type => String,
458
+ :scoped => [true, false],
459
+ },
460
+ ],
461
+ },
462
+ :sub => {},
463
+ :summary => {},
464
+ :sup => {},
465
+ :table => { :attributes => {:border => '1' } },
466
+ :tbody => {:is_a => HtmlSelfClosingElement},
467
+ :td => {
468
+ :is_a => HtmlSelfClosingElement,
469
+ :attributes => TABLE_CELL_ATTRIBUTES,
470
+ },
471
+ :textarea => {
472
+ :attributes => {
473
+ :autofocus => [true, false],
474
+ :cols => Integer,
475
+ :dirname => String,
476
+ :disabled => [true, false],
477
+ :form => String,
478
+ :maxlength => Integer,
479
+ :name => String,
480
+ :placeholder => String,
481
+ :readonly => [true, false],
482
+ :required => [true, false],
483
+ :rows => Integer,
484
+ :wrap => ['soft', 'hard'],
485
+ },
486
+ },
487
+ :tfoot => {:is_a => HtmlSelfClosingElement},
488
+ :th => {
489
+ :is_a => HtmlSelfClosingElement,
490
+ :attributes => [
491
+ TABLE_CELL_ATTRIBUTES,
492
+ {
493
+ :scope => %w(row col rowgroup colgroup),
494
+ },
495
+ ],
496
+ },
497
+ :thead => {:is_a => HtmlSelfClosingElement},
498
+ :time => {
499
+ :attributes => DATETIME_ATTRIBUTE,
500
+ },
501
+ :title => {},
502
+ :tr => {:is_a => HtmlSelfClosingElement},
503
+ :track => {
504
+ :is_a => HtmlSingletonElement,
505
+ :attributes => [
506
+ SRC_ATTRIBUTE,
507
+ {
508
+ :kind => %w(subtitles captions descriptions chapters metadata),
509
+ :srclang => String,
510
+ :label => String,
511
+ :default => [true, false],
512
+ },
513
+ ],
514
+ },
515
+ :tt => {},
516
+ :u => {},
517
+ :ul => {},
518
+ :var => {},
519
+ :video => {
520
+ :attributes => [
521
+ SRC_ATTRIBUTE,
522
+ CROSSORIGIN_ATTRIBUTE,
523
+ DIMENSION_ATTRIBUTES,
524
+ COMMON_MEDIA_ATTRIBUTES,
525
+ {
526
+ :poster => [String, URI],
527
+ },
528
+ ],
529
+ },
530
+ :wbr => {:is_a => HtmlSingletonElement},
531
+ }
532
+ end
533
+ end
@@ -0,0 +1,70 @@
1
+ require 'rxhp/constants'
2
+ require 'rxhp/scope'
3
+
4
+ module Rxhp
5
+ # Base class for all element-like things in RXHP.
6
+ #
7
+ # Everything in the tree is a subclass of this, a subclass of String,
8
+ # something that responds nicely to to_s, or something that will cause en
9
+ # error at render-time :p
10
+ class Element
11
+ include ::Rxhp::Scope
12
+ attr_accessor :attributes, :children
13
+
14
+ def initialize attributes = {}
15
+ @attributes = attributes
16
+ @children = Array.new
17
+ end
18
+
19
+ def children?
20
+ !children.empty?
21
+ end
22
+
23
+ # Return a flat HTML string for this element and all its' decendants.
24
+ #
25
+ # You probably don't want to implement this yourself - interesting
26
+ # implementations are in Rxhp::Fragment, Rxhp::HtmlElement, and
27
+ # Rxhp::ComposableElement
28
+ def render options = {}
29
+ raise NotImplementedError.new
30
+ end
31
+
32
+ # Called when something that isn't an element is found in the tree.
33
+ #
34
+ # Implemented in Rxhp::HtmlElement.
35
+ def render_string string, options
36
+ raise NotImplementedError.new
37
+ end
38
+
39
+
40
+ # Iterate over all the children, calling render.
41
+ def render_children options = {}
42
+ return if children.empty?
43
+
44
+ out = String.new
45
+ children.each do |child|
46
+ case child
47
+ when Element
48
+ out += child.render(options)
49
+ when String
50
+ out += self.render_string(child, options)
51
+ else
52
+ out += self.render_string(child.to_s, options)
53
+ end
54
+ end
55
+ out
56
+ end
57
+
58
+ # Fill default options
59
+ def fill_options options
60
+ {
61
+ :pretty => true,
62
+ :format => Rxhp::HTML_FORMAT,
63
+ :skip_doctype => false,
64
+ :doctype => Rxhp::HTML_5,
65
+ :depth => 0,
66
+ :indent => 2,
67
+ }.merge(options)
68
+ end
69
+ end
70
+ end
data/lib/rxhp/error.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Rxhp
2
+ class Error < ::StandardError
3
+ end
4
+
5
+ class ScriptError < ::ScriptError
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'rxhp/element'
2
+
3
+ module Rxhp
4
+ # Fake element that only renders its' children.
5
+ #
6
+ # Can be used like an array, or if you just need something that acts like
7
+ # an element - this is used internally as the root of all render trees.
8
+ class Fragment < Element
9
+ def render options = {}
10
+ self.render_children(options)
11
+ end
12
+ end
13
+ end
data/lib/rxhp/html.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'rxhp/data/html/attributes'
2
+ require 'rxhp/data/html/tags'
3
+
4
+ module Rxhp
5
+ module Html
6
+ def fragment x
7
+ Rxhp::Scope.current.children.push x
8
+ end
9
+ alias :frag :fragment
10
+ alias :text :fragment
11
+
12
+ class <<self
13
+ def fragment x
14
+ Rxhp::Scope.current.children.push x
15
+ end
16
+ alias :frag :fragment
17
+ alias :text :fragment
18
+ end
19
+ end
20
+ end
21
+
22
+ Rxhp::Html::TAGS.each do |tag, data|
23
+ if data[:require]
24
+ require data[:require]
25
+ else
26
+ data = {
27
+ :is_a => Rxhp::HtmlElement,
28
+ }.merge(data)
29
+
30
+ klass_name = tag.to_s.dup
31
+ klass_name[0] = klass_name[0,1].upcase
32
+ klass = Class.new(data[:is_a])
33
+ klass.send(:define_method, :tag_name) { tag.to_s }
34
+
35
+ if data[:attributes]
36
+ klass.accept_attributes data[:attributes]
37
+ end
38
+
39
+ Rxhp::Html.const_set(klass_name, klass)
40
+
41
+ Rxhp::Scope.define_element tag, klass, Rxhp::Html
42
+ end
43
+ end
@@ -0,0 +1,143 @@
1
+ require 'rxhp/element'
2
+ require 'rxhp/attribute_validator'
3
+
4
+ require 'rxhp/data/html/attributes'
5
+
6
+ module Rxhp
7
+ # Base class for 'real' elements that actually end up in the markup.
8
+ #
9
+ # For example, <span> is a subclass of this.
10
+ #
11
+ # To use:
12
+ # 1. subclass
13
+ # 2. define tag_name
14
+ # 3. call Rxhp::Scope.define_element('foo', Foo) to register the class
15
+ # ... or just add a define_tag line to html.rb.
16
+ #
17
+ # There's another two base classes for special types of html elements:
18
+ # - HtmlSelfClosingElement - elements where in HTML, the closing tag is
19
+ # optional - for example, <p>, <li>, and <body>
20
+ # - HtmlSingletonElement - not only is the closing tag optional, but
21
+ # child elements are forbidden - for example, <br> and <img>
22
+ #
23
+ # These can also be defined via Rxhp::Html#define_tag
24
+ #
25
+ # If you're making a custom element that's purely server-side (i.e. is
26
+ # just composed of HTML elements), you want to subclass ComposableElement
27
+ # instead.
28
+ class HtmlElement < Element
29
+ include Rxhp::AttributeValidator
30
+ accept_attributes Rxhp::Html::GLOBAL_ATTRIBUTES
31
+ accept_attributes Rxhp::Html::GLOBAL_EVENT_HANDLERS
32
+
33
+ def initialize *args
34
+ super *args
35
+ validate_attributes!
36
+ end
37
+
38
+ def tag_name
39
+ raise NotImplementedError.new
40
+ end
41
+
42
+ # Render the element.
43
+ #
44
+ # Pays attention to the formatter type, doctype, pretty print options,
45
+ # etc.
46
+ def render options = {}
47
+ validate_attributes!
48
+ options = fill_options(options)
49
+
50
+ open = render_open_tag(options)
51
+ inner = render_children(options)
52
+ close = render_close_tag(options)
53
+
54
+ if options[:pretty]
55
+ indent = ' ' * (options[:indent] * options[:depth])
56
+ out = "%s%s\n" % [indent, open]
57
+ out += inner if inner
58
+ out += "%s%s\n" % [indent, close] if close && !close.empty?
59
+ out
60
+ else
61
+ out = open
62
+ out += inner if inner
63
+ out += close if close
64
+ out
65
+ end
66
+ end
67
+
68
+ # Override to increase the depth count for the sake of pretty printing
69
+ def render_children options = {}
70
+ child_options = options.dup
71
+ child_options[:depth] += 1
72
+ super child_options
73
+ end
74
+
75
+ protected
76
+
77
+ # html-escape a string, paying attention to indentation too.
78
+ def render_string string, options
79
+ escaped = html_escape(string)
80
+ if options[:pretty]
81
+ indent = ' ' * (options[:indent] * options[:depth])
82
+ indent + escaped + "\n"
83
+ else
84
+ escaped
85
+ end
86
+ end
87
+
88
+ # Render the opening tag.
89
+ #
90
+ # Considers:
91
+ # - attributes
92
+ # - XHTML or HTML?
93
+ # - are there any children?
94
+ #
95
+ # #render_close_tag assumes that this will not leave a tag open in XHTML
96
+ # unless there are children.
97
+ def render_open_tag options
98
+ out = '<' + tag_name
99
+ unless attributes.empty?
100
+ attributes.each do |name,value|
101
+ case value
102
+ when false
103
+ next
104
+ when true
105
+ if options[:format] == Rxhp::XHTML_FORMAT
106
+ out += ' ' + name.to_s + '="' + name.to_s + '"'
107
+ else
108
+ out += ' ' + name
109
+ end
110
+ else
111
+ out += ' ' + name.to_s + '="' + html_escape(value.to_s) + '"'
112
+ end
113
+ end
114
+ end
115
+
116
+ if options[:format] == Rxhp::XHTML_FORMAT && !children?
117
+ out + ' />'
118
+ else
119
+ out + '>'
120
+ end
121
+ end
122
+
123
+ # Render the closing tag.
124
+ #
125
+ # Assumes that #render_open_tag would have made a self-closing tag if
126
+ # appropriate.
127
+ #
128
+ # This is overriden in:
129
+ # - HtmlSingletonElement: never any children, so never a closing tag
130
+ # - HtmlSelfClosingElement: calls this, /unless/ tiny html output is
131
+ # selected, in which case the closing tag is skipped.
132
+ def render_close_tag options
133
+ if options[:format] != Rxhp::XHTML_FORMAT || children?
134
+ '</' + tag_name + '>'
135
+ end
136
+ end
137
+
138
+ # Don't pull in ActiveSupport just for this...
139
+ def html_escape s
140
+ s.gsub('&','&amp;').gsub('<','&lt;').gsub('>','&gt;').gsub('"','&quot;')
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,16 @@
1
+ require 'rxhp/html_element'
2
+
3
+ module Rxhp
4
+ # Base class for HTML elements where the closing tag is optional, but
5
+ # there can still be children.
6
+ #
7
+ # For example, </p> is optional in HTML, but required in XHTML.
8
+ # This will change whether they're included or not based on the selected
9
+ # render format.
10
+ class HtmlSelfClosingElement < HtmlElement
11
+ protected
12
+ def render_close_tag options
13
+ super if options[:format] != Rxhp::TINY_HTML_FORMAT
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ require 'rxhp/html_element'
2
+ require 'rxhp/error'
3
+
4
+ module Rxhp
5
+ # Superclass for all HTML elements that should never have children.
6
+ #
7
+ # This is enforced. Examples include '<br>', '<img>' etc
8
+ #
9
+ # There is never a close tag:
10
+ # - in HTML it would be optional (and meaningless) anyway
11
+ # - people don't expect to see them
12
+ # - in XHTML, the opening tag will have been self closing.
13
+ class HtmlSingletonElement < HtmlElement
14
+ def render *args
15
+ unless children.empty?
16
+ raise Rxhp::ScriptError.new('Singleton element has children')
17
+ end
18
+ super *args
19
+ end
20
+
21
+ protected
22
+ def render_close_tag options
23
+ nil
24
+ end
25
+ end
26
+ end
data/lib/rxhp/scope.rb ADDED
@@ -0,0 +1,114 @@
1
+ module Rxhp
2
+ autoload :Fragment, 'rxhp/fragment'
3
+ # A place for factory methods to be defined.
4
+ #
5
+ # These are methods like Rxhp::Scope#h1 which creates an Rxhp::Html::H1
6
+ # instance.
7
+ #
8
+ # The actual HTML classes are defined in rxhp/html.rb
9
+ #
10
+ module Scope
11
+ # Helper function to append a child to the current context.
12
+ # Allows you to do this:
13
+ # inner = body { 'hi' }
14
+ # html do
15
+ # fragment inner
16
+ # end
17
+ def fragment x
18
+ Rxhp::Scope.current.children.push x
19
+ end
20
+ alias :frag :fragment
21
+ alias :text :fragment
22
+
23
+ def self.current
24
+ self.stack.last || Rxhp::Fragment.new
25
+ end
26
+
27
+ def self.with_parent parent
28
+ result = nil
29
+ begin
30
+ self.stack.push parent
31
+ result = yield
32
+ ensure
33
+ self.stack.pop
34
+ end
35
+ result
36
+ end
37
+
38
+ # Define the factory method.
39
+ #
40
+ # can be called like:
41
+ # tag 'content'
42
+ # tag { content }
43
+ # tag 'content', :attribute => 'value'
44
+ # tag(:attribute=>'value') do
45
+ # content
46
+ # end
47
+ # tag
48
+ # tag (:attribute => value)
49
+ def self.define_element name, klass, namespace
50
+ impl = Proc.new do |*args, &block|
51
+ # Yay for faking named parameters as a hash :p
52
+ children = nil
53
+ attributes = {}
54
+ args.each do |arg|
55
+ if arg.is_a?(Hash)
56
+ attributes = arg
57
+ else
58
+ children ||= []
59
+ children.push arg
60
+ end
61
+ end
62
+
63
+ # Create the actual element
64
+ element = klass.new(attributes)
65
+ Rxhp::Scope.current.children.push element
66
+
67
+ # Append non-block children
68
+ if children
69
+ if children.is_a? Array
70
+ children.each do |child|
71
+ element.children.push child
72
+ end
73
+ else
74
+ element.children.push children # well, child
75
+ end
76
+ end
77
+
78
+ if block
79
+ Rxhp::Scope.with_parent(element) do
80
+ if block.call.is_a? String
81
+ raise Rxhp::ScriptError.new(
82
+ "In a block, use the 'text' method to include Strings"
83
+ )
84
+ end
85
+ nil
86
+ end
87
+ end
88
+ element
89
+ end
90
+
91
+ # Instance method if mixed in.
92
+ #
93
+ # Usage:
94
+ # include Rxhp::Html
95
+ # html do
96
+ # ...
97
+ # end
98
+ namespace.send(:define_method, name, impl)
99
+ # Class method for fully-qualified.
100
+ #
101
+ # Usage:
102
+ # Rxhp::Html.html do
103
+ # ...
104
+ # end
105
+ (class <<namespace; self; end).send(:define_method, name, impl)
106
+ end
107
+
108
+ private
109
+
110
+ def self.stack
111
+ Thread.current[:rxhp_scope_stack] ||= []
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,21 @@
1
+ require 'rxhp/html_self_closing_element'
2
+ require 'rxhp/scope'
3
+
4
+ module Rxhp
5
+ module Html
6
+ class Html < HtmlSelfClosingElement
7
+ def tag_name; 'html'; end
8
+
9
+ protected
10
+ def render_open_tag options
11
+ if options[:skip_doctype]
12
+ super
13
+ else
14
+ options[:doctype] + super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Rxhp::Scope.define_element('html', Rxhp::Html::Html, Rxhp::Html)
data/lib/rxhp.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'rxhp/constants'
2
+ require 'rxhp/error'
3
+ require 'rxhp/composable_element'
4
+ require 'rxhp/html'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rxhp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Fred Emmott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-21 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: An object-oriented validating HTML template system
15
+ email:
16
+ - rxhp-gem@fredemmott.co.uk
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/rxhp/attribute_validator.rb
22
+ - lib/rxhp/composable_element.rb
23
+ - lib/rxhp/constants.rb
24
+ - lib/rxhp/data/html/attributes.rb
25
+ - lib/rxhp/data/html/tags.rb
26
+ - lib/rxhp/element.rb
27
+ - lib/rxhp/error.rb
28
+ - lib/rxhp/fragment.rb
29
+ - lib/rxhp/html.rb
30
+ - lib/rxhp/html_element.rb
31
+ - lib/rxhp/html_self_closing_element.rb
32
+ - lib/rxhp/html_singleton_element.rb
33
+ - lib/rxhp/scope.rb
34
+ - lib/rxhp/tags/html_tag.rb
35
+ - lib/rxhp.rb
36
+ homepage: https://github.com/fredemmott/rxhp
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.6
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: An object-oriented validating HTML template system
60
+ test_files: []