tobyh-markaby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,265 @@
1
+ This fork:
2
+ * has a .gemspec to make use of the github gem server
3
+ * is compatible with ruby 1.9
4
+ * does not have a gem dependency on 'builder'
5
+ * requires 'activesupport' if 'builder' is not available
6
+ * is surely of no use to anyone but me (tobyh)
7
+
8
+ --------
9
+
10
+ = Markaby (Markup as Ruby)
11
+
12
+ Markaby is a very short bit of code for writing HTML pages in pure Ruby.
13
+ It is an alternative to ERb which weaves the two languages together.
14
+ Also a replacement for templating languages which use primitive languages
15
+ that blend with HTML.
16
+
17
+ == Using Markaby as a Rails plugin
18
+
19
+ Write Rails templates in pure Ruby. Example layout:
20
+
21
+ html do
22
+ head do
23
+ title 'Products: ' + action_name
24
+ stylesheet_link_tag 'scaffold'
25
+ end
26
+
27
+ body do
28
+ p flash[:notice], :style => "color: green"
29
+
30
+ self << content_for_layout
31
+ end
32
+ end
33
+
34
+ == Using Markaby as a Ruby class
35
+
36
+ Markaby is flaming easy to call from your Ruby classes.
37
+
38
+ require 'markaby'
39
+
40
+ mab = Markaby::Builder.new
41
+ mab.html do
42
+ head { title "Boats.com" }
43
+ body do
44
+ h1 "Boats.com has great deals"
45
+ ul do
46
+ li "$49 for a canoe"
47
+ li "$39 for a raft"
48
+ li "$29 for a huge boot that floats and can fit 5 people"
49
+ end
50
+ end
51
+ end
52
+ puts mab.to_s
53
+
54
+ Markaby::Builder.new does take two arguments for passing in variables and
55
+ a helper object. You can also affix the block right on to the class.
56
+
57
+ See Markaby::Builder for all of that.
58
+
59
+ = A Note About <tt>instance_eval</tt>
60
+
61
+ The Markaby::Builder class is different from the normal Builder class,
62
+ since it uses <tt>instance_eval</tt> when running blocks. This cleans
63
+ up the appearance of the Markaby code you write. If <tt>instance_eval</tt>
64
+ was not used, the code would look like this:
65
+
66
+ mab = Markaby::Builder.new
67
+ mab.html do
68
+ mab.head { mab.title "Boats.com" }
69
+ mab.body do
70
+ mab.h1 "Boats.com has great deals"
71
+ end
72
+ end
73
+ puts mab.to_s
74
+
75
+ So, the advantage is the cleanliness of your code. The disadvantage is that
76
+ the block will run inside the Markaby::Builder object's scope. This means
77
+ that inside these blocks, <tt>self</tt> will be your Markaby::Builder object.
78
+ When you use instance variables in these blocks, they will be instance variables
79
+ of the Markaby::Builder object.
80
+
81
+ This doesn't affect Rails users, but when used in regular Ruby code, it can
82
+ be a bit disorienting. You are recommended to put your Markaby code in a
83
+ module where it won't mix with anything.
84
+
85
+ = The Six Steps of Markaby
86
+
87
+ If you dive right into Markaby, it'll probably make good sense, but you're
88
+ likely to run into a few kinks. Why not review these six steps and commit
89
+ them memory so you can really *know* what you're doing?
90
+
91
+ == 1. Element Classes
92
+
93
+ Element classes may be added by hooking methods onto container elements:
94
+
95
+ div.entry do
96
+ h2.entryTitle 'Son of WebPage'
97
+ div.entrySection %{by Anthony}
98
+ div.entryContent 'Okay, once again, the idea here is ...'
99
+ end
100
+
101
+ Which results in:
102
+
103
+ <div class="entry">
104
+ <h2 class="entryTitle">Son of WebPage</h2>
105
+ <div class="entrySection">by Anthony</div>
106
+ <div class="entryContent">Okay, once again, the idea here is ...</div>
107
+ </div>
108
+
109
+ == 2. Element IDs
110
+
111
+ IDs may be added by the use of bang methods:
112
+
113
+ div.page! {
114
+ div.content! {
115
+ h1 "A Short Short Saintly Dog"
116
+ }
117
+ }
118
+
119
+ Which results in:
120
+
121
+ <div id="page">
122
+ <div id="content">
123
+ <h1>A Short Short Saintly Dog</h1>
124
+ </div>
125
+ </div>
126
+
127
+ == 3. Validate Your XHTML 1.0 Output
128
+
129
+ If you'd like Markaby to help you assemble valid XHTML documents,
130
+ you can use the <tt>xhtml_transitional</tt> or <tt>xhtml_strict</tt>
131
+ methods in place of the normal <tt>html</tt> tag.
132
+
133
+ xhtml_strict do
134
+ head { ... }
135
+ body { ... }
136
+ end
137
+
138
+ This will add the XML instruction and the doctype tag to your document.
139
+ Also, a character set meta tag will be placed inside your <tt>head</tt>
140
+ tag.
141
+
142
+ Now, since Markaby knows which doctype you're using, it checks a big
143
+ list of valid tags and attributes before printing anything.
144
+
145
+ >> div :styl => "padding: 10px" do
146
+ >> img :src => "samorost.jpg"
147
+ >> end
148
+ InvalidHtmlError: no such attribute `styl'
149
+
150
+ Markaby will also make sure you don't use the same element ID twice!
151
+
152
+ == 4. Escape or No Escape?
153
+
154
+ Markaby uses a simple convention for escaping stuff: if a string
155
+ is an argument, it gets escaped. If the string is in a block, it
156
+ doesn't.
157
+
158
+ This is handy if you're using something like RedCloth or
159
+ RDoc inside an element. Pass the string back through the block
160
+ and it'll skip out of escaping.
161
+
162
+ div.comment { RedCloth.new(str).to_html }
163
+
164
+ But, if we have some raw text that needs escaping, pass it in
165
+ as an argument:
166
+
167
+ div.comment raw_str
168
+
169
+ One caveat: if you have other tags inside a block, the string
170
+ passed back will be ignored.
171
+
172
+ div.comment {
173
+ div.author "_why"
174
+ div.says "Torpedoooooes!"
175
+ "<div>Silence.</div>"
176
+ }
177
+
178
+ The final div above won't appear in the output. You can't mix
179
+ tag modes like that, friend.
180
+
181
+ == 5. Auto-stringification
182
+
183
+ If you end up using any of your Markaby "tags" as a string, the
184
+ tag won't be output. It'll be up to you to add the new string
185
+ back into the HTML output.
186
+
187
+ This means if you call <tt>to_s</tt>, you'll get a string back.
188
+
189
+ div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s }
190
+
191
+ But, when you're adding strings in Ruby, <tt>to_s</tt> happens automatically.
192
+
193
+ div.title { "Rock Bottom" + span(" by Robert Wyatt") }
194
+
195
+ Interpolation works fine.
196
+
197
+ div.title { "Rock Bottom #{span(" by Robert Wyatt")}" }
198
+
199
+ And any other operation you might perform on a string.
200
+
201
+ div.menu! \
202
+ ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category|
203
+ link_to category
204
+ end.
205
+ join( " | " )
206
+
207
+ == 6. The <tt>tag!</tt> Method
208
+
209
+ If you need to force a tag at any time, call <tt>tag!</tt> with the
210
+ tag name followed by the possible arguments and block. The CssProxy
211
+ won't work with this technique.
212
+
213
+ tag! :select, :id => "country_list" do
214
+ countries.each do |country|
215
+ tag! :option, country
216
+ end
217
+ end
218
+
219
+ = A Note About Rails Helpers
220
+
221
+ When used in Rails templates, the Rails helper object is passed into
222
+ Markaby::Builder. When you call helper methods inside Markaby, the output
223
+ from those methods will be output to the stream. This is incredibly
224
+ handy, since most Rails helpers output HTML tags.
225
+
226
+ head do
227
+ javascript_include_tag 'prototype'
228
+ autodiscovery_link_tag
229
+ end
230
+
231
+ However, some methods are designed to give back a String which you can use
232
+ elsewhere. That's okay! Every method returns a Fragment object, which can
233
+ be used as a string.
234
+
235
+ p { "Total is: #{number_to_human_size @file_bytes}" }
236
+
237
+ Also see the Quick Tour above, specifically the stuff about auto-stringification.
238
+
239
+ If for any reason you have trouble with fragments, you can just
240
+ call the <tt>@helpers</tt> object with the method and you'll get
241
+ the String back and nothing will be output.
242
+
243
+ p { "Total is: #{@helpers.number_to_human_size @file_bytes}" }
244
+
245
+ Conversely, you may call instance variables from your controller by using
246
+ a method and its value will be returned, nothing will be output.
247
+
248
+ # Inside imaginary ProductController
249
+ def list
250
+ @products = Product.find :all
251
+ end
252
+
253
+ # Inside app/views/product/list.mab
254
+ products.each do |product|
255
+ p product.title
256
+ end
257
+
258
+ = Credits
259
+
260
+ Markaby is a work of immense hope by Tim Fletcher and why the lucky stiff.
261
+ Thankyou for giving it a whirl.
262
+
263
+ Markaby is inspired by the HTML library within cgi.rb. Hopefully it will
264
+ turn around and take some cues.
265
+
@@ -0,0 +1,62 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'tools/rakehelp'
7
+ require 'fileutils'
8
+ include FileUtils
9
+
10
+ REV = File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
11
+ VERS = ENV['VERSION'] || "0.5" + (REV ? ".#{REV}" : "")
12
+
13
+ task :default => [:package]
14
+
15
+ setup_tests
16
+ setup_rdoc ['README', 'CHANGELOG', 'lib/**/*.rb']
17
+
18
+ summary = "Markup as Ruby, write HTML in your native Ruby tongue"
19
+ test_file = "test/test_markaby.rb"
20
+ setup_gem("markaby", VERS, "Tim Fletcher and _why", summary, [['builder', '>=2.0.0']], test_file)
21
+
22
+ desc "List any Markaby specific warnings"
23
+ task :warnings do
24
+ `ruby -w test/test_markaby.rb 2>&1`.split(/\n/).each do |line|
25
+ next unless line =~ /warning:/
26
+ next if line =~ /builder-/
27
+ puts line
28
+ end
29
+ end
30
+
31
+ desc "Start a Markaby-aware IRB session"
32
+ task :irb do
33
+ sh 'irb -I lib -r markaby -r markaby/kernel_method'
34
+ end
35
+
36
+ namespace :test do
37
+ desc ''
38
+ task :rails do
39
+ Dir.chdir '../../../'
40
+ sh 'rake test:plugins PLUGIN=markaby'
41
+ end
42
+ end
43
+
44
+ begin
45
+ require 'jeweler'
46
+ Jeweler::Tasks.new do |gemspec|
47
+ gemspec.name = "markaby"
48
+ gemspec.summary = "Markup as Ruby, write HTML in your native Ruby tongue"
49
+ gemspec.authors = 'Tim Fletcher and _why'
50
+ gemspec.test_file = "test/test_markaby.rb"
51
+ gemspec.platform = Gem::Platform::RUBY
52
+ gemspec.has_rdoc = true
53
+ gemspec.extra_rdoc_files = [ "README" ]
54
+ gemspec.files = %w(README Rakefile setup.rb) +
55
+ Dir.glob("{bin,doc,test,lib}/**/*") +
56
+ Dir.glob("ext/**/*.{h,c,rb}") +
57
+ Dir.glob("examples/**/*.rb") +
58
+ Dir.glob("tools/*.rb")
59
+ end
60
+ rescue LoadError
61
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
62
+ end
@@ -0,0 +1,43 @@
1
+ # = About lib/markaby.rb
2
+ #
3
+ # By requiring <tt>lib/markaby</tt>, you can load Markaby's dependency (the Builder library,)
4
+ # as well as the full set of Markaby classes.
5
+ #
6
+ # For a full list of features and instructions, see the README.
7
+ $:.unshift File.expand_path(File.dirname(__FILE__))
8
+
9
+ # Markaby is a module containing all of the great Markaby classes that
10
+ # do such an excellent job.
11
+ #
12
+ # * Markaby::Builder: the class for actually calling the Ruby methods
13
+ # which write the HTML.
14
+ # * Markaby::CSSProxy: a class which adds element classes and IDs to
15
+ # elements when used within Markaby::Builder.
16
+ # * Markaby::MetAid: metaprogramming helper methods.
17
+ # * Markaby::Tags: lists the roles of various XHTML tags to help Builder
18
+ # use these tags as they are intended.
19
+ # * Markaby::Template: a class for hooking Markaby into Rails as a
20
+ # proper templating language.
21
+ module Markaby
22
+ VERSION = '0.5'
23
+
24
+ class InvalidXhtmlError < StandardError; end
25
+ end
26
+
27
+ unless defined?(Builder)
28
+ require 'rubygems'
29
+ begin
30
+ require 'builder'
31
+ rescue LoadError => e
32
+ begin
33
+ require 'activesupport'
34
+ rescue LoadError
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'markaby/builder'
41
+ require 'markaby/cssproxy'
42
+ require 'markaby/metaid'
43
+ require 'markaby/template'
@@ -0,0 +1,292 @@
1
+ require 'markaby/tags'
2
+
3
+ module Markaby
4
+ # The Markaby::Builder class is the central gear in the system. When using
5
+ # from Ruby code, this is the only class you need to instantiate directly.
6
+ #
7
+ # mab = Markaby::Builder.new
8
+ # mab.html do
9
+ # head { title "Boats.com" }
10
+ # body do
11
+ # h1 "Boats.com has great deals"
12
+ # ul do
13
+ # li "$49 for a canoe"
14
+ # li "$39 for a raft"
15
+ # li "$29 for a huge boot that floats and can fit 5 people"
16
+ # end
17
+ # end
18
+ # end
19
+ # puts mab.to_s
20
+ #
21
+ class Builder
22
+
23
+ @@default = {
24
+ :indent => 0,
25
+ :output_helpers => true,
26
+ :output_xml_instruction => true,
27
+ :output_meta_tag => true,
28
+ :auto_validation => true,
29
+ :tagset => Markaby::XHTMLTransitional,
30
+ :root_attributes => {
31
+ :xmlns => 'http://www.w3.org/1999/xhtml', :'xml:lang' => 'en', :lang => 'en'
32
+ }
33
+ }
34
+
35
+ def self.set(option, value)
36
+ @@default[option] = value
37
+ end
38
+
39
+ def self.ignored_helpers
40
+ @@ignored_helpers ||= []
41
+ end
42
+
43
+ def self.ignore_helpers(*helpers)
44
+ ignored_helpers.concat helpers
45
+ end
46
+
47
+ attr_accessor :output_helpers, :tagset
48
+
49
+ # Create a Markaby builder object. Pass in a hash of variable assignments to
50
+ # +assigns+ which will be available as instance variables inside tag construction
51
+ # blocks. If an object is passed in to +helpers+, its methods will be available
52
+ # from those same blocks.
53
+ #
54
+ # Pass in a +block+ to new and the block will be evaluated.
55
+ #
56
+ # mab = Markaby::Builder.new {
57
+ # html do
58
+ # body do
59
+ # h1 "Matching Mole"
60
+ # end
61
+ # end
62
+ # }
63
+ #
64
+ def initialize(assigns = {}, helpers = nil, &block)
65
+ @streams = [[]]
66
+ @assigns = assigns.dup
67
+ @helpers = helpers
68
+ @elements = {}
69
+
70
+ @@default.each do |k, v|
71
+ instance_variable_set("@#{k}", @assigns.delete(k) || v)
72
+ end
73
+
74
+ @assigns.each do |k, v|
75
+ instance_variable_set("@#{k}", v)
76
+ end
77
+
78
+ @builder = XmlMarkup.new(:indent => @indent, :target => @streams.last)
79
+
80
+ text(capture(&block)) if block
81
+ end
82
+
83
+ # Returns a string containing the HTML stream. Internally, the stream is stored as an Array.
84
+ def to_s
85
+ @streams.last.join
86
+ end
87
+
88
+ # Write a +string+ to the HTML stream without escaping it.
89
+ def text(string)
90
+ @builder << string.to_s
91
+ nil
92
+ end
93
+ alias_method :<<, :text
94
+ alias_method :concat, :text
95
+
96
+ # Captures the HTML code built inside the +block+. This is done by creating a new
97
+ # stream for the builder object, running the block and passing back its stream as a string.
98
+ #
99
+ # >> Markaby::Builder.new.capture { h1 "TEST"; h2 "CAPTURE ME" }
100
+ # => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
101
+ #
102
+ def capture(&block)
103
+ @streams.push(@builder.target = [])
104
+ @builder.level += 1
105
+ str = instance_eval(&block)
106
+ str = @streams.last.join if @streams.last.any?
107
+ @streams.pop
108
+ @builder.level -= 1 if @builder.level > 1
109
+ @builder.target = @streams.last
110
+ str
111
+ end
112
+
113
+ # Create a tag named +tag+. Other than the first argument which is the tag name,
114
+ # the arguments are the same as the tags implemented via method_missing.
115
+ def tag!(tag, *args, &block)
116
+ ele_id = nil
117
+ if @auto_validation and @tagset
118
+ if !@tagset.tagset.has_key?(tag)
119
+ raise InvalidXhtmlError, "no element `#{tag}' for #{tagset.doctype}"
120
+ elsif args.last.respond_to?(:to_hash)
121
+ attrs = args.last.to_hash
122
+
123
+ if @tagset.forms.include?(tag) and attrs[:id]
124
+ attrs[:name] ||= attrs[:id]
125
+ end
126
+
127
+ attrs.each do |k, v|
128
+ atname = k.to_s.downcase.intern
129
+ unless k =~ /:/ or @tagset.tagset[tag].include? atname
130
+ raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements"
131
+ end
132
+ if atname == :id
133
+ ele_id = v.to_s
134
+ if @elements.has_key? ele_id
135
+ raise InvalidXhtmlError, "id `#{ele_id}' already used (id's must be unique)."
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ if block
142
+ str = capture(&block)
143
+ block = proc { text(str) }
144
+ elsif !@tagset.self_closing.include?(tag.to_sym) && !args.find{|t|!t.kind_of?(Hash)}
145
+ block = proc { }
146
+ end
147
+
148
+ f = fragment { @builder.method_missing(tag, *args, &block) }
149
+ @elements[ele_id] = f if ele_id
150
+ f
151
+ end
152
+
153
+ # This method is used to intercept calls to helper methods and instance
154
+ # variables. Here is the order of interception:
155
+ #
156
+ # * If +sym+ is a helper method, the helper method is called
157
+ # and output to the stream.
158
+ # * If +sym+ is a Builder::XmlMarkup method, it is passed on to the builder object.
159
+ # * If +sym+ is also the name of an instance variable, the
160
+ # value of the instance variable is returned.
161
+ # * If +sym+ has come this far and no +tagset+ is found, +sym+ and its arguments are passed to tag!
162
+ # * If a tagset is found, though, +NoMethodError+ is raised.
163
+ #
164
+ # method_missing used to be the lynchpin in Markaby, but it's no longer used to handle
165
+ # HTML tags. See html_tag for that.
166
+ def method_missing(sym, *args, &block)
167
+ if @helpers.respond_to?(sym, true) && !self.class.ignored_helpers.include?(sym)
168
+ r = @helpers.send(sym, *args, &block)
169
+ if @output_helpers and r.respond_to? :to_str
170
+ fragment { @builder << r }
171
+ else
172
+ r
173
+ end
174
+ elsif @assigns.has_key?(sym)
175
+ @assigns[sym]
176
+ elsif @assigns.has_key?(stringy_key = sym.to_s)
177
+ # Rails' ActionView assigns hash has string keys for
178
+ # instance variables that are defined in the controller.
179
+ @assigns[stringy_key]
180
+ elsif instance_variables.include?(ivar = "@#{sym}")
181
+ instance_variable_get(ivar)
182
+ elsif !@helpers.nil? && @helpers.instance_variables.include?(ivar)
183
+ @helpers.instance_variable_get(ivar)
184
+ elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s)
185
+ @builder.__send__(sym, *args, &block)
186
+ elsif @tagset.nil?
187
+ tag!(sym, *args, &block)
188
+ else
189
+ raise NoMethodError, "no such method `#{sym}'"
190
+ end
191
+ end
192
+
193
+ # Every HTML tag method goes through an html_tag call. So, calling <tt>div</tt> is equivalent
194
+ # to calling <tt>html_tag(:div)</tt>. All HTML tags in Markaby's list are given generated wrappers
195
+ # for this method.
196
+ #
197
+ # If the @auto_validation setting is on, this method will check for many common mistakes which
198
+ # could lead to invalid XHTML.
199
+ def html_tag(sym, *args, &block)
200
+ if @auto_validation and @tagset.self_closing.include?(sym) and block
201
+ raise InvalidXhtmlError, "the `#{sym}' element is self-closing, please remove the block"
202
+ elsif args.empty? and block.nil?
203
+ CssProxy.new(self, @streams.last, sym)
204
+ else
205
+ tag!(sym, *args, &block)
206
+ end
207
+ end
208
+
209
+ XHTMLTransitional.tags.each do |k|
210
+ class_eval %{
211
+ def #{k}(*args, &block)
212
+ html_tag(#{k.inspect}, *args, &block)
213
+ end
214
+ }
215
+ end
216
+
217
+ remove_method :head
218
+
219
+ # Builds a head tag. Adds a <tt>meta</tt> tag inside with Content-Type
220
+ # set to <tt>text/html; charset=utf-8</tt>.
221
+ def head(*args, &block)
222
+ tag!(:head, *args) do
223
+ tag!(:meta, "http-equiv" => "Content-Type", "content" => "text/html; charset=utf-8") if @output_meta_tag
224
+ instance_eval(&block)
225
+ end
226
+ end
227
+
228
+ # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype
229
+ # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml",
230
+ # :lang => "en"</tt>.
231
+ def xhtml_transitional(attrs = {}, &block)
232
+ self.tagset = Markaby::XHTMLTransitional
233
+ xhtml_html(attrs, &block)
234
+ end
235
+
236
+ # Builds an html tag with XHTML 1.0 Strict doctype instead.
237
+ def xhtml_strict(attrs = {}, &block)
238
+ self.tagset = Markaby::XHTMLStrict
239
+ xhtml_html(attrs, &block)
240
+ end
241
+
242
+ # Builds an html tag with XHTML 1.0 Frameset doctype instead.
243
+ def xhtml_frameset(attrs = {}, &block)
244
+ self.tagset = Markaby::XHTMLFrameset
245
+ xhtml_html(attrs, &block)
246
+ end
247
+
248
+ private
249
+
250
+ def xhtml_html(attrs = {}, &block)
251
+ @builder.level = 0
252
+ @builder.instruct! if @output_xml_instruction
253
+ @builder.declare!(:DOCTYPE, :html, :PUBLIC, *tagset.doctype)
254
+ tag!(:html, @root_attributes.merge(attrs), &block)
255
+ end
256
+
257
+ def fragment
258
+ stream = @streams.last
259
+ start = stream.length
260
+ yield
261
+ length = stream.length - start
262
+ Fragment.new(stream, start, length)
263
+ end
264
+
265
+ end
266
+
267
+ # Every tag method in Markaby returns a Fragment. If any method gets called on the Fragment,
268
+ # the tag is removed from the Markaby stream and given back as a string. Usually the fragment
269
+ # is never used, though, and the stream stays intact.
270
+ #
271
+ # For a more practical explanation, check out the README.
272
+ class Fragment < ::Builder::BlankSlate
273
+ def initialize(*args)
274
+ @stream, @start, @length = args
275
+ end
276
+ def method_missing(*args, &block)
277
+ # We can't do @stream.slice!(@start, @length),
278
+ # as it would invalidate the @starts and @lengths of other Fragment instances.
279
+ @str = @stream[@start, @length].to_s
280
+ @stream[@start, @length] = [nil] * @length
281
+ def self.method_missing(*args, &block)
282
+ @str.send(*args, &block)
283
+ end
284
+ @str.send(*args, &block)
285
+ end
286
+ end
287
+
288
+ class XmlMarkup < ::Builder::XmlMarkup
289
+ attr_accessor :target, :level
290
+ end
291
+
292
+ end