builder 2.1.2 → 3.0.0
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.
- 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
|
|