erector 0.8.3 → 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +21 -0
- data/Rakefile +171 -0
- data/VERSION.yml +3 -2
- data/lib/erector.rb +3 -5
- data/lib/erector/abstract_widget.rb +76 -31
- data/lib/erector/attributes.rb +34 -0
- data/lib/erector/caching.rb +1 -0
- data/lib/erector/convenience.rb +12 -4
- data/lib/erector/dependency.rb +2 -1
- data/lib/erector/element.rb +113 -0
- data/lib/erector/erect/erect.rb +10 -7
- data/lib/erector/externals.rb +2 -1
- data/lib/erector/html.rb +6 -340
- data/lib/erector/html_widget.rb +300 -0
- data/lib/erector/inline.rb +1 -1
- data/lib/erector/output.rb +39 -12
- data/lib/erector/promise.rb +137 -0
- data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
- data/lib/erector/rails3.rb +1 -1
- data/lib/erector/sass.rb +14 -19
- data/lib/erector/tag.rb +65 -0
- data/lib/erector/text.rb +123 -0
- data/lib/erector/version.rb +1 -1
- data/lib/erector/widget.rb +52 -12
- data/lib/erector/widgets/page.rb +12 -10
- data/lib/erector/xml_widget.rb +131 -0
- data/spec/erector/caching_spec.rb +1 -0
- data/spec/erector/externals_spec.rb +0 -1
- data/spec/erector/html_spec.rb +9 -25
- data/spec/erector/output_spec.rb +102 -9
- data/spec/erector/promise_spec.rb +173 -0
- data/spec/erector/sass_spec.rb +1 -1
- data/spec/erector/tag_spec.rb +67 -0
- data/spec/erector/widget_spec.rb +53 -2
- data/spec/erector/xml_widget_spec.rb +74 -0
- data/spec/rails2/rails_app/Gemfile +1 -0
- data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
- data/spec/rails_root/Gemfile +11 -0
- data/spec/rails_root/README +256 -0
- data/spec/rails_root/Rakefile +7 -0
- data/spec/rails_root/app/controllers/application.rb +6 -0
- data/spec/rails_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_root/app/views/test/_erb.erb +1 -0
- data/spec/rails_root/app/views/test/_erector.rb +5 -0
- data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
- data/spec/rails_root/app/views/test/bare.rb +5 -0
- data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
- data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
- data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
- data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
- data/spec/rails_root/app/views/test/needs.html.rb +7 -0
- data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
- data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
- data/spec/rails_root/config.ru +4 -0
- data/spec/rails_root/config/application.rb +42 -0
- data/spec/rails_root/config/boot.rb +13 -0
- data/spec/rails_root/config/database.yml +22 -0
- data/spec/rails_root/config/environment.rb +5 -0
- data/spec/rails_root/config/environments/development.rb +22 -0
- data/spec/rails_root/config/environments/production.rb +49 -0
- data/spec/rails_root/config/environments/test.rb +35 -0
- data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_root/config/initializers/inflections.rb +10 -0
- data/spec/rails_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails_root/config/initializers/session_store.rb +8 -0
- data/spec/rails_root/config/locales/en.yml +5 -0
- data/spec/rails_root/config/routes.rb +58 -0
- data/spec/rails_root/db/seeds.rb +7 -0
- data/spec/rails_root/doc/README_FOR_APP +2 -0
- data/spec/rails_root/log/development.log +17 -0
- data/spec/rails_root/log/test.log +3750 -0
- data/spec/rails_root/public/404.html +26 -0
- data/spec/rails_root/public/422.html +26 -0
- data/spec/rails_root/public/500.html +26 -0
- data/spec/rails_root/public/dispatch.cgi +10 -0
- data/spec/rails_root/public/dispatch.fcgi +24 -0
- data/spec/rails_root/public/dispatch.rb +10 -0
- data/spec/rails_root/public/favicon.ico +0 -0
- data/spec/rails_root/public/images/rails.png +0 -0
- data/spec/rails_root/public/index.html +262 -0
- data/spec/rails_root/public/javascripts/application.js +2 -0
- data/spec/rails_root/public/javascripts/controls.js +965 -0
- data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
- data/spec/rails_root/public/javascripts/effects.js +1123 -0
- data/spec/rails_root/public/javascripts/prototype.js +6001 -0
- data/spec/rails_root/public/javascripts/rails.js +175 -0
- data/spec/rails_root/public/robots.txt +5 -0
- data/spec/rails_root/script/about +3 -0
- data/spec/rails_root/script/console +3 -0
- data/spec/rails_root/script/destroy +3 -0
- data/spec/rails_root/script/generate +3 -0
- data/spec/rails_root/script/performance/benchmarker +3 -0
- data/spec/rails_root/script/performance/profiler +3 -0
- data/spec/rails_root/script/performance/request +3 -0
- data/spec/rails_root/script/plugin +3 -0
- data/spec/rails_root/script/process/inspector +3 -0
- data/spec/rails_root/script/process/reaper +3 -0
- data/spec/rails_root/script/process/spawner +3 -0
- data/spec/rails_root/script/rails +6 -0
- data/spec/rails_root/script/runner +3 -0
- data/spec/rails_root/script/server +3 -0
- data/spec/rails_root/spec/form_builder_spec.rb +21 -0
- data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
- data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
- data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
- data/spec/rails_root/spec/render_spec.rb +298 -0
- data/spec/rails_root/test/performance/browsing_test.rb +9 -0
- data/spec/rails_root/test/test_helper.rb +13 -0
- data/spec/spec_helper.rb +3 -1
- metadata +202 -66
@@ -45,7 +45,7 @@ module Erector
|
|
45
45
|
|
46
46
|
output_buffer = view.with_output_buffer do
|
47
47
|
# Set parent to the view and use Rails's output buffer.
|
48
|
-
new_output = Output.new { view.output_buffer }
|
48
|
+
new_output = Output.new :buffer => lambda { view.output_buffer }
|
49
49
|
widget.to_html(options.merge(:parent => view,
|
50
50
|
:output => new_output))
|
51
51
|
end
|
data/lib/erector/rails3.rb
CHANGED
@@ -39,7 +39,7 @@ module Erector
|
|
39
39
|
# Set parent and helpers to the view and use Rails's output buffer.
|
40
40
|
widget.to_html(options.merge(:helpers => view,
|
41
41
|
:parent => view,
|
42
|
-
:output => Output.new { view.output_buffer }))
|
42
|
+
:output => Output.new(:buffer => lambda { view.output_buffer })))
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/erector/sass.rb
CHANGED
@@ -1,22 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# loading from files, precompilation, etc.
|
16
|
-
module Sass
|
17
|
-
def sass(sass_text)
|
18
|
-
style ::Sass::Engine.new(sass_text, :cache => false).render
|
19
|
-
end
|
1
|
+
module Erector
|
2
|
+
# Adds sass support to Erector widgets.
|
3
|
+
#
|
4
|
+
# Sass is an *optional dependency* of the Erector gem, so
|
5
|
+
# a call to +sass+ inside a widget will fail unless you have already
|
6
|
+
# installed the sass gem (e.g. "gem 'sass'" in your code or Gemfile).
|
7
|
+
#
|
8
|
+
# Current support is barebones. Please offer suggestions (or better
|
9
|
+
# yet, patches) for whether and how to support, e.g., caching,
|
10
|
+
# loading from files, precompilation, etc.
|
11
|
+
module Sass
|
12
|
+
def sass(sass_text)
|
13
|
+
require "sass"
|
14
|
+
style ::Sass::Engine.new(sass_text, :cache => false).render
|
20
15
|
end
|
21
16
|
end
|
22
17
|
end
|
data/lib/erector/tag.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Erector
|
2
|
+
|
3
|
+
# Defines a type of tag (not an actual element with attributes and contents)
|
4
|
+
class Tag
|
5
|
+
|
6
|
+
# Pass the self_closing and inline params as symbols, e.g.
|
7
|
+
#
|
8
|
+
# Tag.new("i", :inline)
|
9
|
+
# Tag.new("input", :inline, :self_closing)
|
10
|
+
#
|
11
|
+
# @param name the name of the tag, e.g. "div"
|
12
|
+
# @param self_closing whether it can (false) or cannot (true) contain text or other elements. Default: false
|
13
|
+
# @param inline whether it should appear in line with other elements (true) or on a line by itself (false) in pretty mode. Default: false
|
14
|
+
# @param snake whether to covert the method name into "snake case" (aka underscorized). Default: false
|
15
|
+
#
|
16
|
+
def initialize(name, *params)
|
17
|
+
@name = name.to_s
|
18
|
+
@method_name = if params.first.is_a? String
|
19
|
+
params.shift
|
20
|
+
else
|
21
|
+
@name
|
22
|
+
end
|
23
|
+
@self_closing = params.include?(:self_closing)
|
24
|
+
@inline = params.include?(:inline)
|
25
|
+
@method_name = snake_case(@method_name) if params.include?(:snake_case)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :name, :method_name
|
29
|
+
|
30
|
+
def self_closing?
|
31
|
+
@self_closing
|
32
|
+
end
|
33
|
+
|
34
|
+
def newliney?
|
35
|
+
!@inline
|
36
|
+
end
|
37
|
+
|
38
|
+
def inline?
|
39
|
+
@inline
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Convert to snake case.
|
44
|
+
#
|
45
|
+
# "FooBar".snake_case #=> "foo_bar"
|
46
|
+
# "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
|
47
|
+
# "CNN".snake_case #=> "cnn"
|
48
|
+
#
|
49
|
+
# @return [String] Receiver converted to snake case.
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
# borrowed from https://github.com/datamapper/extlib/blob/master/lib/extlib/string.rb
|
53
|
+
def snake_case(s)
|
54
|
+
if s.match(/\A[A-Z]+\z/)
|
55
|
+
s.downcase
|
56
|
+
else
|
57
|
+
s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
58
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').
|
59
|
+
downcase
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/erector/text.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require "erector/raw_string"
|
2
|
+
|
3
|
+
module Erector
|
4
|
+
module Text
|
5
|
+
# Emits text to the output buffer, e.g.
|
6
|
+
#
|
7
|
+
# text "my dog smells awful"
|
8
|
+
# => "my dog smells awful"
|
9
|
+
#
|
10
|
+
# If a string is passed in, it will be HTML-escaped. If the
|
11
|
+
# result of calling methods such as raw is passed in, the HTML will not be
|
12
|
+
# HTML-escaped again. If another kind of object is passed in, the result
|
13
|
+
# of calling its to_s method will be treated as a string would be.
|
14
|
+
#
|
15
|
+
# You shouldn't pass a widget in to this method, as that will cause
|
16
|
+
# performance problems (as well as being semantically goofy). Use the
|
17
|
+
# #widget method instead.
|
18
|
+
#
|
19
|
+
# You may pass a series of values (i.e. varargs). In that case, each value
|
20
|
+
# will be emitted to the output stream in turn. You can specify a delimiter
|
21
|
+
# by using an options hash with as the final argument, using +:join+ as the key,
|
22
|
+
# e.g.
|
23
|
+
#
|
24
|
+
# text "my", "dog", "smells", :join => " "
|
25
|
+
# => "my dog smells"
|
26
|
+
#
|
27
|
+
# You may also pass a Promise as a parameter; every tag
|
28
|
+
# method now returns a Promise after emitting. This allows
|
29
|
+
# you to easily embed simple HTML formatting into a sentence, e.g.
|
30
|
+
#
|
31
|
+
# text "my", "dog", "smells", b("great!"), :join => " "
|
32
|
+
# => "my dog smells <b>great!</b>"
|
33
|
+
#
|
34
|
+
# (Yes, the initial call to +b+ emits "\<b>great\</b>" to the output buffer;
|
35
|
+
# the Promise feature takes care of rewinding and rewriting the output
|
36
|
+
# buffer during the later call to +text+.)
|
37
|
+
#
|
38
|
+
def text(*values)
|
39
|
+
options = if values.last.is_a? Hash
|
40
|
+
values.pop
|
41
|
+
else
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
delimiter = options[:join]
|
45
|
+
|
46
|
+
values.select{|value| value.is_a? Promise}.each do |promise|
|
47
|
+
# erase whatever the promises wrote already
|
48
|
+
promise._rewind
|
49
|
+
end
|
50
|
+
|
51
|
+
first = true
|
52
|
+
values.each do |value|
|
53
|
+
if !first and delimiter
|
54
|
+
output << h(delimiter)
|
55
|
+
end
|
56
|
+
first = false
|
57
|
+
|
58
|
+
case value
|
59
|
+
when AbstractWidget
|
60
|
+
# todo: better deprecation
|
61
|
+
raise "Don't pass a widget to the text method. Use the widget method instead."
|
62
|
+
when Promise
|
63
|
+
value._mark # so the promise's rewind won't erase anything
|
64
|
+
value._render # render the promise to the output stream again
|
65
|
+
# note: we could let the promise cache its first effort, but
|
66
|
+
# here I think it's better to optimize for memory over speed
|
67
|
+
else
|
68
|
+
output << h(value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns text which will *not* be HTML-escaped.
|
75
|
+
def raw(value)
|
76
|
+
RawString.new(value.to_s)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
|
80
|
+
def text!(value)
|
81
|
+
text raw(value)
|
82
|
+
end
|
83
|
+
|
84
|
+
alias rawtext text!
|
85
|
+
|
86
|
+
# Returns a copy of value with spaces replaced by non-breaking space characters.
|
87
|
+
# With no arguments, return a single non-breaking space.
|
88
|
+
# The output uses the escaping format ' ' since that works
|
89
|
+
# in both HTML and XML (as opposed to ' ' which only works in HTML).
|
90
|
+
def nbsp(value = " ")
|
91
|
+
raw(h(value).gsub(/ /,' '))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an HTML-escaped version of its parameter. Leaves the output
|
95
|
+
# string untouched. This method is idempotent: h(h(text)) will not
|
96
|
+
# double-escape text. This means that it is safe to do something like
|
97
|
+
# text(h("2<4")) -- it will produce "2<4", not "2&lt;4".
|
98
|
+
def h(content)
|
99
|
+
if content.respond_to?(:html_safe?) && content.html_safe?
|
100
|
+
content
|
101
|
+
else
|
102
|
+
raw(CGI.escapeHTML(content.to_s))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a character given its unicode code point or unicode name.
|
107
|
+
def character(code_point_or_name)
|
108
|
+
if code_point_or_name.is_a?(Symbol)
|
109
|
+
require "erector/unicode"
|
110
|
+
found = Erector::CHARACTERS[code_point_or_name]
|
111
|
+
if found.nil?
|
112
|
+
raise "Unrecognized character #{code_point_or_name}"
|
113
|
+
end
|
114
|
+
raw("&#x#{sprintf '%x', found};")
|
115
|
+
elsif code_point_or_name.is_a?(Integer)
|
116
|
+
raw("&#x#{sprintf '%x', code_point_or_name};")
|
117
|
+
else
|
118
|
+
raise "Unrecognized argument to character: #{code_point_or_name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
data/lib/erector/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Erector
|
|
6
6
|
if !Erector.const_defined?(:VERSION)
|
7
7
|
dir = File.dirname(__FILE__)
|
8
8
|
version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
|
9
|
-
VERSION =
|
9
|
+
VERSION = [version[:major], version[:minor], version[:patch], version[:build]].compact.join('.')
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
data/lib/erector/widget.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
require "erector/element"
|
2
|
+
require "erector/attributes"
|
3
|
+
require "erector/promise"
|
4
|
+
require "erector/text"
|
5
|
+
require "erector/tag"
|
6
|
+
require "erector/html_widget"
|
7
|
+
require "erector/needs"
|
8
|
+
|
1
9
|
module Erector
|
2
|
-
|
10
|
+
|
3
11
|
# A Widget is the center of the Erector universe.
|
4
12
|
#
|
5
13
|
# To create a widget, extend Erector::Widget and implement the +content+
|
@@ -36,19 +44,51 @@ module Erector
|
|
36
44
|
#
|
37
45
|
# This class extends AbstractWidget and includes several modules,
|
38
46
|
# so be sure to check all of those places for API documentation for the
|
39
|
-
# various methods of Widget
|
40
|
-
#
|
47
|
+
# various methods of Widget:
|
48
|
+
#
|
49
|
+
# * AbstractWidget
|
50
|
+
# * Element
|
51
|
+
# * Attributes
|
52
|
+
# * Text
|
53
|
+
# * Needs
|
54
|
+
# * Caching
|
55
|
+
# * Externals
|
56
|
+
# * AfterInitialize
|
41
57
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
58
|
+
# * HTML
|
59
|
+
# * Convenience
|
60
|
+
# * JQuery
|
61
|
+
# * Sass
|
62
|
+
#
|
63
|
+
# Also read the API Cheatsheet in the user guide
|
64
|
+
# at http://erector.rubyforge.org/userguide#apicheatsheet
|
65
|
+
class Widget < HTMLWidget
|
66
|
+
|
67
|
+
# for some reason these need to be included in Widget and not AbstractWidget
|
68
|
+
include Needs
|
69
|
+
include Caching
|
70
|
+
include Externals
|
71
|
+
|
72
|
+
include HTML
|
73
|
+
include Convenience
|
50
74
|
include Erector::JQuery
|
51
|
-
include Erector::AfterInitialize
|
52
75
|
include Erector::Sass if Object.const_defined?(:Sass)
|
76
|
+
|
77
|
+
# alias for AbstractWidget#render
|
78
|
+
def to_html(options = {})
|
79
|
+
raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
|
80
|
+
_render(options).to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# alias for #to_html
|
84
|
+
# @deprecated Please use {#to_html} instead
|
85
|
+
def to_s(*args)
|
86
|
+
unless defined? @@already_warned_to_s
|
87
|
+
$stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
|
88
|
+
@@already_warned_to_s = true
|
89
|
+
end
|
90
|
+
to_html(*args)
|
91
|
+
end
|
92
|
+
|
53
93
|
end
|
54
94
|
end
|
data/lib/erector/widgets/page.rb
CHANGED
@@ -7,14 +7,15 @@
|
|
7
7
|
# declare it.
|
8
8
|
#
|
9
9
|
# The script and style declarations are accumulated at class load time, as
|
10
|
-
# 'dependencies'. This technique allows all widgets to add their own
|
11
|
-
# to the page header without extra logic for declaring which
|
12
|
-
# which nested widgets. Fortunately, Page is now smart enough to
|
13
|
-
# which widgets were actually rendered during the body_content run,
|
14
|
-
# emits into its HEAD the dependencies that are relevant. If it
|
15
|
-
# if you want to add some extra dependencies -- for instance,
|
16
|
-
# to widgets that are rendered later via AJAX -- then return
|
17
|
-
# widget classes in your subclass by overriding the
|
10
|
+
# 'dependencies'. This technique allows all widgets to add their own
|
11
|
+
# requirements to the page header without extra logic for declaring which
|
12
|
+
# pages include which nested widgets. Fortunately, Page is now smart enough to
|
13
|
+
# figure out which widgets were actually rendered during the body_content run,
|
14
|
+
# so it only emits into its HEAD the dependencies that are relevant. If it
|
15
|
+
# misses some, or if you want to add some extra dependencies -- for instance,
|
16
|
+
# styles that apply to widgets that are rendered later via AJAX -- then return
|
17
|
+
# an array of those widget classes in your subclass by overriding the
|
18
|
+
# #extra_widgets method.
|
18
19
|
#
|
19
20
|
# If you want something to show up in the headers for just one page type
|
20
21
|
# (subclass), then override #head_content, call super, and then emit it
|
@@ -93,8 +94,9 @@
|
|
93
94
|
# end
|
94
95
|
#
|
95
96
|
# = Thoughts:
|
96
|
-
# * It may be desirable to unify #js and #script, and #css and #style, and
|
97
|
-
# smart enough to analyze its parameter to decide
|
97
|
+
# * It may be desirable to unify #js and #script, and #css and #style, and
|
98
|
+
# have the routine be smart enough to analyze its parameter to decide
|
99
|
+
# whether to make it a file or a script.
|
98
100
|
#
|
99
101
|
class Erector::Widgets::Page < Erector::InlineWidget
|
100
102
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'erector/abstract_widget'
|
2
|
+
require 'erector/tag'
|
3
|
+
require 'erector/needs'
|
4
|
+
|
5
|
+
module Erector
|
6
|
+
|
7
|
+
# Abstract base class for XML Widgets and HTMLWidget.
|
8
|
+
# Declares "tags" which define methods that emit tags.
|
9
|
+
class XMLWidget < AbstractWidget
|
10
|
+
include Needs
|
11
|
+
|
12
|
+
def self.tag_named tag_name, checked = []
|
13
|
+
@tags ||= {}
|
14
|
+
@tags[tag_name] || begin
|
15
|
+
tag = nil
|
16
|
+
checked << self
|
17
|
+
taggy_ancestors = (ancestors - checked).select{|k| k.respond_to? :tag_named}
|
18
|
+
taggy_ancestors.each do |k|
|
19
|
+
tag = k.tag_named(tag_name, checked)
|
20
|
+
if tag
|
21
|
+
@tags[tag_name] = tag
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
tag
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.tag *args
|
30
|
+
tag = Tag.new(*args)
|
31
|
+
@tags ||= {}
|
32
|
+
@tags[tag.name] = tag
|
33
|
+
|
34
|
+
if instance_methods.include?(tag.method_name.to_sym)
|
35
|
+
warn "method '#{tag.method_name}' is already defined; skipping #{caller[1]}"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
if tag.self_closing?
|
40
|
+
self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
|
41
|
+
def #{tag.method_name}(*args, &block)
|
42
|
+
_empty_element('#{tag.name}', *args, &block)
|
43
|
+
end
|
44
|
+
SRC
|
45
|
+
else
|
46
|
+
self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
|
47
|
+
def #{tag.method_name}(*args, &block)
|
48
|
+
_element('#{tag.name}', *args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def #{tag.method_name}!(*args, &block)
|
52
|
+
_element('#{tag.name}', *(args.map{|a|raw(a)}), &block)
|
53
|
+
end
|
54
|
+
SRC
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Tags which are always self-closing
|
59
|
+
def self.self_closing_tags
|
60
|
+
@tags.values.select{|tag| tag.self_closing?}.map{|tag| tag.name}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Tags which can contain other stuff
|
64
|
+
def self.full_tags
|
65
|
+
@tags.values.select{|tag| !tag.self_closing?}.map{|tag| tag.name}
|
66
|
+
end
|
67
|
+
|
68
|
+
def newliney?(tag_name)
|
69
|
+
tag = self.class.tag_named tag_name
|
70
|
+
if tag
|
71
|
+
tag.newliney?
|
72
|
+
else
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\" ?>
|
78
|
+
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
79
|
+
output << raw("<?xml#{format_sorted(sort_for_xml_declaration(attributes))} ?>")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Emits an XML/HTML comment (<!-- ... -->) surrounding +text+ and/or
|
83
|
+
# the output of +block+. see
|
84
|
+
# http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
|
85
|
+
#
|
86
|
+
# If +text+ is an Internet Explorer conditional comment condition such as
|
87
|
+
# "[if IE]", the output includes the opening condition and closing
|
88
|
+
# "[endif]". See http://www.quirksmode.org/css/condcom.html
|
89
|
+
#
|
90
|
+
# Since "Authors should avoid putting two or more adjacent hyphens inside
|
91
|
+
# comments," we emit a warning if you do that.
|
92
|
+
def comment(text = '')
|
93
|
+
puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
|
94
|
+
|
95
|
+
conditional = text =~ /\[if .*\]/
|
96
|
+
|
97
|
+
rawtext "<!--"
|
98
|
+
rawtext text
|
99
|
+
rawtext ">" if conditional
|
100
|
+
|
101
|
+
if block_given?
|
102
|
+
rawtext "\n"
|
103
|
+
yield
|
104
|
+
rawtext "\n"
|
105
|
+
end
|
106
|
+
|
107
|
+
rawtext "<![endif]" if conditional
|
108
|
+
rawtext "-->\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
alias_method :to_xml, :render
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def sort_for_xml_declaration(attributes)
|
116
|
+
# correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
|
117
|
+
# But we only try to put version before encoding for now.
|
118
|
+
stringized = []
|
119
|
+
attributes.each do |key, value|
|
120
|
+
stringized << [key.to_s, value]
|
121
|
+
end
|
122
|
+
stringized.sort{|a, b| b <=> a}
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
public
|
128
|
+
|
129
|
+
XmlWidget = XMLWidget
|
130
|
+
|
131
|
+
end
|