actionview 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +274 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +34 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +205 -0
  7. data/lib/action_view/buffers.rb +49 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +93 -0
  10. data/lib/action_view/digestor.rb +116 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/helpers.rb +64 -0
  13. data/lib/action_view/helpers/active_model_helper.rb +49 -0
  14. data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
  15. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  16. data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
  17. data/lib/action_view/helpers/cache_helper.rb +200 -0
  18. data/lib/action_view/helpers/capture_helper.rb +216 -0
  19. data/lib/action_view/helpers/controller_helper.rb +25 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +30 -0
  21. data/lib/action_view/helpers/date_helper.rb +1075 -0
  22. data/lib/action_view/helpers/debug_helper.rb +39 -0
  23. data/lib/action_view/helpers/form_helper.rb +1876 -0
  24. data/lib/action_view/helpers/form_options_helper.rb +843 -0
  25. data/lib/action_view/helpers/form_tag_helper.rb +746 -0
  26. data/lib/action_view/helpers/javascript_helper.rb +75 -0
  27. data/lib/action_view/helpers/number_helper.rb +425 -0
  28. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  29. data/lib/action_view/helpers/record_tag_helper.rb +108 -0
  30. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  31. data/lib/action_view/helpers/sanitize_helper.rb +256 -0
  32. data/lib/action_view/helpers/tag_helper.rb +176 -0
  33. data/lib/action_view/helpers/tags.rb +41 -0
  34. data/lib/action_view/helpers/tags/base.rb +148 -0
  35. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  36. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  37. data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
  38. data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  41. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  42. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  43. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  44. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  45. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  46. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  47. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  48. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  50. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  51. data/lib/action_view/helpers/tags/label.rb +65 -0
  52. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  53. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  54. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  56. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  58. data/lib/action_view/helpers/tags/select.rb +41 -0
  59. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  61. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  62. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  65. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  66. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  67. data/lib/action_view/helpers/text_helper.rb +447 -0
  68. data/lib/action_view/helpers/translation_helper.rb +111 -0
  69. data/lib/action_view/helpers/url_helper.rb +625 -0
  70. data/lib/action_view/layouts.rb +426 -0
  71. data/lib/action_view/locale/en.yml +56 -0
  72. data/lib/action_view/log_subscriber.rb +44 -0
  73. data/lib/action_view/lookup_context.rb +249 -0
  74. data/lib/action_view/model_naming.rb +12 -0
  75. data/lib/action_view/path_set.rb +77 -0
  76. data/lib/action_view/railtie.rb +49 -0
  77. data/lib/action_view/record_identifier.rb +84 -0
  78. data/lib/action_view/renderer/abstract_renderer.rb +47 -0
  79. data/lib/action_view/renderer/partial_renderer.rb +492 -0
  80. data/lib/action_view/renderer/renderer.rb +50 -0
  81. data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
  82. data/lib/action_view/renderer/template_renderer.rb +96 -0
  83. data/lib/action_view/rendering.rb +145 -0
  84. data/lib/action_view/routing_url_for.rb +109 -0
  85. data/lib/action_view/tasks/dependencies.rake +17 -0
  86. data/lib/action_view/template.rb +340 -0
  87. data/lib/action_view/template/error.rb +141 -0
  88. data/lib/action_view/template/handlers.rb +53 -0
  89. data/lib/action_view/template/handlers/builder.rb +26 -0
  90. data/lib/action_view/template/handlers/erb.rb +145 -0
  91. data/lib/action_view/template/handlers/raw.rb +11 -0
  92. data/lib/action_view/template/resolver.rb +329 -0
  93. data/lib/action_view/template/text.rb +34 -0
  94. data/lib/action_view/template/types.rb +57 -0
  95. data/lib/action_view/test_case.rb +272 -0
  96. data/lib/action_view/testing/resolvers.rb +50 -0
  97. data/lib/action_view/vendor/html-scanner.rb +20 -0
  98. data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
  99. data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
  100. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
  101. data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
  102. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
  103. data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
  104. data/lib/action_view/version.rb +11 -0
  105. data/lib/action_view/view_paths.rb +96 -0
  106. metadata +218 -0
@@ -0,0 +1,532 @@
1
+ require 'strscan'
2
+
3
+ module HTML #:nodoc:
4
+
5
+ class Conditions < Hash #:nodoc:
6
+ def initialize(hash)
7
+ super()
8
+ hash = { :content => hash } unless Hash === hash
9
+ hash = keys_to_symbols(hash)
10
+ hash.each do |k,v|
11
+ case k
12
+ when :tag, :content then
13
+ # keys are valid, and require no further processing
14
+ when :attributes then
15
+ hash[k] = keys_to_strings(v)
16
+ when :parent, :child, :ancestor, :descendant, :sibling, :before,
17
+ :after
18
+ hash[k] = Conditions.new(v)
19
+ when :children
20
+ hash[k] = v = keys_to_symbols(v)
21
+ v.each do |key,value|
22
+ case key
23
+ when :count, :greater_than, :less_than
24
+ # keys are valid, and require no further processing
25
+ when :only
26
+ v[key] = Conditions.new(value)
27
+ else
28
+ raise "illegal key #{key.inspect} => #{value.inspect}"
29
+ end
30
+ end
31
+ else
32
+ raise "illegal key #{k.inspect} => #{v.inspect}"
33
+ end
34
+ end
35
+ update hash
36
+ end
37
+
38
+ private
39
+
40
+ def keys_to_strings(hash)
41
+ Hash[hash.keys.map {|k| [k.to_s, hash[k]]}]
42
+ end
43
+
44
+ def keys_to_symbols(hash)
45
+ Hash[hash.keys.map do |k|
46
+ raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
47
+ [k.to_sym, hash[k]]
48
+ end]
49
+ end
50
+ end
51
+
52
+ # The base class of all nodes, textual and otherwise, in an HTML document.
53
+ class Node #:nodoc:
54
+ # The array of children of this node. Not all nodes have children.
55
+ attr_reader :children
56
+
57
+ # The parent node of this node. All nodes have a parent, except for the
58
+ # root node.
59
+ attr_reader :parent
60
+
61
+ # The line number of the input where this node was begun
62
+ attr_reader :line
63
+
64
+ # The byte position in the input where this node was begun
65
+ attr_reader :position
66
+
67
+ # Create a new node as a child of the given parent.
68
+ def initialize(parent, line=0, pos=0)
69
+ @parent = parent
70
+ @children = []
71
+ @line, @position = line, pos
72
+ end
73
+
74
+ # Return a textual representation of the node.
75
+ def to_s
76
+ @children.join()
77
+ end
78
+
79
+ # Return false (subclasses must override this to provide specific matching
80
+ # behavior.) +conditions+ may be of any type.
81
+ def match(conditions)
82
+ false
83
+ end
84
+
85
+ # Search the children of this node for the first node for which #find
86
+ # returns non +nil+. Returns the result of the #find call that succeeded.
87
+ def find(conditions)
88
+ conditions = validate_conditions(conditions)
89
+ @children.each do |child|
90
+ node = child.find(conditions)
91
+ return node if node
92
+ end
93
+ nil
94
+ end
95
+
96
+ # Search for all nodes that match the given conditions, and return them
97
+ # as an array.
98
+ def find_all(conditions)
99
+ conditions = validate_conditions(conditions)
100
+
101
+ matches = []
102
+ matches << self if match(conditions)
103
+ @children.each do |child|
104
+ matches.concat child.find_all(conditions)
105
+ end
106
+ matches
107
+ end
108
+
109
+ # Returns +false+. Subclasses may override this if they define a kind of
110
+ # tag.
111
+ def tag?
112
+ false
113
+ end
114
+
115
+ def validate_conditions(conditions)
116
+ Conditions === conditions ? conditions : Conditions.new(conditions)
117
+ end
118
+
119
+ def ==(node)
120
+ return false unless self.class == node.class && children.size == node.children.size
121
+
122
+ equivalent = true
123
+
124
+ children.size.times do |i|
125
+ equivalent &&= children[i] == node.children[i]
126
+ end
127
+
128
+ equivalent
129
+ end
130
+
131
+ class <<self
132
+ def parse(parent, line, pos, content, strict=true)
133
+ if content !~ /^<\S/
134
+ Text.new(parent, line, pos, content)
135
+ else
136
+ scanner = StringScanner.new(content)
137
+
138
+ unless scanner.skip(/</)
139
+ if strict
140
+ raise "expected <"
141
+ else
142
+ return Text.new(parent, line, pos, content)
143
+ end
144
+ end
145
+
146
+ if scanner.skip(/!\[CDATA\[/)
147
+ unless scanner.skip_until(/\]\]>/)
148
+ if strict
149
+ raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
150
+ else
151
+ scanner.skip_until(/\Z/)
152
+ end
153
+ end
154
+
155
+ return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
156
+ end
157
+
158
+ closing = ( scanner.scan(/\//) ? :close : nil )
159
+ return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
160
+ name.downcase!
161
+
162
+ unless closing
163
+ scanner.skip(/\s*/)
164
+ attributes = {}
165
+ while attr = scanner.scan(/[-\w:]+/)
166
+ value = true
167
+ if scanner.scan(/\s*=\s*/)
168
+ if delim = scanner.scan(/['"]/)
169
+ value = ""
170
+ while text = scanner.scan(/[^#{delim}\\]+|./)
171
+ case text
172
+ when "\\" then
173
+ value << text
174
+ break if scanner.eos?
175
+ value << scanner.getch
176
+ when delim
177
+ break
178
+ else value << text
179
+ end
180
+ end
181
+ else
182
+ value = scanner.scan(/[^\s>\/]+/)
183
+ end
184
+ end
185
+ attributes[attr.downcase] = value
186
+ scanner.skip(/\s*/)
187
+ end
188
+
189
+ closing = ( scanner.scan(/\//) ? :self : nil )
190
+ end
191
+
192
+ unless scanner.scan(/\s*>/)
193
+ if strict
194
+ raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
195
+ else
196
+ # throw away all text until we find what we're looking for
197
+ scanner.skip_until(/>/) or scanner.terminate
198
+ end
199
+ end
200
+
201
+ Tag.new(parent, line, pos, name, attributes, closing)
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ # A node that represents text, rather than markup.
208
+ class Text < Node #:nodoc:
209
+
210
+ attr_reader :content
211
+
212
+ # Creates a new text node as a child of the given parent, with the given
213
+ # content.
214
+ def initialize(parent, line, pos, content)
215
+ super(parent, line, pos)
216
+ @content = content
217
+ end
218
+
219
+ # Returns the content of this node.
220
+ def to_s
221
+ @content
222
+ end
223
+
224
+ # Returns +self+ if this node meets the given conditions. Text nodes support
225
+ # conditions of the following kinds:
226
+ #
227
+ # * if +conditions+ is a string, it must be a substring of the node's
228
+ # content
229
+ # * if +conditions+ is a regular expression, it must match the node's
230
+ # content
231
+ # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
232
+ # is either a string or a regexp, and which is interpreted as described
233
+ # above.
234
+ def find(conditions)
235
+ match(conditions) && self
236
+ end
237
+
238
+ # Returns non-+nil+ if this node meets the given conditions, or +nil+
239
+ # otherwise. See the discussion of #find for the valid conditions.
240
+ def match(conditions)
241
+ case conditions
242
+ when String
243
+ @content == conditions
244
+ when Regexp
245
+ @content =~ conditions
246
+ when Hash
247
+ conditions = validate_conditions(conditions)
248
+
249
+ # Text nodes only have :content, :parent, :ancestor
250
+ unless (conditions.keys - [:content, :parent, :ancestor]).empty?
251
+ return false
252
+ end
253
+
254
+ match(conditions[:content])
255
+ else
256
+ nil
257
+ end
258
+ end
259
+
260
+ def ==(node)
261
+ return false unless super
262
+ content == node.content
263
+ end
264
+ end
265
+
266
+ # A CDATA node is simply a text node with a specialized way of displaying
267
+ # itself.
268
+ class CDATA < Text #:nodoc:
269
+ def to_s
270
+ "<![CDATA[#{super}]]>"
271
+ end
272
+ end
273
+
274
+ # A Tag is any node that represents markup. It may be an opening tag, a
275
+ # closing tag, or a self-closing tag. It has a name, and may have a hash of
276
+ # attributes.
277
+ class Tag < Node #:nodoc:
278
+
279
+ # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
280
+ attr_reader :closing
281
+
282
+ # Either +nil+, or a hash of attributes for this node.
283
+ attr_reader :attributes
284
+
285
+ # The name of this tag.
286
+ attr_reader :name
287
+
288
+ # Create a new node as a child of the given parent, using the given content
289
+ # to describe the node. It will be parsed and the node name, attributes and
290
+ # closing status extracted.
291
+ def initialize(parent, line, pos, name, attributes, closing)
292
+ super(parent, line, pos)
293
+ @name = name
294
+ @attributes = attributes
295
+ @closing = closing
296
+ end
297
+
298
+ # A convenience for obtaining an attribute of the node. Returns +nil+ if
299
+ # the node has no attributes.
300
+ def [](attr)
301
+ @attributes ? @attributes[attr] : nil
302
+ end
303
+
304
+ # Returns non-+nil+ if this tag can contain child nodes.
305
+ def childless?(xml = false)
306
+ return false if xml && @closing.nil?
307
+ !@closing.nil? ||
308
+ @name =~ /^(img|br|hr|link|meta|area|base|basefont|
309
+ col|frame|input|isindex|param)$/ox
310
+ end
311
+
312
+ # Returns a textual representation of the node
313
+ def to_s
314
+ if @closing == :close
315
+ "</#{@name}>"
316
+ else
317
+ s = "<#{@name}"
318
+ @attributes.each do |k,v|
319
+ s << " #{k}"
320
+ s << "=\"#{v}\"" if String === v
321
+ end
322
+ s << " /" if @closing == :self
323
+ s << ">"
324
+ @children.each { |child| s << child.to_s }
325
+ s << "</#{@name}>" if @closing != :self && !@children.empty?
326
+ s
327
+ end
328
+ end
329
+
330
+ # If either the node or any of its children meet the given conditions, the
331
+ # matching node is returned. Otherwise, +nil+ is returned. (See the
332
+ # description of the valid conditions in the +match+ method.)
333
+ def find(conditions)
334
+ match(conditions) && self || super
335
+ end
336
+
337
+ # Returns +true+, indicating that this node represents an HTML tag.
338
+ def tag?
339
+ true
340
+ end
341
+
342
+ # Returns +true+ if the node meets any of the given conditions. The
343
+ # +conditions+ parameter must be a hash of any of the following keys
344
+ # (all are optional):
345
+ #
346
+ # * <tt>:tag</tt>: the node name must match the corresponding value
347
+ # * <tt>:attributes</tt>: a hash. The node's values must match the
348
+ # corresponding values in the hash.
349
+ # * <tt>:parent</tt>: a hash. The node's parent must match the
350
+ # corresponding hash.
351
+ # * <tt>:child</tt>: a hash. At least one of the node's immediate children
352
+ # must meet the criteria described by the hash.
353
+ # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
354
+ # meet the criteria described by the hash.
355
+ # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
356
+ # must meet the criteria described by the hash.
357
+ # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
358
+ # meet the criteria described by the hash.
359
+ # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
360
+ # the criteria described by the hash, and at least one sibling must match.
361
+ # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
362
+ # the criteria described by the hash, and at least one sibling must match.
363
+ # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
364
+ # keys:
365
+ # ** <tt>:count</tt>: either a number or a range which must equal (or
366
+ # include) the number of children that match.
367
+ # ** <tt>:less_than</tt>: the number of matching children must be less than
368
+ # this number.
369
+ # ** <tt>:greater_than</tt>: the number of matching children must be
370
+ # greater than this number.
371
+ # ** <tt>:only</tt>: another hash consisting of the keys to use
372
+ # to match on the children, and only matching children will be
373
+ # counted.
374
+ #
375
+ # Conditions are matched using the following algorithm:
376
+ #
377
+ # * if the condition is a string, it must be a substring of the value.
378
+ # * if the condition is a regexp, it must match the value.
379
+ # * if the condition is a number, the value must match number.to_s.
380
+ # * if the condition is +true+, the value must not be +nil+.
381
+ # * if the condition is +false+ or +nil+, the value must be +nil+.
382
+ #
383
+ # Usage:
384
+ #
385
+ # # test if the node is a "span" tag
386
+ # node.match tag: "span"
387
+ #
388
+ # # test if the node's parent is a "div"
389
+ # node.match parent: { tag: "div" }
390
+ #
391
+ # # test if any of the node's ancestors are "table" tags
392
+ # node.match ancestor: { tag: "table" }
393
+ #
394
+ # # test if any of the node's immediate children are "em" tags
395
+ # node.match child: { tag: "em" }
396
+ #
397
+ # # test if any of the node's descendants are "strong" tags
398
+ # node.match descendant: { tag: "strong" }
399
+ #
400
+ # # test if the node has between 2 and 4 span tags as immediate children
401
+ # node.match children: { count: 2..4, only: { tag: "span" } }
402
+ #
403
+ # # get funky: test to see if the node is a "div", has a "ul" ancestor
404
+ # # and an "li" parent (with "class" = "enum"), and whether or not it has
405
+ # # a "span" descendant that contains # text matching /hello world/:
406
+ # node.match tag: "div",
407
+ # ancestor: { tag: "ul" },
408
+ # parent: { tag: "li",
409
+ # attributes: { class: "enum" } },
410
+ # descendant: { tag: "span",
411
+ # child: /hello world/ }
412
+ def match(conditions)
413
+ conditions = validate_conditions(conditions)
414
+ # check content of child nodes
415
+ if conditions[:content]
416
+ if children.empty?
417
+ return false unless match_condition("", conditions[:content])
418
+ else
419
+ return false unless children.find { |child| child.match(conditions[:content]) }
420
+ end
421
+ end
422
+
423
+ # test the name
424
+ return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
425
+
426
+ # test attributes
427
+ (conditions[:attributes] || {}).each do |key, value|
428
+ return false unless match_condition(self[key], value)
429
+ end
430
+
431
+ # test parent
432
+ return false unless parent.match(conditions[:parent]) if conditions[:parent]
433
+
434
+ # test children
435
+ return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
436
+
437
+ # test ancestors
438
+ if conditions[:ancestor]
439
+ return false unless catch :found do
440
+ p = self
441
+ throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
442
+ end
443
+ end
444
+
445
+ # test descendants
446
+ if conditions[:descendant]
447
+ return false unless children.find do |child|
448
+ # test the child
449
+ child.match(conditions[:descendant]) ||
450
+ # test the child's descendants
451
+ child.match(:descendant => conditions[:descendant])
452
+ end
453
+ end
454
+
455
+ # count children
456
+ if opts = conditions[:children]
457
+ matches = children.select do |c|
458
+ (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
459
+ end
460
+
461
+ matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
462
+ opts.each do |key, value|
463
+ next if key == :only
464
+ case key
465
+ when :count
466
+ if Integer === value
467
+ return false if matches.length != value
468
+ else
469
+ return false unless value.include?(matches.length)
470
+ end
471
+ when :less_than
472
+ return false unless matches.length < value
473
+ when :greater_than
474
+ return false unless matches.length > value
475
+ else raise "unknown count condition #{key}"
476
+ end
477
+ end
478
+ end
479
+
480
+ # test siblings
481
+ if conditions[:sibling] || conditions[:before] || conditions[:after]
482
+ siblings = parent ? parent.children : []
483
+ self_index = siblings.index(self)
484
+
485
+ if conditions[:sibling]
486
+ return false unless siblings.detect do |s|
487
+ s != self && s.match(conditions[:sibling])
488
+ end
489
+ end
490
+
491
+ if conditions[:before]
492
+ return false unless siblings[self_index+1..-1].detect do |s|
493
+ s != self && s.match(conditions[:before])
494
+ end
495
+ end
496
+
497
+ if conditions[:after]
498
+ return false unless siblings[0,self_index].detect do |s|
499
+ s != self && s.match(conditions[:after])
500
+ end
501
+ end
502
+ end
503
+
504
+ true
505
+ end
506
+
507
+ def ==(node)
508
+ return false unless super
509
+ return false unless closing == node.closing && self.name == node.name
510
+ attributes == node.attributes
511
+ end
512
+
513
+ private
514
+ # Match the given value to the given condition.
515
+ def match_condition(value, condition)
516
+ case condition
517
+ when String
518
+ value && value == condition
519
+ when Regexp
520
+ value && value.match(condition)
521
+ when Numeric
522
+ value == condition.to_s
523
+ when true
524
+ !value.nil?
525
+ when false, nil
526
+ value.nil?
527
+ else
528
+ false
529
+ end
530
+ end
531
+ end
532
+ end