Changeset 60

Show
Ignore:
Timestamp:
05/29/2006 05:19:14 (3 years ago)
Author:
tec
Message:

merging in the xhtml-careful branch

Location:
trunk
Files:
7 modified

Legend:

Unmodified
Added
Removed
  • trunk/README

    r20 r60  
    133133IDs may be added by the use of bang methods: 
    134134 
    135   div.page! 
    136     div.content! 
     135  div.page! { 
     136    div.content! { 
    137137      h1 "A Short Short Saintly Dog" 
    138     end 
    139   end 
     138    } 
     139  } 
    140140 
    141141Which results in: 
     
    156156  end 
    157157 
    158 == The <tt>capture</tt> Method 
    159  
    160 Want to catch a block of HTML as a string and play with it a bit? 
    161 Use the <tt>capture</tt> method. 
    162  
    163 Commonly used to join HTML blocks together: 
     158== Markaby prevents invalid HTML 
     159 
     160Since Markaby knows which doctype you're using, it checks a big 
     161list of valid tags and attributes before printing anything. 
     162 
     163  >> div :styl => "padding: 10px" do 
     164  >>   img :src => "samorost.jpg" 
     165  >> end 
     166  InvalidHtmlError: no such attribute `styl' 
     167 
     168== Auto-stringification 
     169 
     170If you end up using any of your Markaby "tags" as a string, the 
     171tag won't be output.  It'll be up to you to add the new string 
     172back into the HTML output. 
     173 
     174This means if you call <tt>to_s</tt>, you'll get a string back. 
     175 
     176  div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s } 
     177 
     178But, when you're adding strings in Ruby, <tt>to_s</tt> happens automatically. 
     179 
     180  div.title { "Rock Bottom" + span(" by Robert Wyatt") } 
     181 
     182Interpolation works fine. 
     183 
     184  div.title { "Rock Bottom #{span(" by Robert Wyatt")}" } 
     185 
     186And any other operation you might perform on a string. 
    164187 
    165188  div.menu! \ 
    166189    ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category| 
    167       capture { link_to category } 
     190      link_to category 
    168191    end. 
    169192    join( " | " ) 
  • trunk/Rakefile

    r29 r60  
    1313summary = "Markup as Ruby, write HTML in your native Ruby tongue" 
    1414test_file = "test/test_markaby.rb" 
    15 setup_gem("markaby", "0.4",  "Tim Fletcher and _why", summary, ['builder'], test_file) 
     15setup_gem("markaby", "0.5",  "Tim Fletcher and _why", summary, [['builder', '>=2.0.0']], test_file) 
  • trunk/lib/markaby.rb

    r30 r60  
    2121module Markaby 
    2222  VERSION = '0.4' 
     23 
     24  class InvalidXhtmlError < Exception; end 
    2325end 
    2426 
  • trunk/lib/markaby/builder.rb

    r50 r60  
    5252    # 
    5353    def initialize(assigns = {}, helpers = nil, &block) 
    54       @stream = [] 
     54      @streams = [[]] 
    5555      @assigns = assigns 
    5656      @margin = -1 
     
    5959      @output_helpers = @@default[:output_helpers] 
    6060      @output_meta_tag = @@default[:output_meta_tag] 
     61 
     62      use_tagset Markaby::XHTMLTransitionalTags 
    6163 
    6264      if helpers 
     
    7779 
    7880      @margin += 1 
    79       @builder = ::Builder::XmlMarkup.new(:indent => @indent, :margin => @margin, :target => @stream) 
     81      @builder = ::Builder::XmlMarkup.new(:indent => @indent, :margin => @margin, :target => @streams.last) 
     82 
     83      def @builder.target=(io) 
     84        @target = io 
     85      end 
    8086 
    8187      instance_eval(&block) if block 
     
    8490    # Returns a string containing the HTML stream.  Internally, the stream is stored as an Array. 
    8591    def to_s 
    86       @stream.join 
     92      @streams.last.join 
    8793    end 
    8894 
     
    105111    # 
    106112    def capture(&block) 
    107       old_stream = @stream.dup 
    108       @stream.replace [] 
    109       str = instance_eval(&block).to_s 
    110       str = to_s unless @stream.empty? 
    111       @stream.replace old_stream 
     113      @streams.push(builder.target = []) 
     114      str = instance_eval(&block) 
     115      str = to_s unless @streams.last.compact.empty? 
     116      @streams.pop 
     117      builder.target = @streams.last 
    112118      str 
    113119    end 
     
    133139    # the arguments are the same as the tags implemented via method_missing. 
    134140    def tag!(tag, *args, &block) 
     141      if @tagset 
     142        if !@tagset.has_key?(tag) 
     143          raise InvalidXhtmlError, "no element `#{tag}' for #{doctype}" 
     144        elsif args.last.respond_to?(:to_hash) 
     145          attrs = args.last.to_hash 
     146          attrs.each do |k, v| 
     147            unless k =~ /:/ or @tagset[tag].include? k.to_s.downcase.intern 
     148              raise InvalidXhtmlError, "no attribute `#{k}' on #{tag} elements" 
     149            end 
     150          end 
     151        end 
     152      end 
    135153      if block 
    136154        str = capture &block 
    137155        block = proc { text(str) } 
    138156      end 
    139       @builder.method_missing(tag, *args, &block) 
     157      fragment { @builder.method_missing(tag, *args, &block) } 
    140158    end 
    141159 
     
    146164    # variables.  Here is the order of interception: 
    147165    # 
     166    # * If +sym+ is a helper method, the helper method is called 
     167    #   and output to the stream. 
    148168    # * If +sym+ is a recognized HTML tag, the tag is output 
    149169    #   or a CssProxy is returned if no arguments are given. 
     
    152172    # * If +sym+ is also the name of an instance variable, the 
    153173    #   value of the instance variable is returned. 
    154     # * If +sym+ is a helper method, the helper method is called 
    155     #   and output to the stream. 
    156174    # * Otherwise, +sym+ and its arguments are passed to tag! 
    157175    def method_missing(sym, *args, &block) 
    158176      if @helpers.respond_to?(sym) 
    159177        r = @helpers.send(sym, *args, &block) 
    160         @builder << r if @output_helpers 
    161         r 
     178        @output_helpers ? fragment { @builder << r } : r 
    162179      elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s) 
    163180        @builder.__send__(sym, *args, &block) 
    164       elsif TAGS.include?(sym) or (FORM_TAGS.include?(sym) and args.empty?) 
    165         if args.empty? and block.nil? 
     181      elsif @tagset and @tagset_tags.include?(sym) 
     182        if @tagset_self_closing.include?(sym) and block 
     183          raise InvalidXhtmlError, "the `#{sym}' element is self-closing, please remove the block" 
     184        end 
     185        if args.empty? and block.nil? and not NO_PROXY.include?(sym) 
    166186          return CssProxy.new do |args, block| 
    167             if FORM_TAGS.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id] 
     187            if @tagset_forms.include?(sym) and args.last.respond_to?(:to_hash) and args.last[:id] 
    168188              args.last[:name] ||= args.last[:id] 
    169189            end 
     
    171191          end 
    172192        end 
     193        if not @tagset_self_closing.include?(sym) and args.first.respond_to?(:to_hash) 
     194          block ||= proc{} 
     195        end 
    173196        tag!(sym, *args, &block) 
    174       elsif SELF_CLOSING_TAGS.include?(sym) 
    175         tag!(sym, *args) 
    176       elsif value = instance_variable_get("@#{sym}") 
    177         value 
     197      elsif instance_variable_get("@#{sym}") 
     198        instance_variable_get("@#{sym}") 
     199      elsif @tagset.nil? 
     200        tag!(sym, *args, &block) 
    178201      else 
    179         tag!(sym, *args, &block) 
     202        raise NoMethodError, "no such method `#{sym}'" 
    180203      end 
    181204    end 
     
    197220    end 
    198221 
     222    def use_tagset(tagset) 
     223      return if @tagset == tagset 
     224      @tagset = tagset 
     225      @tagset_tags = tagset.keys 
     226      @tagset_forms = @tagset_tags & FORM_TAGS 
     227      @tagset_self_closing = @tagset_tags & SELF_CLOSING_TAGS 
     228    end 
     229 
    199230    # Builds an html tag.  An XML 1.0 instruction and an XHTML 1.0 Transitional doctype 
    200231    # are prepended.  Also assumes <tt>:xmlns => "http://www.w3.org/1999/xhtml", 
    201     # "xml:lang" => "en", :lang => "en"</tt>. 
     232    # :lang => "en"</tt>. 
    202233    def html(*doctype, &block) 
    203       doctype = XHTMLTransitional if doctype.empty? 
     234      if doctype.empty? 
     235        doctype = XHTMLTransitional 
     236        use_tagset Markaby::XHTMLTransitionalTags 
     237      end 
    204238      declare!(:DOCTYPE, :html, :PUBLIC, *doctype) 
    205239      tag!(:html, :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en", &block) 
     
    209243    # Builds an html tag with XHTML 1.0 Strict doctype instead. 
    210244    def xhtml_strict(&block) 
     245      use_tagset Markaby::XHTMLStrictTags 
    211246      instruct! 
    212247      html *XHTMLStrict, &block 
    213248    end 
    214249 
     250    private 
     251 
     252    def fragment 
     253      stream = @streams.last 
     254      f1 = stream.length 
     255      yield 
     256      f2 = stream.length - f1 
     257      Fragment.new(stream, f1, f2) 
     258    end 
     259 
    215260  end 
     261 
     262  class Fragment < ::Builder::BlankSlate 
     263    def initialize(s, a, b) 
     264      @s, @f1, @f2 = s, a, b 
     265    end 
     266    def method_missing(*a) 
     267      unless @str 
     268        @str = @s[@f1, @f2].to_s 
     269        @s[@f1, @f2] = [nil] * @f2 
     270        @str 
     271      end 
     272      @str.send(*a) 
     273    end 
     274  end 
     275 
    216276end 
  • trunk/lib/markaby/tags.rb

    r27 r60  
    11module Markaby 
    22 
    3   TAGS = [ 
    4     :a, :abbr, :acronym, :span, :b, :caption, :del, :cite, :code, :col, 
    5     :colgroup, :dd, :dfn, :dt, :em, :fieldset, :i, :img, :ins, :kbd, :p, 
    6     :label, :legend, :li, :optgroup, :option, :select, :small, :span, :strong, 
    7     :sub, :sup,  :tbody, :td, :textarea, :thead, :title, :th, :tr, :tfoot, 
    8     :tt, :address, :blockquote, :body, :div, :dl, :form, :h1, :h2, :h3, :head, 
    9     :noscript, :object, :ol, :pre, :q, :samp, :script, :style, :table, :ul 
    10   ] 
     3  # Common sets of attributes. 
     4  AttrCustom = [:id, :class, :style] 
     5  AttrEvents = [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove,  
     6      :onmouseout, :onkeypress, :onkeydown, :onkeyup] 
     7  AttrAnno = [:title, :lang, :dir] 
     8  Attrs = AttrCustom + AttrEvents + AttrAnno 
     9 
     10  # Very basic rules from the XHTML 1.0 Strict DTD. 
     11  XHTMLStrictTags = { 
     12    :pre => AttrCustom + AttrAnno + [:space],  
     13    :em => Attrs, 
     14    :code => Attrs, 
     15    :h2 => Attrs, 
     16    :h3 => Attrs, 
     17    :h1 => Attrs, 
     18    :h6 => Attrs, 
     19    :dl => Attrs, 
     20    :h4 => Attrs, 
     21    :h5 => Attrs, 
     22    :area => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :shape, :coords, :href, :nohref, :alt],  
     23    :meta => [:lang, :dir, :id, :name, :content, :scheme, "http-equiv".intern],  
     24    :table => [],  
     25    :dfn => Attrs, 
     26    :label => Attrs + [:for, :accesskey, :onfocus, :onblur],  
     27    :select => Attrs + [:name, :size, :multiple, :disabled, :tabindex, :onfocus, :onblur, :onchange],  
     28    :noscript => Attrs, 
     29    :style => [:lang, :dir, :id, :type, :media, :title, :space],  
     30    :strong => Attrs, 
     31    :span => Attrs, 
     32    :sub => Attrs, 
     33    :img => Attrs + [:src, :alt, :longdesc, :height, :width, :usemap, :ismap],  
     34    :title => [:lang, :dir, :id],  
     35    :bdo => AttrCustom + [:title] + AttrEvents + [:lang, :dir],  
     36    :tr => [],  
     37    :tbody => [],  
     38    :param => [:id, :name, :value, :valuetype, :type],  
     39    :li => Attrs, 
     40    :acronym => Attrs, 
     41    :html => [:lang, :dir, :id, :xmlns],  
     42    :caption => [],  
     43    :tfoot => [],  
     44    :th => [],  
     45    :sup => Attrs, 
     46    :var => Attrs, 
     47    :input => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :type, :name, :value, :checked, :disabled, :readonly, :size, :maxlength, :src, :alt, :usemap, :onselect, :onchange, :accept],  
     48    :td => Attrs + [:summary, :width, :border, :frame, :rules, :cellspacing, :cellpadding, :span, :align, :char, :charoff, :valign, :abbr, :axis, :headers, :scope, :rowspan, :colspan],  
     49    :samp => Attrs, 
     50    :cite => Attrs, 
     51    :thead => [],  
     52    :body => Attrs + [:onload, :onunload],  
     53    :map => AttrCustom + [:lang, :dir, ] + AttrEvents + [:title, :name],  
     54    :head => [:lang, :dir, :id, :profile],  
     55    :blockquote => Attrs + [:cite],  
     56    :fieldset => Attrs, 
     57    :option => Attrs + [:selected, :disabled, :label, :value],  
     58    :form => Attrs + [:action, :method, :enctype, :onsubmit, :onreset, :accept, 'accept-charset'],  
     59    :hr => Attrs, 
     60    :big => Attrs, 
     61    :dd => Attrs, 
     62    :object => Attrs + [:declare, :classid, :codebase, :data, :type, :codetype, :archive, :standby, :height, :width, :usemap, :name, :tabindex],  
     63    :base => [:href, :id],  
     64    :link => Attrs + [:charset, :href, :hreflang, :type, :rel, :rev, :media],  
     65    :kbd => Attrs, 
     66    :br => AttrCustom + [:title],  
     67    :address => Attrs, 
     68    :optgroup => Attrs + [:disabled, :label],  
     69    :dt => Attrs, 
     70    :ins => Attrs + [:cite, :datetime],  
     71    :b => Attrs, 
     72    :legend => Attrs + [:accesskey],  
     73    :abbr => Attrs, 
     74    :a => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :charset, :type, :name, :href, :hreflang, :rel, :rev, :shape, :coords],  
     75    :ol => Attrs, 
     76    :textarea => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :name, :rows, :cols, :disabled, :readonly, :onselect, :onchange],  
     77    :colgroup => [],  
     78    :i => Attrs, 
     79    :button => Attrs + [:accesskey, :tabindex, :onfocus, :onblur, :name, :value, :type, :disabled],  
     80    :script => [:id, :charset, :type, :src, :defer, :space],  
     81    :col => [],  
     82    :q => Attrs + [:cite],  
     83    :p => Attrs, 
     84    :del => Attrs + [:cite, :datetime],  
     85    :small => Attrs, 
     86    :div => Attrs, 
     87    :tt => Attrs, 
     88    :ul => Attrs   
     89  } 
     90 
     91  # Additional tags found in XHTML 1.0 Transitional 
     92  XHTMLTransitionalTags = XHTMLStrictTags.merge \ 
     93    :strike => Attrs, 
     94    :center => Attrs, 
     95    :dir => Attrs + [:compact],  
     96    :noframes => Attrs, 
     97    :basefont => [:id, :size, :color, :face],  
     98    :u => Attrs, 
     99    :menu => Attrs + [:compact],  
     100    :iframe => AttrCustom + [:title, :longdesc, :name, :src, :frameborder, :marginwidth, :marginheight, :scrolling, :align, :height, :width],  
     101    :font => AttrCustom + AttrAnno + [:size, :color, :face],  
     102    :s => Attrs, 
     103    :applet => AttrCustom + [:title, :codebase, :archive, :code, :object, :alt, :name, :width, :height, :align, :hspace, :vspace],  
     104    :isindex => AttrCustom + AttrAnno + [:prompt] 
     105 
     106  # Additional attributes found in XHTML 1.0 Transitional 
     107  { :script => [:language], 
     108    :a => [:target], 
     109    :td => [:bgcolor, :nowrap, :height], 
     110    :p => [:align], 
     111    :h5 => [:align], 
     112    :h3 => [:align], 
     113    :li => [:type, :value], 
     114    :div => [:align], 
     115    :pre => AttrEvents + [:width], 
     116    :body => [:background, :bgcolor, :text, :link, :vlink, :alink], 
     117    :ol => [:type, :compact, :start], 
     118    :h4 => [:align], 
     119    :h2 => [:align], 
     120    :object => [:align, :border, :hspace, :vspace], 
     121    :img => [:name, :align, :border, :hspace, :vspace], 
     122    :link => [:target], 
     123    :legend => [:align], 
     124    :dl => [:compact], 
     125    :input => [:align], 
     126    :h6 => [:align], 
     127    :hr => [:align, :noshade, :size, :width], 
     128    :base => [:target], 
     129    :ul => [:type, :compact], 
     130    :br => [:clear], 
     131    :form => [:name, :target], 
     132    :area => [:target], 
     133    :h1 => [:align] 
     134  }.each do |k, v| 
     135      XHTMLTransitionalTags[k] += v 
     136  end 
    11137 
    12138  FORM_TAGS = [ :form, :input, :select, :textarea ] 
    13139  SELF_CLOSING_TAGS = [ :hr, :br, :link, :meta, :input ] 
     140  NO_PROXY = [ :hr, :br ] 
    14141 
    15142end 
  • trunk/test/test_markaby.rb

    r49 r60  
    1313 
    1414class MarkabyTest < Test::Unit::TestCase 
    15    
     15 
    1616  def mab(*args, &block) 
    1717    Markaby::Builder.new(*args, &block).to_s 
     18  end 
     19 
     20  def assert_exception(exclass, exmsg, *mab_args, &block) 
     21    begin 
     22      mab(*mab_args, &block) 
     23    rescue Exception => e 
     24      assert_equal exclass, e.class 
     25      assert_equal exmsg, e.message 
     26    end 
    1827  end 
    1928 
     
    6574  end 
    6675 
     76  def test_fragments 
     77    assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <small>Miniature</small>\n and <strong>Large</strong>\n</h2>\n<h3>Donkeys</h3>\n<h4>\nParakeet <b>\n<i>Innocent IV</i>\n</b>\n in Classic Chartreuse</h4>\n</div>\n},  
     78        mab { div { h1 "Monkeys"; h2 { "Giraffes #{small 'Miniature' } and #{strong 'Large'}" }; h3 "Donkeys"; h4 { "Parakeet #{b { i 'Innocent IV' }} in Classic Chartreuse" } } } 
     79    assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <strong>Miniature</strong>\n</h2>\n<h3>Donkeys</h3>\n</div>\n},  
     80        mab { div { h1 "Monkeys"; h2 { "Giraffes #{strong 'Miniature' }" }; h3 "Donkeys" } } 
     81    assert_equal %{<div>\n<h1>Monkeys</h1>\n<h2>\nGiraffes <small>Miniature</small>\n and <strong>Large</strong>\n</h2>\n<h3>Donkeys</h3>\n<h4>\nParakeet <strong>Large</strong>\n as well...</h4>\n</div>\n},  
     82        mab { div { @a = small 'Miniature'; @b = strong 'Large'; h1 "Monkeys"; h2 { "Giraffes #{@a} and #{@b}" }; h3 "Donkeys"; h4 { "Parakeet #{@b} as well..." } } } 
     83  end 
     84 
     85  def test_invalid_xhtml 
     86    assert_exception(NoMethodError, "no such method `dav'") { dav {} } 
     87    assert_exception(Markaby::InvalidXhtmlError, "no attribute `styl' on div elements") { div(:styl => 'ok') {} } 
     88    assert_exception(Markaby::InvalidXhtmlError, "no attribute `class' on tbody elements") { tbody.okay {} } 
     89  end 
     90 
     91  def test_full_doc_transitional 
     92    doc = %{<?xml version=\"1.0\" encoding=\"UTF-8\"?> 
     93<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"DTD/xhtml1-transitional.dtd\"> 
     94<html xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\"> 
     95<head>\n<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\n<title>OKay</title>\n</head>\n</html>\n} 
     96    assert_equal doc, mab { instruct!; html { head { title 'OKay' } } } 
     97  end 
     98 
     99  def test_full_doc_transitional 
     100    doc = %{<?xml version=\"1.0\" encoding=\"UTF-8\"?> 
     101<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\"> 
     102<html lang=\"en\" xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> 
     103<head>\n<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\n<title>OKay</title>\n</head>\n</html>\n} 
     104    assert_equal doc, mab { xhtml_strict { head { title 'OKay' } } } 
     105  end 
     106 
    67107end 
  • trunk/tools/rakehelp.rb

    r22 r60  
    8080        s.extra_rdoc_files = [ "README" ] 
    8181        dependencies.each do |dep| 
    82             s.add_dependency(dep) 
     82            s.add_dependency(*dep) 
    8383        end 
    8484        s.files = %w(README Rakefile setup.rb) +