File: lib/redcloth3.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: RedCloth3#167
inherits from
  String ( Builtin-Module )
has properties
constant: VERSION #169
constant: DEFAULT_RULES #170
attribute: filter_html [RW] #185
attribute: filter_styles [RW] #185
attribute: hard_breaks [RW] #194
attribute: lite_mode [RW] #206
attribute: no_span_caps [RW] #216
attribute: rules [RW] #245
method: initialize / 2 #254
method: to_html / 1 #266
constant: TEXTILE_TAGS #326
constant: A_HLGN #341
constant: A_VLGN #342
constant: C_CLAS #343
constant: C_LNGE #344
constant: C_STYL #345
constant: S_CSPN #346
constant: S_RSPN #347
constant: A #348
constant: S #349
constant: C #350
constant: PUNCT #352
constant: PUNCT_NOQ #353
constant: PUNCT_Q #354
constant: HYPERLINK #355
constant: SIMPLE_HTML_TAGS #358
constant: QTAGS #364
constant: QTAGS_JOIN #376
constant: GLYPHS #403
constant: H_ALGN_VALS #426
constant: V_ALGN_VALS #433
method: htmlesc / 2 #442
method: pgl / 1 #454
method: pba / 2 #465
constant: STYLES_RE #508
method: sanitize_styles / 1 #510
constant: TABLE_RE #518
method: block_textile_table / 1 #521
constant: LISTS_RE #550
constant: LISTS_CONTENT_RE #551
method: block_textile_lists / 1 #554
constant: QUOTES_RE #597
constant: QUOTES_CONTENT_RE #598
method: block_textile_quotes / 1 #600
constant: CODE_RE #620
method: inline_textile_code / 1 #627
method: lT / 1 #635
method: hard_break / 1 #639
constant: BLOCKS_GROUP_RE #643
method: blocks / 2 #645
method: textile_bq / 4 #689
method: textile_p / 4 #696
alias: textile_h1 textile_p #701
alias: textile_h2 textile_p #702
alias: textile_h3 textile_p #703
alias: textile_h4 textile_p #704
alias: textile_h5 textile_p #705
alias: textile_h6 textile_p #706
method: textile_fn_ / 5 #708
constant: BLOCK_RE #715
method: block_textile_prefix / 1 #717
constant: SETEXT_RE #733
method: block_markdown_setext / 1 #734
constant: ATX_RE #743
method: block_markdown_atx / 1 #749
constant: MARKDOWN_BQ_RE #758
method: block_markdown_bq / 1 #760
constant: MARKDOWN_RULE_RE #770
method: block_markdown_rule / 1 #774
method: block_markdown_lists / 1 #781
method: inline_textile_span / 1 #784
constant: LINK_RE #808
method: inline_textile_link / 1 #827
constant: MARKDOWN_REFLINK_RE #854
method: inline_markdown_reflink / 1 #861
constant: MARKDOWN_LINK_RE #879
method: inline_markdown_link / 1 #893
constant: TEXTILE_REFS_RE #905
constant: MARKDOWN_REFS_RE #906
method: refs / 1 #908
method: refs_textile / 1 #914
method: refs_markdown / 1 #922
method: check_refs / 1 #931
constant: IMAGE_RE #936
method: inline_textile_image / 1 #949
method: shelve / 1 #983
method: retrieve / 1 #988
method: incoming_entities / 1 #994
method: no_textile / 1 #1002
method: clean_white_space / 1 #1009
method: flush_left / 1 #1023
method: footnote_ref / 1 #1035
constant: OFFTAGS #1040
constant: OFFTAG_MATCH #1041
constant: OFFTAG_OPEN #1042
constant: OFFTAG_CLOSE #1043
constant: HASTAG_MATCH #1044
constant: ALLTAG_MATCH #1045
method: glyphs_textile / 2 #1047
method: rip_offtags / 3 #1074
method: smooth_offtags / 1 #1115
method: inline / 1 #1122
method: h_align / 1 #1130
method: v_align / 1 #1134
method: textile_popup_help / 3 #1138
constant: BASIC_TAGS #1143
method: clean_html / 2 #1177
constant: ALLOWED_TAGS #1202
method: escape_html_tags / 1 #1204

Class Hierarchy

Object ( Builtin-Module )
String ( Builtin-Module )
  RedCloth3    #167

Code

   1  #                                vim:ts=4:sw=4:
   2  # = RedCloth - Textile and Markdown Hybrid for Ruby
   3  #
   4  # Homepage::  http://whytheluckystiff.net/ruby/redcloth/
   5  # Author::    why the lucky stiff (http://whytheluckystiff.net/)
   6  # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
   7  # License::   BSD
   8  #
   9  # (see http://hobix.com/textile/ for a Textile Reference.)
  10  #
  11  # Based on (and also inspired by) both:
  12  #
  13  # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
  14  # Textism for PHP: http://www.textism.com/tools/textile/
  15  #
  16  #
  17 
  18  # = RedCloth
  19  #
  20  # RedCloth is a Ruby library for converting Textile and/or Markdown
  21  # into HTML.  You can use either format, intermingled or separately.
  22  # You can also extend RedCloth to honor your own custom text stylings.
  23  #
  24  # RedCloth users are encouraged to use Textile if they are generating
  25  # HTML and to use Markdown if others will be viewing the plain text.
  26  #
  27  # == What is Textile?
  28  #
  29  # Textile is a simple formatting style for text
  30  # documents, loosely based on some HTML conventions.
  31  #
  32  # == Sample Textile Text
  33  #
  34  #  h2. This is a title
  35  #
  36  #  h3. This is a subhead
  37  #
  38  #  This is a bit of paragraph.
  39  #
  40  #  bq. This is a blockquote.
  41  #
  42  # = Writing Textile
  43  #
  44  # A Textile document consists of paragraphs.  Paragraphs
  45  # can be specially formatted by adding a small instruction
  46  # to the beginning of the paragraph.
  47  #
  48  #  h[n].   Header of size [n].
  49  #  bq.     Blockquote.
  50  #  #       Numeric list.
  51  #  *       Bulleted list.
  52  #
  53  # == Quick Phrase Modifiers
  54  #
  55  # Quick phrase modifiers are also included, to allow formatting
  56  # of small portions of text within a paragraph.
  57  #
  58  #  \_emphasis\_
  59  #  \_\_italicized\_\_
  60  #  \*strong\*
  61  #  \*\*bold\*\*
  62  #  ??citation??
  63  #  -deleted text-
  64  #  +inserted text+
  65  #  ^superscript^
  66  #  ~subscript~
  67  #  @code@
  68  #  %(classname)span%
  69  #
  70  #  ==notextile== (leave text alone)
  71  #
  72  # == Links
  73  #
  74  # To make a hypertext link, put the link text in "quotation 
  75  # marks" followed immediately by a colon and the URL of the link.
  76  # 
  77  # Optional: text in (parentheses) following the link text, 
  78  # but before the closing quotation mark, will become a Title 
  79  # attribute for the link, visible as a tool tip when a cursor is above it.
  80  # 
  81  # Example:
  82  #
  83  #  "This is a link (This is a title) ":http://www.textism.com
  84  # 
  85  # Will become:
  86  # 
  87  #  <a href="http://www.textism.com" title="This is a title">This is a link</a>
  88  #
  89  # == Images
  90  #
  91  # To insert an image, put the URL for the image inside exclamation marks.
  92  #
  93  # Optional: text that immediately follows the URL in (parentheses) will 
  94  # be used as the Alt text for the image. Images on the web should always 
  95  # have descriptive Alt text for the benefit of readers using non-graphical 
  96  # browsers.
  97  #
  98  # Optional: place a colon followed by a URL immediately after the 
  99  # closing ! to make the image into a link.
 100  # 
 101  # Example:
 102  #
 103  #  !http://www.textism.com/common/textist.gif(Textist)!
 104  #
 105  # Will become:
 106  #
 107  #  <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
 108  #
 109  # With a link:
 110  #
 111  #  !/common/textist.gif(Textist)!:http://textism.com
 112  #
 113  # Will become:
 114  #
 115  #  <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
 116  #
 117  # == Defining Acronyms
 118  #
 119  # HTML allows authors to define acronyms via the tag. The definition appears as a 
 120  # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, 
 121  # this should be used at least once for each acronym in documents where they appear.
 122  #
 123  # To quickly define an acronym in Textile, place the full text in (parentheses) 
 124  # immediately following the acronym.
 125  # 
 126  # Example:
 127  #
 128  #  ACLU(American Civil Liberties Union)
 129  #
 130  # Will become:
 131  #
 132  #  <acronym title="American Civil Liberties Union">ACLU</acronym>
 133  #
 134  # == Adding Tables
 135  #
 136  # In Textile, simple tables can be added by seperating each column by
 137  # a pipe.
 138  #
 139  #     |a|simple|table|row|
 140  #     |And|Another|table|row|
 141  #
 142  # Attributes are defined by style definitions in parentheses.
 143  #
 144  #     table(border:1px solid black).
 145  #     (background:#ddd;color:red). |{}| | | |
 146  #
 147  # == Using RedCloth
 148  # 
 149  # RedCloth is simply an extension of the String class, which can handle
 150  # Textile formatting.  Use it like a String and output HTML with its
 151  # RedCloth#to_html method.
 152  #
 153  #  doc = RedCloth.new "
 154  #
 155  #  h2. Test document
 156  #
 157  #  Just a simple test."
 158  #
 159  #  puts doc.to_html
 160  #
 161  # By default, RedCloth uses both Textile and Markdown formatting, with
 162  # Textile formatting taking precedence.  If you want to turn off Markdown
 163  # formatting, to boost speed and limit the processor:
 164  #
 165  #  class RedCloth::Textile.new( str )
 166 
 167  class RedCloth3 < String
 168 
 169      VERSION = '3.0.4'
 170      DEFAULT_RULES = [:textile, :markdown]
 171 
 172      #
 173      # Two accessor for setting security restrictions.
 174      #
 175      # This is a nice thing if you're using RedCloth for
 176      # formatting in public places (e.g. Wikis) where you
 177      # don't want users to abuse HTML for bad things.
 178      #
 179      # If +:filter_html+ is set, HTML which wasn't
 180      # created by the Textile processor will be escaped.
 181      #
 182      # If +:filter_styles+ is set, it will also disable
 183      # the style markup specifier. ('{color: red}')
 184      #
 185      attr_accessor :filter_html, :filter_styles
 186 
 187      #
 188      # Accessor for toggling hard breaks.
 189      #
 190      # If +:hard_breaks+ is set, single newlines will
 191      # be converted to HTML break tags.  This is the
 192      # default behavior for traditional RedCloth.
 193      #
 194      attr_accessor :hard_breaks
 195 
 196      # Accessor for toggling lite mode.
 197      #
 198      # In lite mode, block-level rules are ignored.  This means
 199      # that tables, paragraphs, lists, and such aren't available.
 200      # Only the inline markup for bold, italics, entities and so on.
 201      #
 202      #   r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
 203      #   r.to_html
 204      #   #=> "And then? She <strong>fell</strong>!"
 205      #
 206      attr_accessor :lite_mode
 207 
 208      #
 209      # Accessor for toggling span caps.
 210      #
 211      # Textile places `span' tags around capitalized
 212      # words by default, but this wreaks havoc on Wikis.
 213      # If +:no_span_caps+ is set, this will be
 214      # suppressed.
 215      #
 216      attr_accessor :no_span_caps
 217 
 218      #
 219      # Establishes the markup predence.  Available rules include:
 220      #
 221      # == Textile Rules
 222      #
 223      # The following textile rules can be set individually.  Or add the complete
 224      # set of rules with the single :textile rule, which supplies the rule set in
 225      # the following precedence:
 226      #
 227      # refs_textile::          Textile references (i.e. [hobix]http://hobix.com/)
 228      # block_textile_table::   Textile table block structures
 229      # block_textile_lists::   Textile list structures
 230      # block_textile_prefix::  Textile blocks with prefixes (i.e. bq., h2., etc.)
 231      # inline_textile_image::  Textile inline images
 232      # inline_textile_link::   Textile inline links
 233      # inline_textile_span::   Textile inline spans
 234      # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
 235      #
 236      # == Markdown
 237      #
 238      # refs_markdown::         Markdown references (for example: [hobix]: http://hobix.com/)
 239      # block_markdown_setext:: Markdown setext headers
 240      # block_markdown_atx::    Markdown atx headers
 241      # block_markdown_rule::   Markdown horizontal rules
 242      # block_markdown_bq::     Markdown blockquotes
 243      # block_markdown_lists::  Markdown lists
 244      # inline_markdown_link::  Markdown links
 245      attr_accessor :rules
 246 
 247      # Returns a new RedCloth object, based on _string_ and
 248      # enforcing all the included _restrictions_.
 249      #
 250      #   r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
 251      #   r.to_html
 252      #     #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
 253      #
 254      def initialize( string, restrictions = [] )
 255          restrictions.each { |r| method( "#{ r }=" ).call( true ) }
 256          super( string )
 257      end
 258 
 259      #
 260      # Generates HTML from the Textile contents.
 261      #
 262      #   r = RedCloth.new( "And then? She *fell*!" )
 263      #   r.to_html( true )
 264      #     #=>"And then? She <strong>fell</strong>!"
 265      #
 266      def to_html( *rules )
 267          rules = DEFAULT_RULES if rules.empty?
 268          # make our working copy
 269          text = self.dup
 270          
 271          @urlrefs = {}
 272          @shelf = []
 273          textile_rules = [:block_textile_table, :block_textile_lists,
 274                           :block_textile_prefix, :inline_textile_image, :inline_textile_link,
 275                           :inline_textile_code, :inline_textile_span, :glyphs_textile]
 276          markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
 277                            :block_markdown_bq, :block_markdown_lists, 
 278                            :inline_markdown_reflink, :inline_markdown_link]
 279          @rules = rules.collect do |rule|
 280              case rule
 281              when :markdown
 282                  markdown_rules
 283              when :textile
 284                  textile_rules
 285              else
 286                  rule
 287              end
 288          end.flatten
 289 
 290          # standard clean up
 291          incoming_entities text 
 292          clean_white_space text 
 293 
 294          # start processor
 295          @pre_list = []
 296          rip_offtags text
 297          no_textile text
 298          escape_html_tags text
 299          # need to do this before #hard_break and #blocks
 300          block_textile_quotes text unless @lite_mode
 301          hard_break text 
 302          unless @lite_mode
 303              refs text
 304              blocks text
 305          end
 306          inline text
 307          smooth_offtags text
 308 
 309          retrieve text
 310 
 311          text.gsub!( /<\/?notextile>/, '' )
 312          text.gsub!( /x%x%/, '&#38;' )
 313          clean_html text if filter_html
 314          text.strip!
 315          text
 316 
 317      end
 318 
 319      #######
 320      private
 321      #######
 322      #
 323      # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
 324      # (from PyTextile)
 325      #
 326      TEXTILE_TAGS =
 327 
 328          [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], 
 329           [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], 
 330           [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], 
 331           [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], 
 332           [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
 333 
 334          collect! do |a, b|
 335              [a.chr, ( b.zero? and "" or "&#{ b };" )]
 336          end
 337 
 338      #
 339      # Regular expressions to convert to HTML.
 340      #
 341      A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
 342      A_VLGN = /[\-^~]/
 343      C_CLAS = '(?:\([^")]+\))'
 344      C_LNGE = '(?:\[[^"\[\]]+\])'
 345      C_STYL = '(?:\{[^"}]+\})'
 346      S_CSPN = '(?:\\\\\d+)'
 347      S_RSPN = '(?:/\d+)'
 348      A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
 349      S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
 350      C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
 351      # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
 352      PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
 353      PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
 354      PUNCT_Q = Regexp::quote( '*-_+^~%' )
 355      HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
 356 
 357      # Text markup tags, don't conflict with block tags
 358      SIMPLE_HTML_TAGS = [
 359          'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code', 
 360          'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
 361          'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
 362      ]
 363 
 364      QTAGS = [
 365          ['**', 'b', :limit],
 366          ['*', 'strong', :limit],
 367          ['??', 'cite', :limit],
 368          ['-', 'del', :limit],
 369          ['__', 'i', :limit],
 370          ['_', 'em', :limit],
 371          ['%', 'span', :limit],
 372          ['+', 'ins', :limit],
 373          ['^', 'sup', :limit],
 374          ['~', 'sub', :limit]
 375      ] 
 376      QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|')
 377      
 378      QTAGS.collect! do |rc, ht, rtype|
 379          rcq = Regexp::quote rc
 380          re =
 381              case rtype
 382              when :limit
 383                  /(^|[>\s\(])          # sta
 384                  (?!\-\-)
 385                  (#{QTAGS_JOIN}|)      # oqs
 386                  (#{rcq})              # qtag
 387                  (\w|[^\s].*?[^\s])    # content
 388                  (?!\-\-)
 389                  #{rcq}
 390                  (#{QTAGS_JOIN}|)      # oqa
 391                  (?=[[:punct:]]|<|\s|\)|$)/x
 392              else
 393                  /(#{rcq})
 394                  (#{C})
 395                  (?::(\S+))?
 396                  (\w|[^\s\-].*?[^\s\-])
 397                  #{rcq}/xm 
 398              end
 399          [rc, ht, re, rtype]
 400      end
 401 
 402      # Elements to handle
 403      GLYPHS = [
 404      #   [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
 405      #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
 406      #   [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
 407      #   [ /\'/, '&#8216;' ], # single opening
 408      #   [ /</, '&lt;' ], # less-than
 409      #   [ />/, '&gt;' ], # greater-than
 410      #   [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
 411      #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
 412      #   [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
 413      #   [ /"/, '&#8220;' ], # double opening
 414      #   [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
 415      #   [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
 416      #   [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
 417      #   [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
 418      #   [ /\s->\s/, ' &rarr; ' ], # right arrow
 419      #   [ /\s-\s/, ' &#8211; ' ], # en dash
 420      #   [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
 421      #   [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
 422      #   [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
 423      #   [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
 424      ]
 425 
 426      H_ALGN_VALS = {
 427          '<' => 'left',
 428          '=' => 'center',
 429          '>' => 'right',
 430          '<>' => 'justify'
 431      }
 432 
 433      V_ALGN_VALS = {
 434          '^' => 'top',
 435          '-' => 'middle',
 436          '~' => 'bottom'
 437      }
 438 
 439      #
 440      # Flexible HTML escaping
 441      #
 442      def htmlesc( str, mode=:Quotes )
 443        if str
 444          str.gsub!( '&', '&amp;' )
 445          str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
 446          str.gsub!( "'", '&#039;' ) if mode == :Quotes
 447          str.gsub!( '<', '&lt;')
 448          str.gsub!( '>', '&gt;')
 449        end
 450        str
 451      end
 452 
 453      # Search and replace for Textile glyphs (quotes, dashes, other symbols)
 454      def pgl( text )
 455          #GLYPHS.each do |re, resub, tog|
 456          #    next if tog and method( tog ).call
 457          #    text.gsub! re, resub
 458          #end
 459          text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m|
 460            "<acronym title=\"#{htmlesc $2}\">#{$1}</acronym>"
 461          end
 462      end
 463 
 464      # Parses Textile attribute lists and builds an HTML attribute string
 465      def pba( text_in, element = "" )
 466          
 467          return '' unless text_in
 468 
 469          style = []
 470          text = text_in.dup
 471          if element == 'td'
 472              colspan = $1 if text =~ /\\(\d+)/
 473              rowspan = $1 if text =~ /\/(\d+)/
 474              style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
 475          end
 476 
 477          if text.sub!( /\{([^"}]*)\}/, '' ) && !filter_styles
 478            sanitized = sanitize_styles($1)
 479            style << "#{ sanitized };" unless sanitized.blank?
 480          end
 481 
 482          lang = $1 if
 483              text.sub!( /\[([^)]+?)\]/, '' )
 484 
 485          cls = $1 if
 486              text.sub!( /\(([^()]+?)\)/, '' )
 487                          
 488          style << "padding-left:#{ $1.length }em;" if
 489              text.sub!( /([(]+)/, '' )
 490 
 491          style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
 492 
 493          style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
 494 
 495          cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
 496          
 497          atts = ''
 498          atts << " style=\"#{ style.join }\"" unless style.empty?
 499          atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
 500          atts << " lang=\"#{ lang }\"" if lang
 501          atts << " id=\"#{ id }\"" if id
 502          atts << " colspan=\"#{ colspan }\"" if colspan
 503          atts << " rowspan=\"#{ rowspan }\"" if rowspan
 504          
 505          atts
 506      end
 507 
 508      STYLES_RE = /^(color|width|height|border|background|padding|margin|font|text)(-[a-z]+)*:\s*((\d+%?|\d+px|\d+(\.\d+)?em|#[0-9a-f]+|[a-z]+)\s*)+$/i
 509 
 510      def sanitize_styles(str)
 511        styles = str.split(";").map(&:strip)
 512        styles.reject! do |style|
 513          !style.match(STYLES_RE)
 514        end
 515        styles.join(";")
 516      end
 517 
 518      TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
 519      
 520      # Parses a Textile table block, building HTML from the result.
 521      def block_textile_table( text ) 
 522          text.gsub!( TABLE_RE ) do |matches|
 523 
 524              tatts, fullrow = $~[1..2]
 525              tatts = pba( tatts, 'table' )
 526              tatts = shelve( tatts ) if tatts
 527              rows = []
 528 
 529              fullrow.each_line do |row|
 530                  ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
 531                  cells = []
 532                  row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
 533                      next if cell == '|'
 534                      ctyp = 'd'
 535                      ctyp = 'h' if cell =~ /^_/
 536 
 537                      catts = ''
 538                      catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
 539 
 540                      catts = shelve( catts ) if catts
 541                      cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>" 
 542                  end
 543                  ratts = shelve( ratts ) if ratts
 544                  rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
 545              end
 546              "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
 547          end
 548      end
 549 
 550      LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
 551      LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
 552 
 553      # Parses Textile lists and generates HTML
 554      def block_textile_lists( text ) 
 555          text.gsub!( LISTS_RE ) do |match|
 556              lines = match.split( /\n/ )
 557              last_line = -1
 558              depth = []
 559              lines.each_with_index do |line, line_id|
 560                  if line =~ LISTS_CONTENT_RE 
 561                      tl,atts,content = $~[1..3]
 562                      if depth.last
 563                          if depth.last.length > tl.length
 564                              (depth.length - 1).downto(0) do |i|
 565                                  break if depth[i].length == tl.length
 566                                  lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
 567                                  depth.pop
 568                              end
 569                          end
 570                          if depth.last and depth.last.length == tl.length
 571                              lines[line_id - 1] << '</li>'
 572                          end
 573                      end
 574                      unless depth.last == tl
 575                          depth << tl
 576                          atts = pba( atts )
 577                          atts = shelve( atts ) if atts
 578                          lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
 579                      else
 580                          lines[line_id] = "\t\t<li>#{ content }"
 581                      end
 582                      last_line = line_id
 583 
 584                  else
 585                      last_line = line_id
 586                  end
 587                  if line_id - last_line > 1 or line_id == lines.length - 1
 588                      depth.delete_if do |v|
 589                          lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
 590                      end
 591                  end
 592              end
 593              lines.join( "\n" )
 594          end
 595      end
 596      
 597      QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m
 598      QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
 599      
 600      def block_textile_quotes( text )
 601        text.gsub!( QUOTES_RE ) do |match|
 602          lines = match.split( /\n/ )
 603          quotes = ''
 604          indent = 0
 605          lines.each do |line|
 606            line =~ QUOTES_CONTENT_RE 
 607            bq,content = $1, $2
 608            l = bq.count('>')
 609            if l != indent
 610              quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
 611              indent = l
 612            end
 613            quotes << (content + "\n")
 614          end
 615          quotes << ("\n" + '</blockquote>' * indent + "\n\n")
 616          quotes
 617        end
 618      end
 619 
 620      CODE_RE = /(\W)
 621          @
 622          (?:\|(\w+?)\|)?
 623          (.+?)
 624          @
 625          (?=\W)/x
 626 
 627      def inline_textile_code( text ) 
 628          text.gsub!( CODE_RE ) do |m|
 629              before,lang,code,after = $~[1..4]
 630              lang = " lang=\"#{ lang }\"" if lang
 631              rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }", false )
 632          end
 633      end
 634 
 635      def lT( text ) 
 636          text =~ /\#$/ ? 'o' : 'u'
 637      end
 638 
 639      def hard_break( text )
 640          text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
 641      end
 642 
 643      BLOCKS_GROUP_RE = /\n{2,}(?! )/m
 644 
 645      def blocks( text, deep_code = false )
 646          text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
 647              plain = blk !~ /\A[#*> ]/
 648 
 649              # skip blocks that are complex HTML
 650              if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
 651                  blk
 652              else
 653                  # search for indentation levels
 654                  blk.strip!
 655                  if blk.empty?
 656                      blk
 657                  else
 658                      code_blk = nil
 659                      blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
 660                          flush_left iblk
 661                          blocks iblk, plain
 662                          iblk.gsub( /^(\S)/, "\t\\1" )
 663                          if plain
 664                              code_blk = iblk; ""
 665                          else
 666                              iblk
 667                          end
 668                      end
 669 
 670                      block_applied = 0 
 671                      @rules.each do |rule_name|
 672                          block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
 673                      end
 674                      if block_applied.zero?
 675                          if deep_code
 676                              blk = "\t<pre><code>#{ blk }</code></pre>"
 677                          else
 678                              blk = "\t<p>#{ blk }</p>"
 679                          end
 680                      end
 681                      # hard_break blk
 682                      blk + "\n#{ code_blk }"
 683                  end
 684              end
 685 
 686          end.join( "\n\n" ) )
 687      end
 688 
 689      def textile_bq( tag, atts, cite, content )
 690          cite, cite_title = check_refs( cite )
 691          cite = " cite=\"#{ cite }\"" if cite
 692          atts = shelve( atts ) if atts
 693          "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
 694      end
 695 
 696      def textile_p( tag, atts, cite, content )
 697          atts = shelve( atts ) if atts
 698          "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
 699      end
 700 
 701      alias textile_h1 textile_p
 702      alias textile_h2 textile_p
 703      alias textile_h3 textile_p
 704      alias textile_h4 textile_p
 705      alias textile_h5 textile_p
 706      alias textile_h6 textile_p
 707 
 708      def textile_fn_( tag, num, atts, cite, content )
 709          atts << " id=\"fn#{ num }\" class=\"footnote\""
 710          content = "<sup>#{ num }</sup> #{ content }"
 711          atts = shelve( atts ) if atts
 712          "\t<p#{ atts }>#{ content }</p>"
 713      end
 714 
 715      BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
 716 
 717      def block_textile_prefix( text ) 
 718          if text =~ BLOCK_RE
 719              tag,tagpre,num,atts,cite,content = $~[1..6]
 720              atts = pba( atts )
 721 
 722              # pass to prefix handler
 723              replacement = nil
 724              if respond_to? "textile_#{ tag }", true
 725                replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content )
 726              elsif respond_to? "textile_#{ tagpre }_", true
 727                replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content )  
 728              end
 729              text.gsub!( $& ) { replacement } if replacement
 730          end
 731      end
 732      
 733      SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
 734      def block_markdown_setext( text )
 735          if text =~ SETEXT_RE
 736              tag = if $2 == "="; "h1"; else; "h2"; end
 737              blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
 738              blocks cont
 739              text.replace( blk + cont )
 740          end
 741      end
 742 
 743      ATX_RE = /\A(\#{1,6})  # $1 = string of #'s
 744                [ ]*
 745                (.+?)       # $2 = Header text
 746                [ ]*
 747                \#*         # optional closing #'s (not counted)
 748                $/x
 749      def block_markdown_atx( text )
 750          if text =~ ATX_RE
 751              tag = "h#{ $1.length }"
 752              blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
 753              blocks cont
 754              text.replace( blk + cont )
 755          end
 756      end
 757 
 758      MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
 759 
 760      def block_markdown_bq( text )
 761          text.gsub!( MARKDOWN_BQ_RE ) do |blk|
 762              blk.gsub!( /^ *> ?/, '' )
 763              flush_left blk
 764              blocks blk
 765              blk.gsub!( /^(\S)/, "\t\\1" )
 766              "<blockquote>\n#{ blk }\n</blockquote>\n\n"
 767          end
 768      end
 769 
 770      MARKDOWN_RULE_RE = /^(#{
 771          ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
 772      })$/
 773 
 774      def block_markdown_rule( text )
 775          text.gsub!( MARKDOWN_RULE_RE ) do |blk|
 776              "<hr />"
 777          end
 778      end
 779 
 780      # XXX TODO XXX
 781      def block_markdown_lists( text )
 782      end
 783 
 784      def inline_textile_span( text ) 
 785          QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
 786              text.gsub!( qtag_re ) do |m|
 787               
 788                  case rtype
 789                  when :limit
 790                      sta,oqs,qtag,content,oqa = $~[1..6]
 791                      atts = nil
 792                      if content =~ /^(#{C})(.+)$/
 793                        atts, content = $~[1..2]
 794                      end
 795                  else
 796                      qtag,atts,cite,content = $~[1..4]
 797                      sta = ''
 798                  end
 799                  atts = pba( atts )
 800                  atts = shelve( atts ) if atts
 801 
 802                  "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }</#{ ht }>#{ oqa }"
 803 
 804              end
 805          end
 806      end
 807 
 808      LINK_RE = /
 809              (
 810              ([\s\[{(]|[#{PUNCT}])?     # $pre
 811              "                          # start
 812              (#{C})                     # $atts
 813              ([^"\n]+?)                 # $text
 814              \s?
 815              (?:\(([^)]+?)\)(?="))?     # $title
 816              ":
 817              (                          # $url
 818              (\/|[a-zA-Z]+:\/\/|www\.|mailto:)  # $proto
 819              [\w\/]\S+?
 820              )               
 821              (\/)?                      # $slash
 822              ([^\w\=\/;\(\)]*?)         # $post
 823              )
 824              (?=<|\s|$)
 825          /x 
 826  #"
 827      def inline_textile_link( text ) 
 828          text.gsub!( LINK_RE ) do |m|
 829            all,pre,atts,text,title,url,proto,slash,post = $~[1..9]
 830            if text.include?('<br />')
 831              all
 832            else
 833              url, url_title = check_refs( url )
 834              title ||= url_title
 835              
 836              # Idea below : an URL with unbalanced parethesis and
 837              # ending by ')' is put into external parenthesis
 838              if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
 839                url=url[0..-2] # discard closing parenth from url
 840                post = ")"+post # add closing parenth to post
 841              end
 842              atts = pba( atts )
 843              atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }"
 844              atts << " title=\"#{ htmlesc title }\"" if title
 845              atts = shelve( atts ) if atts
 846              
 847              external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
 848              
 849              "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
 850            end
 851          end
 852      end
 853 
 854      MARKDOWN_REFLINK_RE = /
 855              \[([^\[\]]+)\]      # $text
 856              [ ]?                # opt. space
 857              (?:\n[ ]*)?         # one optional newline followed by spaces
 858              \[(.*?)\]           # $id
 859          /x 
 860 
 861      def inline_markdown_reflink( text ) 
 862          text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
 863              text, id = $~[1..2]
 864 
 865              if id.empty?
 866                  url, title = check_refs( text )
 867              else
 868                  url, title = check_refs( id )
 869              end
 870              
 871              atts = " href=\"#{ url }\""
 872              atts << " title=\"#{ title }\"" if title
 873              atts = shelve( atts )
 874              
 875              "<a#{ atts }>#{ text }</a>"
 876          end
 877      end
 878 
 879      MARKDOWN_LINK_RE = /
 880              \[([^\[\]]+)\]      # $text
 881              \(                  # open paren
 882              [ \t]*              # opt space
 883              <?(.+?)>?           # $href
 884              [ \t]*              # opt space
 885              (?:                 # whole title
 886              (['"])              # $quote
 887              (.*?)               # $title
 888              \3                  # matching quote
 889              )?                  # title is optional
 890              \)
 891          /x 
 892 
 893      def inline_markdown_link( text ) 
 894          text.gsub!( MARKDOWN_LINK_RE ) do |m|
 895              text, url, quote, title = $~[1..4]
 896 
 897              atts = " href=\"#{ url }\""
 898              atts << " title=\"#{ title }\"" if title
 899              atts = shelve( atts )
 900              
 901              "<a#{ atts }>#{ text }</a>"
 902          end
 903      end
 904 
 905      TEXTILE_REFS_RE =  /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
 906      MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
 907 
 908      def refs( text )
 909          @rules.each do |rule_name|
 910              method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
 911          end
 912      end
 913 
 914      def refs_textile( text ) 
 915          text.gsub!( TEXTILE_REFS_RE ) do |m|
 916              flag, url = $~[2..3]
 917              @urlrefs[flag.downcase] = [url, nil]
 918              nil
 919          end
 920      end
 921      
 922      def refs_markdown( text )
 923          text.gsub!( MARKDOWN_REFS_RE ) do |m|
 924              flag, url = $~[2..3]
 925              title = $~[6]
 926              @urlrefs[flag.downcase] = [url, title]
 927              nil
 928          end
 929      end
 930 
 931      def check_refs( text ) 
 932          ret = @urlrefs[text.downcase] if text
 933          ret || [text, nil]
 934      end
 935 
 936      IMAGE_RE = /
 937              (>|\s|^)           # start of line?
 938              \!                   # opening
 939              (\<|\=|\>)?          # optional alignment atts
 940              (#{C})               # optional style,class atts
 941              (?:\. )?             # optional dot-space
 942              ([^\s(!]+?)          # presume this is the src
 943              \s?                  # optional space
 944              (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))?   # optional title
 945              \!                   # closing
 946              (?::#{ HYPERLINK })? # optional href
 947          /x 
 948 
 949      def inline_textile_image( text ) 
 950          text.gsub!( IMAGE_RE )  do |m|
 951              stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
 952              htmlesc title
 953              atts = pba( atts )
 954              atts = " src=\"#{ htmlesc url.dup }\"#{ atts }"
 955              atts << " title=\"#{ title }\"" if title
 956              atts << " alt=\"#{ title }\"" 
 957              # size = @getimagesize($url);
 958              # if($size) $atts.= " $size[3]";
 959 
 960              href, alt_title = check_refs( href ) if href
 961              url, url_title = check_refs( url )
 962 
 963              out = ''
 964              out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
 965              out << "<img#{ shelve( atts ) } />"
 966              out << "</a>#{ href_a1 }#{ href_a2 }" if href
 967              
 968              if algn 
 969                  algn = h_align( algn )
 970                  if stln == "<p>"
 971                      out = "<p style=\"float:#{ algn }\">#{ out }"
 972                  else
 973                      out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
 974                  end
 975              else
 976                  out = stln + out
 977              end
 978 
 979              out
 980          end
 981      end
 982 
 983      def shelve( val ) 
 984          @shelf << val
 985          " :redsh##{ @shelf.length }:"
 986      end
 987      
 988      def retrieve( text ) 
 989          @shelf.each_with_index do |r, i|
 990              text.gsub!( " :redsh##{ i + 1 }:", r )
 991          end
 992      end
 993 
 994      def incoming_entities( text ) 
 995          ## turn any incoming ampersands into a dummy character for now.
 996          ## This uses a negative lookahead for alphanumerics followed by a semicolon,
 997          ## implying an incoming html entity, to be skipped
 998 
 999          text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
1000      end
1001 
1002      def no_textile( text ) 
1003          text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
1004              '\1<notextile>\2</notextile>\3' )
1005          text.gsub!( /^ *==([^=]+.*?)==/m,
1006              '\1<notextile>\2</notextile>\3' )
1007      end
1008 
1009      def clean_white_space( text ) 
1010          # normalize line breaks
1011          text.gsub!( /\r\n/, "\n" )
1012          text.gsub!( /\r/, "\n" )
1013          text.gsub!( /\t/, '    ' )
1014          text.gsub!( /^ +$/, '' )
1015          text.gsub!( /\n{3,}/, "\n\n" )
1016          text.gsub!( /"$/, "\" " )
1017 
1018          # if entire document is indented, flush
1019          # to the left side
1020          flush_left text
1021      end
1022 
1023      def flush_left( text )
1024          indt = 0
1025          if text =~ /^ /
1026              while text !~ /^ {#{indt}}\S/
1027                  indt += 1
1028              end unless text.empty?
1029              if indt.nonzero?
1030                  text.gsub!( /^ {#{indt}}/, '' )
1031              end
1032          end
1033      end
1034 
1035      def footnote_ref( text ) 
1036          text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1037              '<sup><a href="#fn\1">\1</a></sup>\2' )
1038      end
1039      
1040      OFFTAGS = /(code|pre|kbd|notextile)/
1041      OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi
1042      OFFTAG_OPEN = /<#{ OFFTAGS }/
1043      OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1044      HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1045      ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1046 
1047      def glyphs_textile( text, level = 0 )
1048          if text !~ HASTAG_MATCH
1049              pgl text
1050              footnote_ref text
1051          else
1052              codepre = 0
1053              text.gsub!( ALLTAG_MATCH ) do |line|
1054                  ## matches are off if we're between <code>, <pre> etc.
1055                  if $1
1056                      if line =~ OFFTAG_OPEN
1057                          codepre += 1
1058                      elsif line =~ OFFTAG_CLOSE
1059                          codepre -= 1
1060                          codepre = 0 if codepre < 0
1061                      end 
1062                  elsif codepre.zero?
1063                      glyphs_textile( line, level + 1 )
1064                  else
1065                      htmlesc( line, :NoQuotes )
1066                  end
1067                  # p [level, codepre, line]
1068 
1069                  line
1070              end
1071          end
1072      end
1073 
1074      def rip_offtags( text, escape_aftertag=true, escape_line=true )
1075          if text =~ /<.*>/
1076              ## strip and encode <pre> content
1077              codepre, used_offtags = 0, {}
1078              text.gsub!( OFFTAG_MATCH ) do |line|
1079                  if $3
1080                      first, offtag, aftertag = $3, $4, $5
1081                      codepre += 1
1082                      used_offtags[offtag] = true
1083                      if codepre - used_offtags.length > 0
1084                          htmlesc( line, :NoQuotes ) if escape_line
1085                          @pre_list.last << line
1086                          line = ""
1087                      else
1088                          ### htmlesc is disabled between CODE tags which will be parsed with highlighter
1089                          ### Regexp in formatter.rb is : /<code\s+class="(\w+)">\s?(.+)/m
1090                          ### NB: some changes were made not to use $N variables, because we use "match"
1091                          ###   and it breaks following lines
1092                          htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(/<code\s+class="(\w+)">/)
1093                          line = "<redpre##{ @pre_list.length }>"
1094                          first.match(/<#{ OFFTAGS }([^>]*)>/)
1095                          tag = $1
1096                          $2.to_s.match(/(class\=("[^"]+"|'[^']+'))/i)
1097                          tag << " #{$1}" if $1
1098                          @pre_list << "<#{ tag }>#{ aftertag }"
1099                      end
1100                  elsif $1 and codepre > 0
1101                      if codepre - used_offtags.length > 0
1102                          htmlesc( line, :NoQuotes ) if escape_line
1103                          @pre_list.last << line
1104                          line = ""
1105                      end
1106                      codepre -= 1 unless codepre.zero?
1107                      used_offtags = {} if codepre.zero?
1108                  end 
1109                  line
1110              end
1111          end
1112          text
1113      end
1114 
1115      def smooth_offtags( text )
1116          unless @pre_list.empty?
1117              ## replace <pre> content
1118              text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1119          end
1120      end
1121 
1122      def inline( text ) 
1123          [/^inline_/, /^glyphs_/].each do |meth_re|
1124              @rules.each do |rule_name|
1125                  method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1126              end
1127          end
1128      end
1129 
1130      def h_align( text ) 
1131          H_ALGN_VALS[text]
1132      end
1133 
1134      def v_align( text ) 
1135          V_ALGN_VALS[text]
1136      end
1137 
1138      def textile_popup_help( name, windowW, windowH )
1139          ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1140      end
1141 
1142      # HTML cleansing stuff
1143      BASIC_TAGS = {
1144          'a' => ['href', 'title'],
1145          'img' => ['src', 'alt', 'title'],
1146          'br' => [],
1147          'i' => nil,
1148          'u' => nil, 
1149          'b' => nil,
1150          'pre' => nil,
1151          'kbd' => nil,
1152          'code' => ['lang'],
1153          'cite' => nil,
1154          'strong' => nil,
1155          'em' => nil,
1156          'ins' => nil,
1157          'sup' => nil,
1158          'sub' => nil,
1159          'del' => nil,
1160          'table' => nil,
1161          'tr' => nil,
1162          'td' => ['colspan', 'rowspan'],
1163          'th' => nil,
1164          'ol' => nil,
1165          'ul' => nil,
1166          'li' => nil,
1167          'p' => nil,
1168          'h1' => nil,
1169          'h2' => nil,
1170          'h3' => nil,
1171          'h4' => nil,
1172          'h5' => nil,
1173          'h6' => nil, 
1174          'blockquote' => ['cite']
1175      }
1176 
1177      def clean_html( text, tags = BASIC_TAGS )
1178          text.gsub!( /<!\[CDATA\[/, '' )
1179          text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1180              raw = $~
1181              tag = raw[2].downcase
1182              if tags.has_key? tag
1183                  pcs = [tag]
1184                  tags[tag].each do |prop|
1185                      ['"', "'", ''].each do |q|
1186                          q2 = ( q != '' ? q : '\s' )
1187                          if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1188                              attrv = $1
1189                              next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1190                              pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1191                              break
1192                          end
1193                      end
1194                  end if tags[tag]
1195                  "<#{raw[1]}#{pcs.join " "}>"
1196              else
1197                  " "
1198              end
1199          end
1200      end
1201      
1202      ALLOWED_TAGS = %w(redpre pre code notextile)
1203      
1204      def escape_html_tags(text)
1205        text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1206      end
1207  end
1208