builder 2.1.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -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
- bound_method = nil
40
- unbound_method = find_hidden_method(name)
41
- fail "Don't know how to reveal method '#{name}'" unless unbound_method
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
 
@@ -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
- BlankSlate = ::BlankSlate
17
+ if Object::const_defined?(:BasicObject)
18
+ BlankSlate = ::BasicObject
19
+ else
20
+ require 'blankslate'
21
+ BlankSlate = ::BlankSlate
22
+ end
20
23
  end
@@ -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.instance_methods.include?(method_name)
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
- # Enhance the Fixnum class with a XML escaped character conversion.
88
- #
89
- class Fixnum
90
- XChar = Builder::XChar if ! defined?(XChar)
91
-
92
- # XML escaped version of chr
93
- def xchr
94
- n = XChar::CP1252[self] || self
95
- case n when *XChar::VALID
96
- XChar::PREDEFINED[n] or (n<128 ? n.chr : "&##{n};")
97
- else
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 String class with a XML escaped character version of
106
- # to_s.
107
- #
108
- class String
109
- # XML escaped version of to_s
110
- def to_xs
111
- unpack('U*').map {|n| n.xchr}.join # ASCII, UTF-8
112
- rescue
113
- unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252
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
@@ -13,19 +13,22 @@ module Builder
13
13
 
14
14
  # Create an XML markup builder.
15
15
  #
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
- #
22
- def initialize(indent=0, initial=0)
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 arguements are the same as the tags
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, "XmlMarkup cannot mix a text argument with a block"
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
- _nested_structures(block)
59
- _indent
60
- _end_tag(sym)
61
- _newline
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 brakets as:
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 brakets as:
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
- def _escape(text)
115
- text.to_xs
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)
@@ -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") # => <em>emphasized</em>
27
- # xm.em { xmm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
26
+ # xm.em("emphasized") # => <em>emphasized</em>
27
+ # xm.em { xm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
28
28
  # xm.a("A Link", "href"=>"http://onestepback.org")
29
- # # => <a href="http://onestepback.org">A Link</a>
30
- # xm.div { br } # => <div><br/></div>
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
- # # => <target option="fast" name="compile"\>
33
- # # NOTE: order of attributes is not specified.
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. +out+ must respond to the
167
- # <tt><<</tt> operator. The default is a plain string target.
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=><b>OBSOLETE</em>::
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
- "<?#{directive_tag}",
246
- "?>",
247
- nil,
248
- attrs,
249
- [:version, :encoding, :standalone])
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
- fail IllegalBlockError,
322
- "Blocks are not allowed on XML instructions"
326
+ ::Kernel::raise IllegalBlockError.new(
327
+ "Blocks are not allowed on XML instructions"
328
+ )
323
329
  end
324
330
  end
325
331
 
@@ -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