0
+ # The Markaby::Builder class is the central gear in the system. When using
0
+ # from Ruby code, this is the only class you need to instantiate directly.
0
+ # mab = Markaby::Builder.new
0
+ # head { title "Boats.com" }
0
+ # h1 "Boats.com has great deals"
0
+ # li "$49 for a canoe"
0
+ # li "$29 for a huge boot that floats and can fit 5 people"
0
+ :output_helpers => true,
0
+ :output_xml_instruction => true,
0
+ :output_meta_tag => true,
0
+ :auto_validation => true,
0
+ :tagset => Markaby::XHTMLTransitional,
0
+ :xmlns => 'http://www.w3.org/1999/xhtml', :'xml:lang' => 'en', :lang => 'en'
0
+ def self.set(option, value)
0
+ @@default[option] = value
0
+ def self.ignored_helpers
0
+ @@ignored_helpers ||= []
0
+ def self.ignore_helpers(*helpers)
0
+ ignored_helpers.concat helpers
0
+ attr_accessor :output_helpers, :tagset
0
+ # Create a Markaby builder object. Pass in a hash of variable assignments to
0
+ # +assigns+ which will be available as instance variables inside tag construction
0
+ # blocks. If an object is passed in to +helpers+, its methods will be available
0
+ # from those same blocks.
0
+ # Pass in a +block+ to new and the block will be evaluated.
0
+ # mab = Markaby::Builder.new {
0
+ def initialize(assigns = {}, helpers = nil, &block)
0
+ @assigns = assigns.dup
0
+ @@default.each do |k, v|
0
+ instance_variable_set("@#{k}", @assigns.delete(k) || v)
0
+ @assigns.each do |k, v|
0
+ instance_variable_set("@#{k}", v)
0
+ @builder = XmlMarkup.new(:indent => @indent, :target => @streams.last)
0
+ text(capture(&block)) if block
0
+ # Returns a string containing the HTML stream. Internally, the stream is stored as an Array.
0
+ # Write a +string+ to the HTML stream without escaping it.
0
+ @builder << string.to_s
0
+ alias_method :<<, :text
0
+ alias_method :concat, :text
0
+ # Captures the HTML code built inside the +block+. This is done by creating a new
0
+ # stream for the builder object, running the block and passing back its stream as a string.
0
+ # >> Markaby::Builder.new.capture { h1 "TEST"; h2 "CAPTURE ME" }
0
+ # => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
0
+ @streams.push(@builder.target = [])
0
+ str = instance_eval(&block)
0
+ str = @streams.last.join if @streams.last.any?
0
+ @builder.target = @streams.last
0
+ # Create a tag named +tag+. Other than the first argument which is the tag name,
0
+ # the arguments are the same as the tags implemented via method_missing.
0
+ def tag!(tag, *args, &block)
0
+ if @auto_validation and @tagset
0
+ if !@tagset.tagset.has_key?(tag)
0
+ raise InvalidXhtmlError, "no element `#{tag}' for #{tagset.doctype}"
0
+ elsif args.last.respond_to?(:to_hash)
0
+ attrs = args.last.to_hash
0
+ if @tagset.forms.include?(tag) and attrs[:id]
0
+ attrs[:name] ||= attrs[:id]
0
+ atname = k.to_s.downcase.intern
0
+ unless k =~ /:/ or @tagset.tagset[tag].include? atname
0
+ raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements"
0
+ if @elements.has_key? ele_id
0
+ raise InvalidXhtmlError, "id `#{ele_id}' already used (id's must be unique)."
0
+ block = proc { text(str) }
0
+ f = fragment { @builder.method_missing(tag, *args, &block) }
0
+ @elements[ele_id] = f if ele_id
0
+ # This method is used to intercept calls to helper methods and instance
0
+ # variables. Here is the order of interception:
0
+ # * If +sym+ is a helper method, the helper method is called
0
+ # and output to the stream.
0
+ # * If +sym+ is a Builder::XmlMarkup method, it is passed on to the builder object.
0
+ # * If +sym+ is also the name of an instance variable, the
0
+ # value of the instance variable is returned.
0
+ # * If +sym+ has come this far and no +tagset+ is found, +sym+ and its arguments are passed to tag!
0
+ # * If a tagset is found, though, +NoMethodError+ is raised.
0
+ # method_missing used to be the lynchpin in Markaby, but it's no longer used to handle
0
+ # HTML tags. See html_tag for that.
0
+ def method_missing(sym, *args, &block)
0
+ if @helpers.respond_to?(sym, true) && !self.class.ignored_helpers.include?(sym)
0
+ r = @helpers.send(sym, *args, &block)
0
+ if @output_helpers and r.respond_to? :to_str
0
+ fragment { @builder << r }
0
+ elsif @assigns.has_key?(sym)
0
+ elsif @assigns.has_key?(stringy_key = sym.to_s)
0
+ # Rails' ActionView assigns hash has string keys for
0
+ # instance variables that are defined in the controller.
0
+ elsif instance_variables.include?(ivar = "@#{sym}")
0
+ instance_variable_get(ivar)
0
+ elsif !@helpers.nil? && @helpers.instance_variables.include?(ivar)
0
+ @helpers.instance_variable_get(ivar)
0
+ elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s)
0
+ @builder.__send__(sym, *args, &block)
0
+ tag!(sym, *args, &block)
0
+ raise NoMethodError, "no such method `#{sym}'"
0
+ # Every HTML tag method goes through an html_tag call. So, calling <tt>div</tt> is equivalent
0
+ # to calling <tt>html_tag(:div)</tt>. All HTML tags in Markaby's list are given generated wrappers
0
+ # If the @auto_validation setting is on, this method will check for many common mistakes which
0
+ # could lead to invalid XHTML.
0
+ def html_tag(sym, *args, &block)
0
+ if @auto_validation and @tagset.self_closing.include?(sym) and block
0
+ raise InvalidXhtmlError, "the `#{sym}' element is self-closing, please remove the block"
0
+ elsif args.empty? and block.nil?
0
+ CssProxy.new(self, @streams.last, sym)
0
+ tag!(sym, *args, &block)
0
+ XHTMLTransitional.tags.each do |k|
0
+ def #{k}(*args, &block)
0
+ html_tag(#{k.inspect}, *args, &block)
0
+ # Builds a head tag. Adds a <tt>meta</tt> tag inside with Content-Type
0
+ # set to <tt>text/html; charset=utf-8</tt>.
0
+ def head(*args, &block)
0
+ tag!(:meta, "http-equiv" => "Content-Type", "content" => "text/html; charset=utf-8") if @output_meta_tag
0
+ # Builds an html tag. An XML 1.0 instruction and an XHTML 1.0 Transitional doctype
0
+ # are prepended. Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml",
0
+ def xhtml_transitional(attrs = {}, &block)
0
+ self.tagset = Markaby::XHTMLTransitional
0
+ xhtml_html(attrs, &block)
0
+ # Builds an html tag with XHTML 1.0 Strict doctype instead.
0
+ def xhtml_strict(attrs = {}, &block)
0
+ self.tagset = Markaby::XHTMLStrict
0
+ xhtml_html(attrs, &block)
0
+ def xhtml_html(attrs = {}, &block)
0
+ instruct! if @output_xml_instruction
0
+ declare!(:DOCTYPE, :html, :PUBLIC, *tagset.doctype)
0
+ tag!(:html, @root_attributes.merge(attrs), &block)
0
+ stream = @streams.last
0
+ length = stream.length - start
0
+ Fragment.new(stream, start, length)
0
+ # Every tag method in Markaby returns a Fragment. If any method gets called on the Fragment,
0
+ # the tag is removed from the Markaby stream and given back as a string. Usually the fragment
0
+ # is never used, though, and the stream stays intact.
0
+ # For a more practical explanation, check out the README.
0
+ class Fragment < ::Builder::BlankSlate
0
+ @stream, @start, @length = args
0
+ def method_missing(*args, &block)
0
+ # We can't do @stream.slice!(@start, @length),
0
+ # as it would invalidate the @starts and @lengths of other Fragment instances.
0
+ @str = @stream[@start, @length].to_s
0
+ @stream[@start, @length] = [nil] * @length
0
+ def self.method_missing(*args, &block)
0
+ @str.send(*args, &block)
0
+ @str.send(*args, &block)
0
+ class XmlMarkup < ::Builder::XmlMarkup
0
+ attr_accessor :target, :level