builder 2.1.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +5 -1
- data/README +20 -1
- data/README.rdoc +232 -0
- data/Rakefile +62 -29
- data/TAGS +55364 -0
- data/doc/releases/builder-2.1.1.rdoc +0 -0
- data/lib/blankslate.rb +3 -7
- data/lib/builder/blankslate.rb +6 -3
- data/lib/builder/xchar.rb +108 -26
- data/lib/builder/xmlbase.rb +40 -19
- data/lib/builder/xmlmarkup.rb +29 -23
- data/test/performance.rb +10 -0
- data/test/preload.rb +10 -0
- data/test/{testblankslate.rb → test_blankslate.rb} +42 -0
- data/test/test_cssbuilder.rb +125 -0
- data/test/{testeventbuilder.rb → test_eventbuilder.rb} +17 -0
- data/test/{testmarkupbuilder.rb → test_markupbuilder.rb} +100 -3
- data/test/test_namecollision.rb +39 -0
- data/test/test_xchar.rb +43 -3
- metadata +77 -48
- data/scripts/publish.rb +0 -17
File without changes
|
data/lib/blankslate.rb
CHANGED
@@ -36,13 +36,9 @@ class BlankSlate
|
|
36
36
|
# Redefine a previously hidden method so that it may be called on a blank
|
37
37
|
# slate object.
|
38
38
|
def reveal(name)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
define_method(name) do |*args|
|
43
|
-
bound_method ||= unbound_method.bind(self)
|
44
|
-
bound_method.call(*args)
|
45
|
-
end
|
39
|
+
hidden_method = find_hidden_method(name)
|
40
|
+
fail "Don't know how to reveal method '#{name}'" unless hidden_method
|
41
|
+
define_method(name, hidden_method)
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
data/lib/builder/blankslate.rb
CHANGED
@@ -8,13 +8,16 @@
|
|
8
8
|
# above copyright notice is included.
|
9
9
|
#++
|
10
10
|
|
11
|
-
require 'blankslate'
|
12
|
-
|
13
11
|
######################################################################
|
14
12
|
# BlankSlate has been promoted to a top level name and is now
|
15
13
|
# available as a standalone gem. We make the name available in the
|
16
14
|
# Builder namespace for compatibility.
|
17
15
|
#
|
18
16
|
module Builder
|
19
|
-
|
17
|
+
if Object::const_defined?(:BasicObject)
|
18
|
+
BlankSlate = ::BasicObject
|
19
|
+
else
|
20
|
+
require 'blankslate'
|
21
|
+
BlankSlate = ::BlankSlate
|
22
|
+
end
|
20
23
|
end
|
data/lib/builder/xchar.rb
CHANGED
@@ -10,14 +10,14 @@
|
|
10
10
|
|
11
11
|
module Builder
|
12
12
|
def self.check_for_name_collision(klass, method_name, defined_constant=nil)
|
13
|
-
if klass.
|
13
|
+
if klass.method_defined?(method_name.to_s)
|
14
14
|
fail RuntimeError,
|
15
15
|
"Name Collision: Method '#{method_name}' is already defined in #{klass}"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
if ! defined?(Builder::XChar)
|
20
|
+
if ! defined?(Builder::XChar) and ! String.method_defined?(:encode)
|
21
21
|
Builder.check_for_name_collision(String, "to_xs")
|
22
22
|
Builder.check_for_name_collision(Fixnum, "xchr")
|
23
23
|
end
|
@@ -78,38 +78,120 @@ module Builder
|
|
78
78
|
(0xE000..0xFFFD),
|
79
79
|
(0x10000..0x10FFFF)
|
80
80
|
]
|
81
|
+
|
82
|
+
# http://www.fileformat.info/info/unicode/char/fffd/index.htm
|
83
|
+
REPLACEMENT_CHAR =
|
84
|
+
if String.method_defined?(:encode)
|
85
|
+
"\uFFFD"
|
86
|
+
elsif $KCODE == 'UTF8'
|
87
|
+
"\xEF\xBF\xBD"
|
88
|
+
else
|
89
|
+
'*'
|
90
|
+
end
|
81
91
|
end
|
82
92
|
|
83
93
|
end
|
84
94
|
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
'
|
96
|
+
if String.method_defined?(:encode)
|
97
|
+
module Builder
|
98
|
+
module XChar # :nodoc:
|
99
|
+
CP1252_DIFFERENCES, UNICODE_EQUIVALENT = Builder::XChar::CP1252.each.
|
100
|
+
inject([[],[]]) {|(domain,range),(key,value)|
|
101
|
+
[domain << key,range << value]
|
102
|
+
}.map {|seq| seq.pack('U*').force_encoding('utf-8')}
|
103
|
+
|
104
|
+
XML_PREDEFINED = Regexp.new('[' +
|
105
|
+
Builder::XChar::PREDEFINED.keys.pack('U*').force_encoding('utf-8') +
|
106
|
+
']')
|
107
|
+
|
108
|
+
INVALID_XML_CHAR = Regexp.new('[^'+
|
109
|
+
Builder::XChar::VALID.map { |item|
|
110
|
+
case item
|
111
|
+
when Fixnum
|
112
|
+
[item].pack('U').force_encoding('utf-8')
|
113
|
+
when Range
|
114
|
+
[item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8')
|
115
|
+
end
|
116
|
+
}.join +
|
117
|
+
']')
|
118
|
+
|
119
|
+
ENCODING_BINARY = Encoding.find('BINARY')
|
120
|
+
ENCODING_UTF8 = Encoding.find('UTF-8')
|
121
|
+
ENCODING_ISO1 = Encoding.find('ISO-8859-1')
|
122
|
+
|
123
|
+
# convert a string to valid UTF-8, compensating for a number of
|
124
|
+
# common errors.
|
125
|
+
def XChar.unicode(string)
|
126
|
+
if string.encoding == ENCODING_BINARY
|
127
|
+
if string.ascii_only?
|
128
|
+
string
|
129
|
+
else
|
130
|
+
string = string.clone.force_encoding(ENCODING_UTF8)
|
131
|
+
if string.valid_encoding?
|
132
|
+
string
|
133
|
+
else
|
134
|
+
string.encode(ENCODING_UTF8, ENCODING_ISO1)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
elsif string.encoding == ENCODING_UTF8
|
139
|
+
if string.valid_encoding?
|
140
|
+
string
|
141
|
+
else
|
142
|
+
string.encode(ENCODING_UTF8, ENCODING_ISO1)
|
143
|
+
end
|
144
|
+
|
145
|
+
else
|
146
|
+
string.encode(ENCODING_UTF8)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# encode a string per XML rules
|
151
|
+
def XChar.encode(string)
|
152
|
+
unicode(string).
|
153
|
+
tr(CP1252_DIFFERENCES, UNICODE_EQUIVALENT).
|
154
|
+
gsub(INVALID_XML_CHAR, REPLACEMENT_CHAR).
|
155
|
+
gsub(XML_PREDEFINED) {|c| PREDEFINED[c.ord]}
|
156
|
+
end
|
99
157
|
end
|
100
158
|
end
|
101
|
-
end
|
102
159
|
|
160
|
+
else
|
103
161
|
|
104
|
-
######################################################################
|
105
|
-
# Enhance the
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
162
|
+
######################################################################
|
163
|
+
# Enhance the Fixnum class with a XML escaped character conversion.
|
164
|
+
#
|
165
|
+
class Fixnum
|
166
|
+
XChar = Builder::XChar if ! defined?(XChar)
|
167
|
+
|
168
|
+
# XML escaped version of chr. When <tt>escape</tt> is set to false
|
169
|
+
# the CP1252 fix is still applied but utf-8 characters are not
|
170
|
+
# converted to character entities.
|
171
|
+
def xchr(escape=true)
|
172
|
+
n = XChar::CP1252[self] || self
|
173
|
+
case n when *XChar::VALID
|
174
|
+
XChar::PREDEFINED[n] or
|
175
|
+
(n<128 ? n.chr : (escape ? "&##{n};" : [n].pack('U*')))
|
176
|
+
else
|
177
|
+
Builder::XChar::REPLACEMENT_CHAR
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
######################################################################
|
184
|
+
# Enhance the String class with a XML escaped character version of
|
185
|
+
# to_s.
|
186
|
+
#
|
187
|
+
class String
|
188
|
+
# XML escaped version of to_s. When <tt>escape</tt> is set to false
|
189
|
+
# the CP1252 fix is still applied but utf-8 characters are not
|
190
|
+
# converted to character entities.
|
191
|
+
def to_xs(escape=true)
|
192
|
+
unpack('U*').map {|n| n.xchr(escape)}.join # ASCII, UTF-8
|
193
|
+
rescue
|
194
|
+
unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252
|
195
|
+
end
|
114
196
|
end
|
115
197
|
end
|
data/lib/builder/xmlbase.rb
CHANGED
@@ -13,19 +13,22 @@ module Builder
|
|
13
13
|
|
14
14
|
# Create an XML markup builder.
|
15
15
|
#
|
16
|
-
# out::
|
17
|
-
#
|
18
|
-
# indent::
|
19
|
-
#
|
20
|
-
# initial::
|
21
|
-
#
|
22
|
-
|
16
|
+
# out:: Object receiving the markup. +out+ must respond to
|
17
|
+
# <tt><<</tt>.
|
18
|
+
# indent:: Number of spaces used for indentation (0 implies no
|
19
|
+
# indentation and no line breaks).
|
20
|
+
# initial:: Level of initial indentation.
|
21
|
+
# encoding:: When <tt>encoding</tt> and $KCODE are set to 'utf-8'
|
22
|
+
# characters aren't converted to character entities in
|
23
|
+
# the output stream.
|
24
|
+
def initialize(indent=0, initial=0, encoding='utf-8')
|
23
25
|
@indent = indent
|
24
26
|
@level = initial
|
27
|
+
@encoding = encoding.downcase
|
25
28
|
end
|
26
29
|
|
27
30
|
# Create a tag named +sym+. Other than the first argument which
|
28
|
-
# is the tag name, the
|
31
|
+
# is the tag name, the arguments are the same as the tags
|
29
32
|
# implemented via <tt>method_missing</tt>.
|
30
33
|
def tag!(sym, *args, &block)
|
31
34
|
method_missing(sym.to_sym, *args, &block)
|
@@ -37,10 +40,10 @@ module Builder
|
|
37
40
|
def method_missing(sym, *args, &block)
|
38
41
|
text = nil
|
39
42
|
attrs = nil
|
40
|
-
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol)
|
43
|
+
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
|
41
44
|
args.each do |arg|
|
42
45
|
case arg
|
43
|
-
when Hash
|
46
|
+
when ::Hash
|
44
47
|
attrs ||= {}
|
45
48
|
attrs.merge!(arg)
|
46
49
|
else
|
@@ -50,15 +53,19 @@ module Builder
|
|
50
53
|
end
|
51
54
|
if block
|
52
55
|
unless text.nil?
|
53
|
-
raise ArgumentError,
|
56
|
+
::Kernel::raise ::ArgumentError,
|
57
|
+
"XmlMarkup cannot mix a text argument with a block"
|
54
58
|
end
|
55
59
|
_indent
|
56
60
|
_start_tag(sym, attrs)
|
57
61
|
_newline
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
begin
|
63
|
+
_nested_structures(block)
|
64
|
+
ensure
|
65
|
+
_indent
|
66
|
+
_end_tag(sym)
|
67
|
+
_newline
|
68
|
+
end
|
62
69
|
elsif text.nil?
|
63
70
|
_indent
|
64
71
|
_start_tag(sym, attrs, true)
|
@@ -74,7 +81,7 @@ module Builder
|
|
74
81
|
end
|
75
82
|
|
76
83
|
# Append text to the output target. Escape any markup. May be
|
77
|
-
# used within the markup
|
84
|
+
# used within the markup brackets as:
|
78
85
|
#
|
79
86
|
# builder.p { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p>
|
80
87
|
def text!(text)
|
@@ -82,7 +89,7 @@ module Builder
|
|
82
89
|
end
|
83
90
|
|
84
91
|
# Append text to the output target without escaping any markup.
|
85
|
-
# May be used within the markup
|
92
|
+
# May be used within the markup brackets as:
|
86
93
|
#
|
87
94
|
# builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
|
88
95
|
#
|
@@ -111,8 +118,22 @@ module Builder
|
|
111
118
|
private
|
112
119
|
|
113
120
|
require 'builder/xchar'
|
114
|
-
|
115
|
-
text
|
121
|
+
if ::String.method_defined?(:encode)
|
122
|
+
def _escape(text)
|
123
|
+
result = XChar.encode(text)
|
124
|
+
begin
|
125
|
+
result.encode(@encoding)
|
126
|
+
rescue
|
127
|
+
# if the encoding can't be supported, use numeric character references
|
128
|
+
result.
|
129
|
+
gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}.
|
130
|
+
force_encoding('ascii')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else
|
134
|
+
def _escape(text)
|
135
|
+
text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))
|
136
|
+
end
|
116
137
|
end
|
117
138
|
|
118
139
|
def _escape_quote(text)
|
data/lib/builder/xmlmarkup.rb
CHANGED
@@ -23,14 +23,14 @@ module Builder
|
|
23
23
|
# Examples will demonstrate this easier than words. In the
|
24
24
|
# following, +xm+ is an +XmlMarkup+ object.
|
25
25
|
#
|
26
|
-
# xm.em("emphasized")
|
27
|
-
# xm.em {
|
26
|
+
# xm.em("emphasized") # => <em>emphasized</em>
|
27
|
+
# xm.em { xm.b("emp & bold") } # => <em><b>emph & bold</b></em>
|
28
28
|
# xm.a("A Link", "href"=>"http://onestepback.org")
|
29
|
-
#
|
30
|
-
# xm.div { br }
|
29
|
+
# # => <a href="http://onestepback.org">A Link</a>
|
30
|
+
# xm.div { xm.br } # => <div><br/></div>
|
31
31
|
# xm.target("name"=>"compile", "option"=>"fast")
|
32
|
-
#
|
33
|
-
#
|
32
|
+
# # => <target option="fast" name="compile"\>
|
33
|
+
# # NOTE: order of attributes is not specified.
|
34
34
|
#
|
35
35
|
# xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
|
36
36
|
# xm.html { # <html>
|
@@ -163,8 +163,9 @@ module Builder
|
|
163
163
|
# option hash.
|
164
164
|
#
|
165
165
|
# :target=><em>target_object</em>::
|
166
|
-
# Object receiving the markup. +
|
167
|
-
# <tt
|
166
|
+
# Object receiving the markup. +target_object+ must respond to
|
167
|
+
# the <tt><<(<em>a_string</em>)</tt> operator and return
|
168
|
+
# itself. The default target is a plain string target.
|
168
169
|
#
|
169
170
|
# :indent=><em>indentation</em>::
|
170
171
|
# Number of spaces used for indentation. The default is no
|
@@ -174,7 +175,7 @@ module Builder
|
|
174
175
|
# Amount of initial indentation (specified in levels, not
|
175
176
|
# spaces).
|
176
177
|
#
|
177
|
-
# :escape_attrs=><
|
178
|
+
# :escape_attrs=><em>OBSOLETE</em>::
|
178
179
|
# The :escape_attrs option is no longer supported by builder
|
179
180
|
# (and will be quietly ignored). String attribute values are
|
180
181
|
# now automatically escaped. If you need unescaped attribute
|
@@ -195,7 +196,7 @@ module Builder
|
|
195
196
|
end
|
196
197
|
|
197
198
|
def comment!(comment_text)
|
198
|
-
_ensure_no_block block_given?
|
199
|
+
_ensure_no_block ::Kernel::block_given?
|
199
200
|
_special("<!-- ", " -->", comment_text, nil)
|
200
201
|
end
|
201
202
|
|
@@ -210,13 +211,13 @@ module Builder
|
|
210
211
|
@target << "<!#{inst}"
|
211
212
|
args.each do |arg|
|
212
213
|
case arg
|
213
|
-
when String
|
214
|
+
when ::String
|
214
215
|
@target << %{ "#{arg}"} # " WART
|
215
|
-
when Symbol
|
216
|
+
when ::Symbol
|
216
217
|
@target << " #{arg}"
|
217
218
|
end
|
218
219
|
end
|
219
|
-
if block_given?
|
220
|
+
if ::Kernel::block_given?
|
220
221
|
@target << " ["
|
221
222
|
_newline
|
222
223
|
_nested_structures(block)
|
@@ -235,18 +236,22 @@ module Builder
|
|
235
236
|
# xml.instruct! :aaa, :bbb=>"ccc"
|
236
237
|
# #=> <?aaa bbb="ccc"?>
|
237
238
|
#
|
239
|
+
# Note: If the encoding is setup to "UTF-8" and the value of
|
240
|
+
# $KCODE is "UTF8", then builder will emit UTF-8 encoded strings
|
241
|
+
# rather than the entity encoding normally used.
|
238
242
|
def instruct!(directive_tag=:xml, attrs={})
|
239
|
-
_ensure_no_block block_given?
|
243
|
+
_ensure_no_block ::Kernel::block_given?
|
240
244
|
if directive_tag == :xml
|
241
245
|
a = { :version=>"1.0", :encoding=>"UTF-8" }
|
242
246
|
attrs = a.merge attrs
|
247
|
+
@encoding = attrs[:encoding].downcase
|
243
248
|
end
|
244
249
|
_special(
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
+
"<?#{directive_tag}",
|
251
|
+
"?>",
|
252
|
+
nil,
|
253
|
+
attrs,
|
254
|
+
[:version, :encoding, :standalone])
|
250
255
|
end
|
251
256
|
|
252
257
|
# Insert a CDATA section into the XML markup.
|
@@ -257,7 +262,7 @@ module Builder
|
|
257
262
|
# #=> <![CDATA[text to be included in cdata]]>
|
258
263
|
#
|
259
264
|
def cdata!(text)
|
260
|
-
_ensure_no_block block_given?
|
265
|
+
_ensure_no_block ::Kernel::block_given?
|
261
266
|
_special("<![CDATA[", "]]>", text, nil)
|
262
267
|
end
|
263
268
|
|
@@ -309,7 +314,7 @@ module Builder
|
|
309
314
|
|
310
315
|
def _attr_value(value)
|
311
316
|
case value
|
312
|
-
when Symbol
|
317
|
+
when ::Symbol
|
313
318
|
value.to_s
|
314
319
|
else
|
315
320
|
_escape_quote(value.to_s)
|
@@ -318,8 +323,9 @@ module Builder
|
|
318
323
|
|
319
324
|
def _ensure_no_block(got_block)
|
320
325
|
if got_block
|
321
|
-
|
322
|
-
|
326
|
+
::Kernel::raise IllegalBlockError.new(
|
327
|
+
"Blocks are not allowed on XML instructions"
|
328
|
+
)
|
323
329
|
end
|
324
330
|
end
|
325
331
|
|
data/test/performance.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
#--
|
4
|
+
# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net).
|
6
|
+
# All rights reserved.
|
7
|
+
|
8
|
+
# Permission is granted for use, copying, modification, distribution,
|
9
|
+
# and distribution of modified versions of this work as long as the
|
10
|
+
# above copyright notice is included.
|
11
|
+
#++
|
12
|
+
|
3
13
|
require 'builder/xmlmarkup'
|
4
14
|
require 'benchmark'
|
5
15
|
|