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.
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