Chris@909: # vim:ts=4:sw=4: Chris@909: # = RedCloth - Textile and Markdown Hybrid for Ruby Chris@909: # Chris@909: # Homepage:: http://whytheluckystiff.net/ruby/redcloth/ Chris@909: # Author:: why the lucky stiff (http://whytheluckystiff.net/) Chris@909: # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.) Chris@909: # License:: BSD Chris@909: # Chris@909: # (see http://hobix.com/textile/ for a Textile Reference.) Chris@909: # Chris@909: # Based on (and also inspired by) both: Chris@909: # Chris@909: # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt Chris@909: # Textism for PHP: http://www.textism.com/tools/textile/ Chris@909: # Chris@909: # Chris@909: Chris@909: # = RedCloth Chris@909: # Chris@909: # RedCloth is a Ruby library for converting Textile and/or Markdown Chris@909: # into HTML. You can use either format, intermingled or separately. Chris@909: # You can also extend RedCloth to honor your own custom text stylings. Chris@909: # Chris@909: # RedCloth users are encouraged to use Textile if they are generating Chris@909: # HTML and to use Markdown if others will be viewing the plain text. Chris@909: # Chris@909: # == What is Textile? Chris@909: # Chris@909: # Textile is a simple formatting style for text Chris@909: # documents, loosely based on some HTML conventions. Chris@909: # Chris@909: # == Sample Textile Text Chris@909: # Chris@909: # h2. This is a title Chris@909: # Chris@909: # h3. This is a subhead Chris@909: # Chris@909: # This is a bit of paragraph. Chris@909: # Chris@909: # bq. This is a blockquote. Chris@909: # Chris@909: # = Writing Textile Chris@909: # Chris@909: # A Textile document consists of paragraphs. Paragraphs Chris@909: # can be specially formatted by adding a small instruction Chris@909: # to the beginning of the paragraph. Chris@909: # Chris@909: # h[n]. Header of size [n]. Chris@909: # bq. Blockquote. Chris@909: # # Numeric list. Chris@909: # * Bulleted list. Chris@909: # Chris@909: # == Quick Phrase Modifiers Chris@909: # Chris@909: # Quick phrase modifiers are also included, to allow formatting Chris@909: # of small portions of text within a paragraph. Chris@909: # Chris@909: # \_emphasis\_ Chris@909: # \_\_italicized\_\_ Chris@909: # \*strong\* Chris@909: # \*\*bold\*\* Chris@909: # ??citation?? Chris@909: # -deleted text- Chris@909: # +inserted text+ Chris@909: # ^superscript^ Chris@909: # ~subscript~ Chris@909: # @code@ Chris@909: # %(classname)span% Chris@909: # Chris@909: # ==notextile== (leave text alone) Chris@909: # Chris@909: # == Links Chris@909: # Chris@909: # To make a hypertext link, put the link text in "quotation Chris@909: # marks" followed immediately by a colon and the URL of the link. Chris@909: # Chris@909: # Optional: text in (parentheses) following the link text, Chris@909: # but before the closing quotation mark, will become a Title Chris@909: # attribute for the link, visible as a tool tip when a cursor is above it. Chris@909: # Chris@909: # Example: Chris@909: # Chris@909: # "This is a link (This is a title) ":http://www.textism.com Chris@909: # Chris@909: # Will become: Chris@909: # Chris@909: # This is a link Chris@909: # Chris@909: # == Images Chris@909: # Chris@909: # To insert an image, put the URL for the image inside exclamation marks. Chris@909: # Chris@909: # Optional: text that immediately follows the URL in (parentheses) will Chris@909: # be used as the Alt text for the image. Images on the web should always Chris@909: # have descriptive Alt text for the benefit of readers using non-graphical Chris@909: # browsers. Chris@909: # Chris@909: # Optional: place a colon followed by a URL immediately after the Chris@909: # closing ! to make the image into a link. Chris@909: # Chris@909: # Example: Chris@909: # Chris@909: # !http://www.textism.com/common/textist.gif(Textist)! Chris@909: # Chris@909: # Will become: Chris@909: # Chris@909: # Textist Chris@909: # Chris@909: # With a link: Chris@909: # Chris@909: # !/common/textist.gif(Textist)!:http://textism.com Chris@909: # Chris@909: # Will become: Chris@909: # Chris@909: # Textist Chris@909: # Chris@909: # == Defining Acronyms Chris@909: # Chris@909: # HTML allows authors to define acronyms via the tag. The definition appears as a Chris@909: # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, Chris@909: # this should be used at least once for each acronym in documents where they appear. Chris@909: # Chris@909: # To quickly define an acronym in Textile, place the full text in (parentheses) Chris@909: # immediately following the acronym. Chris@909: # Chris@909: # Example: Chris@909: # Chris@909: # ACLU(American Civil Liberties Union) Chris@909: # Chris@909: # Will become: Chris@909: # Chris@909: # ACLU Chris@909: # Chris@909: # == Adding Tables Chris@909: # Chris@909: # In Textile, simple tables can be added by seperating each column by Chris@909: # a pipe. Chris@909: # Chris@909: # |a|simple|table|row| Chris@909: # |And|Another|table|row| Chris@909: # Chris@909: # Attributes are defined by style definitions in parentheses. Chris@909: # Chris@909: # table(border:1px solid black). Chris@909: # (background:#ddd;color:red). |{}| | | | Chris@909: # Chris@909: # == Using RedCloth Chris@909: # Chris@909: # RedCloth is simply an extension of the String class, which can handle Chris@909: # Textile formatting. Use it like a String and output HTML with its Chris@909: # RedCloth#to_html method. Chris@909: # Chris@909: # doc = RedCloth.new " Chris@909: # Chris@909: # h2. Test document Chris@909: # Chris@909: # Just a simple test." Chris@909: # Chris@909: # puts doc.to_html Chris@909: # Chris@909: # By default, RedCloth uses both Textile and Markdown formatting, with Chris@909: # Textile formatting taking precedence. If you want to turn off Markdown Chris@909: # formatting, to boost speed and limit the processor: Chris@909: # Chris@909: # class RedCloth::Textile.new( str ) Chris@909: Chris@909: class RedCloth3 < String Chris@909: Chris@909: VERSION = '3.0.4' Chris@909: DEFAULT_RULES = [:textile, :markdown] Chris@909: Chris@909: # Chris@909: # Two accessor for setting security restrictions. Chris@909: # Chris@909: # This is a nice thing if you're using RedCloth for Chris@909: # formatting in public places (e.g. Wikis) where you Chris@909: # don't want users to abuse HTML for bad things. Chris@909: # Chris@909: # If +:filter_html+ is set, HTML which wasn't Chris@909: # created by the Textile processor will be escaped. Chris@909: # Chris@909: # If +:filter_styles+ is set, it will also disable Chris@909: # the style markup specifier. ('{color: red}') Chris@909: # Chris@909: attr_accessor :filter_html, :filter_styles Chris@909: Chris@909: # Chris@909: # Accessor for toggling hard breaks. Chris@909: # Chris@909: # If +:hard_breaks+ is set, single newlines will Chris@909: # be converted to HTML break tags. This is the Chris@909: # default behavior for traditional RedCloth. Chris@909: # Chris@909: attr_accessor :hard_breaks Chris@909: Chris@909: # Accessor for toggling lite mode. Chris@909: # Chris@909: # In lite mode, block-level rules are ignored. This means Chris@909: # that tables, paragraphs, lists, and such aren't available. Chris@909: # Only the inline markup for bold, italics, entities and so on. Chris@909: # Chris@909: # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] ) Chris@909: # r.to_html Chris@909: # #=> "And then? She fell!" Chris@909: # Chris@909: attr_accessor :lite_mode Chris@909: Chris@909: # Chris@909: # Accessor for toggling span caps. Chris@909: # Chris@909: # Textile places `span' tags around capitalized Chris@909: # words by default, but this wreaks havoc on Wikis. Chris@909: # If +:no_span_caps+ is set, this will be Chris@909: # suppressed. Chris@909: # Chris@909: attr_accessor :no_span_caps Chris@909: Chris@909: # Chris@909: # Establishes the markup predence. Available rules include: Chris@909: # Chris@909: # == Textile Rules Chris@909: # Chris@909: # The following textile rules can be set individually. Or add the complete Chris@909: # set of rules with the single :textile rule, which supplies the rule set in Chris@909: # the following precedence: Chris@909: # Chris@909: # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/) Chris@909: # block_textile_table:: Textile table block structures Chris@909: # block_textile_lists:: Textile list structures Chris@909: # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.) Chris@909: # inline_textile_image:: Textile inline images Chris@909: # inline_textile_link:: Textile inline links Chris@909: # inline_textile_span:: Textile inline spans Chris@909: # glyphs_textile:: Textile entities (such as em-dashes and smart quotes) Chris@909: # Chris@909: # == Markdown Chris@909: # Chris@909: # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/) Chris@909: # block_markdown_setext:: Markdown setext headers Chris@909: # block_markdown_atx:: Markdown atx headers Chris@909: # block_markdown_rule:: Markdown horizontal rules Chris@909: # block_markdown_bq:: Markdown blockquotes Chris@909: # block_markdown_lists:: Markdown lists Chris@909: # inline_markdown_link:: Markdown links Chris@909: attr_accessor :rules Chris@909: Chris@909: # Returns a new RedCloth object, based on _string_ and Chris@909: # enforcing all the included _restrictions_. Chris@909: # Chris@909: # r = RedCloth.new( "h1. A bold man", [:filter_html] ) Chris@909: # r.to_html Chris@909: # #=>"

A <b>bold</b> man

" Chris@909: # Chris@909: def initialize( string, restrictions = [] ) Chris@909: restrictions.each { |r| method( "#{ r }=" ).call( true ) } Chris@909: super( string ) Chris@909: end Chris@909: Chris@909: # Chris@909: # Generates HTML from the Textile contents. Chris@909: # Chris@909: # r = RedCloth.new( "And then? She *fell*!" ) Chris@909: # r.to_html( true ) Chris@909: # #=>"And then? She fell!" Chris@909: # Chris@909: def to_html( *rules ) Chris@909: rules = DEFAULT_RULES if rules.empty? Chris@909: # make our working copy Chris@909: text = self.dup Chris@909: Chris@909: @urlrefs = {} Chris@909: @shelf = [] Chris@909: textile_rules = [:block_textile_table, :block_textile_lists, Chris@909: :block_textile_prefix, :inline_textile_image, :inline_textile_link, Chris@909: :inline_textile_code, :inline_textile_span, :glyphs_textile] Chris@909: markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule, Chris@909: :block_markdown_bq, :block_markdown_lists, Chris@909: :inline_markdown_reflink, :inline_markdown_link] Chris@909: @rules = rules.collect do |rule| Chris@909: case rule Chris@909: when :markdown Chris@909: markdown_rules Chris@909: when :textile Chris@909: textile_rules Chris@909: else Chris@909: rule Chris@909: end Chris@909: end.flatten Chris@909: Chris@909: # standard clean up Chris@909: incoming_entities text Chris@909: clean_white_space text Chris@909: Chris@909: # start processor Chris@909: @pre_list = [] Chris@909: rip_offtags text Chris@909: no_textile text Chris@909: escape_html_tags text Chris@909: # need to do this before #hard_break and #blocks Chris@909: block_textile_quotes text unless @lite_mode Chris@909: hard_break text Chris@909: unless @lite_mode Chris@909: refs text Chris@909: blocks text Chris@909: end Chris@909: inline text Chris@909: smooth_offtags text Chris@909: Chris@909: retrieve text Chris@909: Chris@909: text.gsub!( /<\/?notextile>/, '' ) Chris@909: text.gsub!( /x%x%/, '&' ) Chris@909: clean_html text if filter_html Chris@909: text.strip! Chris@909: text Chris@909: Chris@909: end Chris@909: Chris@909: ####### Chris@909: private Chris@909: ####### Chris@909: # Chris@909: # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. Chris@909: # (from PyTextile) Chris@909: # Chris@909: TEXTILE_TAGS = Chris@909: Chris@909: [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], Chris@909: [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], Chris@909: [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], Chris@909: [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], Chris@909: [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. Chris@909: Chris@909: collect! do |a, b| Chris@909: [a.chr, ( b.zero? and "" or "&#{ b };" )] Chris@909: end Chris@909: Chris@909: # Chris@909: # Regular expressions to convert to HTML. Chris@909: # Chris@909: A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ Chris@909: A_VLGN = /[\-^~]/ Chris@909: C_CLAS = '(?:\([^")]+\))' Chris@909: C_LNGE = '(?:\[[^"\[\]]+\])' Chris@909: C_STYL = '(?:\{[^"}]+\})' Chris@909: S_CSPN = '(?:\\\\\d+)' Chris@909: S_RSPN = '(?:/\d+)' Chris@909: A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" Chris@909: S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" Chris@909: C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" Chris@909: # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ) Chris@909: PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) Chris@909: PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' ) Chris@909: PUNCT_Q = Regexp::quote( '*-_+^~%' ) Chris@909: HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)' Chris@909: Chris@909: # Text markup tags, don't conflict with block tags Chris@909: SIMPLE_HTML_TAGS = [ Chris@909: 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code', Chris@909: 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br', Chris@909: 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo' Chris@909: ] Chris@909: Chris@909: QTAGS = [ Chris@909: ['**', 'b', :limit], Chris@909: ['*', 'strong', :limit], Chris@909: ['??', 'cite', :limit], Chris@909: ['-', 'del', :limit], Chris@909: ['__', 'i', :limit], Chris@909: ['_', 'em', :limit], Chris@909: ['%', 'span', :limit], Chris@909: ['+', 'ins', :limit], Chris@909: ['^', 'sup', :limit], Chris@909: ['~', 'sub', :limit] Chris@909: ] Chris@909: QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|') Chris@909: Chris@909: QTAGS.collect! do |rc, ht, rtype| Chris@909: rcq = Regexp::quote rc Chris@909: re = Chris@909: case rtype Chris@909: when :limit Chris@909: /(^|[>\s\(]) # sta Chris@909: (?!\-\-) Chris@909: (#{QTAGS_JOIN}|) # oqs Chris@909: (#{rcq}) # qtag Chris@909: (\w|[^\s].*?[^\s]) # content Chris@909: (?!\-\-) Chris@909: #{rcq} Chris@909: (#{QTAGS_JOIN}|) # oqa Chris@909: (?=[[:punct:]]|<|\s|\)|$)/x Chris@909: else Chris@909: /(#{rcq}) Chris@909: (#{C}) Chris@909: (?::(\S+))? Chris@909: (\w|[^\s\-].*?[^\s\-]) Chris@909: #{rcq}/xm Chris@909: end Chris@909: [rc, ht, re, rtype] Chris@909: end Chris@909: Chris@909: # Elements to handle Chris@909: GLYPHS = [ Chris@909: # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing Chris@909: # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing Chris@909: # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing Chris@909: # [ /\'/, '‘' ], # single opening Chris@909: # [ //, '>' ], # greater-than Chris@909: # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing Chris@909: # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing Chris@909: # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing Chris@909: # [ /"/, '“' ], # double opening Chris@909: # [ /\b( )?\.{3}/, '\1…' ], # ellipsis Chris@909: # [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym Chris@909: # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^\2\3', :no_span_caps ], # 3+ uppercase caps Chris@909: # [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash Chris@909: # [ /\s->\s/, ' → ' ], # right arrow Chris@909: # [ /\s-\s/, ' – ' ], # en dash Chris@909: # [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign Chris@909: # [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark Chris@909: # [ /\b ?[(\[]R[\])]/i, '®' ], # registered Chris@909: # [ /\b ?[(\[]C[\])]/i, '©' ] # copyright Chris@909: ] Chris@909: Chris@909: H_ALGN_VALS = { Chris@909: '<' => 'left', Chris@909: '=' => 'center', Chris@909: '>' => 'right', Chris@909: '<>' => 'justify' Chris@909: } Chris@909: Chris@909: V_ALGN_VALS = { Chris@909: '^' => 'top', Chris@909: '-' => 'middle', Chris@909: '~' => 'bottom' Chris@909: } Chris@909: Chris@909: # Chris@909: # Flexible HTML escaping Chris@909: # Chris@909: def htmlesc( str, mode=:Quotes ) Chris@909: if str Chris@909: str.gsub!( '&', '&' ) Chris@909: str.gsub!( '"', '"' ) if mode != :NoQuotes Chris@909: str.gsub!( "'", ''' ) if mode == :Quotes Chris@909: str.gsub!( '<', '<') Chris@909: str.gsub!( '>', '>') Chris@909: end Chris@909: str Chris@909: end Chris@909: Chris@909: # Search and replace for Textile glyphs (quotes, dashes, other symbols) Chris@909: def pgl( text ) Chris@909: #GLYPHS.each do |re, resub, tog| Chris@909: # next if tog and method( tog ).call Chris@909: # text.gsub! re, resub Chris@909: #end Chris@909: text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m| Chris@909: "#{$1}" Chris@909: end Chris@909: end Chris@909: Chris@909: # Parses Textile attribute lists and builds an HTML attribute string Chris@909: def pba( text_in, element = "" ) Chris@909: Chris@909: return '' unless text_in Chris@909: Chris@909: style = [] Chris@909: text = text_in.dup Chris@909: if element == 'td' Chris@909: colspan = $1 if text =~ /\\(\d+)/ Chris@909: rowspan = $1 if text =~ /\/(\d+)/ Chris@909: style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN Chris@909: end Chris@909: Chris@909: style << "#{ htmlesc $1 };" if text.sub!( /\{([^}]*)\}/, '' ) && !filter_styles Chris@909: Chris@909: lang = $1 if Chris@909: text.sub!( /\[([^)]+?)\]/, '' ) Chris@909: Chris@909: cls = $1 if Chris@909: text.sub!( /\(([^()]+?)\)/, '' ) Chris@909: Chris@909: style << "padding-left:#{ $1.length }em;" if Chris@909: text.sub!( /([(]+)/, '' ) Chris@909: Chris@909: style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) Chris@909: Chris@909: style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN Chris@909: Chris@909: cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ Chris@909: Chris@909: atts = '' Chris@909: atts << " style=\"#{ style.join }\"" unless style.empty? Chris@909: atts << " class=\"#{ cls }\"" unless cls.to_s.empty? Chris@909: atts << " lang=\"#{ lang }\"" if lang Chris@909: atts << " id=\"#{ id }\"" if id Chris@909: atts << " colspan=\"#{ colspan }\"" if colspan Chris@909: atts << " rowspan=\"#{ rowspan }\"" if rowspan Chris@909: Chris@909: atts Chris@909: end Chris@909: Chris@909: TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m Chris@909: Chris@909: # Parses a Textile table block, building HTML from the result. Chris@909: def block_textile_table( text ) Chris@909: text.gsub!( TABLE_RE ) do |matches| Chris@909: Chris@909: tatts, fullrow = $~[1..2] Chris@909: tatts = pba( tatts, 'table' ) Chris@909: tatts = shelve( tatts ) if tatts Chris@909: rows = [] Chris@909: Chris@909: fullrow.each_line do |row| Chris@909: ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m Chris@909: cells = [] Chris@909: row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell| Chris@909: next if cell == '|' Chris@909: ctyp = 'd' Chris@909: ctyp = 'h' if cell =~ /^_/ Chris@909: Chris@909: catts = '' Chris@909: catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/ Chris@909: Chris@909: catts = shelve( catts ) if catts Chris@909: cells << "\t\t\t#{ cell }" Chris@909: end Chris@909: ratts = shelve( ratts ) if ratts Chris@909: rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" Chris@909: end Chris@909: "\t\n#{ rows.join( "\n" ) }\n\t\n\n" Chris@909: end Chris@909: end Chris@909: Chris@909: LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m Chris@909: LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m Chris@909: Chris@909: # Parses Textile lists and generates HTML Chris@909: def block_textile_lists( text ) Chris@909: text.gsub!( LISTS_RE ) do |match| Chris@909: lines = match.split( /\n/ ) Chris@909: last_line = -1 Chris@909: depth = [] Chris@909: lines.each_with_index do |line, line_id| Chris@909: if line =~ LISTS_CONTENT_RE Chris@909: tl,atts,content = $~[1..3] Chris@909: if depth.last Chris@909: if depth.last.length > tl.length Chris@909: (depth.length - 1).downto(0) do |i| Chris@909: break if depth[i].length == tl.length Chris@909: lines[line_id - 1] << "\n\t\n\t" Chris@909: depth.pop Chris@909: end Chris@909: end Chris@909: if depth.last and depth.last.length == tl.length Chris@909: lines[line_id - 1] << '' Chris@909: end Chris@909: end Chris@909: unless depth.last == tl Chris@909: depth << tl Chris@909: atts = pba( atts ) Chris@909: atts = shelve( atts ) if atts Chris@909: lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t
  • #{ content }" Chris@909: else Chris@909: lines[line_id] = "\t\t
  • #{ content }" Chris@909: end Chris@909: last_line = line_id Chris@909: Chris@909: else Chris@909: last_line = line_id Chris@909: end Chris@909: if line_id - last_line > 1 or line_id == lines.length - 1 Chris@909: depth.delete_if do |v| Chris@909: lines[last_line] << "
  • \n\t" Chris@909: end Chris@909: end Chris@909: end Chris@909: lines.join( "\n" ) Chris@909: end Chris@909: end Chris@909: Chris@909: QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m Chris@909: QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m Chris@909: Chris@909: def block_textile_quotes( text ) Chris@909: text.gsub!( QUOTES_RE ) do |match| Chris@909: lines = match.split( /\n/ ) Chris@909: quotes = '' Chris@909: indent = 0 Chris@909: lines.each do |line| Chris@909: line =~ QUOTES_CONTENT_RE Chris@909: bq,content = $1, $2 Chris@909: l = bq.count('>') Chris@909: if l != indent Chris@909: quotes << ("\n\n" + (l>indent ? '
    ' * (l-indent) : '
    ' * (indent-l)) + "\n\n") Chris@909: indent = l Chris@909: end Chris@909: quotes << (content + "\n") Chris@909: end Chris@909: quotes << ("\n" + '' * indent + "\n\n") Chris@909: quotes Chris@909: end Chris@909: end Chris@909: Chris@909: CODE_RE = /(\W) Chris@909: @ Chris@909: (?:\|(\w+?)\|)? Chris@909: (.+?) Chris@909: @ Chris@909: (?=\W)/x Chris@909: Chris@909: def inline_textile_code( text ) Chris@909: text.gsub!( CODE_RE ) do |m| Chris@909: before,lang,code,after = $~[1..4] Chris@909: lang = " lang=\"#{ lang }\"" if lang Chris@909: rip_offtags( "#{ before }#{ code }#{ after }", false ) Chris@909: end Chris@909: end Chris@909: Chris@909: def lT( text ) Chris@909: text =~ /\#$/ ? 'o' : 'u' Chris@909: end Chris@909: Chris@909: def hard_break( text ) Chris@909: text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks Chris@909: end Chris@909: Chris@909: BLOCKS_GROUP_RE = /\n{2,}(?! )/m Chris@909: Chris@909: def blocks( text, deep_code = false ) Chris@909: text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk| Chris@909: plain = blk !~ /\A[#*> ]/ Chris@909: Chris@909: # skip blocks that are complex HTML Chris@909: if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1 Chris@909: blk Chris@909: else Chris@909: # search for indentation levels Chris@909: blk.strip! Chris@909: if blk.empty? Chris@909: blk Chris@909: else Chris@909: code_blk = nil Chris@909: blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk| Chris@909: flush_left iblk Chris@909: blocks iblk, plain Chris@909: iblk.gsub( /^(\S)/, "\t\\1" ) Chris@909: if plain Chris@909: code_blk = iblk; "" Chris@909: else Chris@909: iblk Chris@909: end Chris@909: end Chris@909: Chris@909: block_applied = 0 Chris@909: @rules.each do |rule_name| Chris@909: block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) ) Chris@909: end Chris@909: if block_applied.zero? Chris@909: if deep_code Chris@909: blk = "\t
    #{ blk }
    " Chris@909: else Chris@909: blk = "\t

    #{ blk }

    " Chris@909: end Chris@909: end Chris@909: # hard_break blk Chris@909: blk + "\n#{ code_blk }" Chris@909: end Chris@909: end Chris@909: Chris@909: end.join( "\n\n" ) ) Chris@909: end Chris@909: Chris@909: def textile_bq( tag, atts, cite, content ) Chris@909: cite, cite_title = check_refs( cite ) Chris@909: cite = " cite=\"#{ cite }\"" if cite Chris@909: atts = shelve( atts ) if atts Chris@909: "\t\n\t\t#{ content }

    \n\t" Chris@909: end Chris@909: Chris@909: def textile_p( tag, atts, cite, content ) Chris@909: atts = shelve( atts ) if atts Chris@909: "\t<#{ tag }#{ atts }>#{ content }" Chris@909: end Chris@909: Chris@909: alias textile_h1 textile_p Chris@909: alias textile_h2 textile_p Chris@909: alias textile_h3 textile_p Chris@909: alias textile_h4 textile_p Chris@909: alias textile_h5 textile_p Chris@909: alias textile_h6 textile_p Chris@909: Chris@909: def textile_fn_( tag, num, atts, cite, content ) Chris@909: atts << " id=\"fn#{ num }\" class=\"footnote\"" Chris@909: content = "#{ num } #{ content }" Chris@909: atts = shelve( atts ) if atts Chris@909: "\t#{ content }

    " Chris@909: end Chris@909: Chris@909: BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m Chris@909: Chris@909: def block_textile_prefix( text ) Chris@909: if text =~ BLOCK_RE Chris@909: tag,tagpre,num,atts,cite,content = $~[1..6] Chris@909: atts = pba( atts ) Chris@909: Chris@909: # pass to prefix handler Chris@909: replacement = nil Chris@909: if respond_to? "textile_#{ tag }", true Chris@909: replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content ) Chris@909: elsif respond_to? "textile_#{ tagpre }_", true Chris@909: replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) Chris@909: end Chris@909: text.gsub!( $& ) { replacement } if replacement Chris@909: end Chris@909: end Chris@909: Chris@909: SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m Chris@909: def block_markdown_setext( text ) Chris@909: if text =~ SETEXT_RE Chris@909: tag = if $2 == "="; "h1"; else; "h2"; end Chris@909: blk, cont = "<#{ tag }>#{ $1 }", $' Chris@909: blocks cont Chris@909: text.replace( blk + cont ) Chris@909: end Chris@909: end Chris@909: Chris@909: ATX_RE = /\A(\#{1,6}) # $1 = string of #'s Chris@909: [ ]* Chris@909: (.+?) # $2 = Header text Chris@909: [ ]* Chris@909: \#* # optional closing #'s (not counted) Chris@909: $/x Chris@909: def block_markdown_atx( text ) Chris@909: if text =~ ATX_RE Chris@909: tag = "h#{ $1.length }" Chris@909: blk, cont = "<#{ tag }>#{ $2 }\n\n", $' Chris@909: blocks cont Chris@909: text.replace( blk + cont ) Chris@909: end Chris@909: end Chris@909: Chris@909: MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m Chris@909: Chris@909: def block_markdown_bq( text ) Chris@909: text.gsub!( MARKDOWN_BQ_RE ) do |blk| Chris@909: blk.gsub!( /^ *> ?/, '' ) Chris@909: flush_left blk Chris@909: blocks blk Chris@909: blk.gsub!( /^(\S)/, "\t\\1" ) Chris@909: "
    \n#{ blk }\n
    \n\n" Chris@909: end Chris@909: end Chris@909: Chris@909: MARKDOWN_RULE_RE = /^(#{ Chris@909: ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) Chris@909: })$/ Chris@909: Chris@909: def block_markdown_rule( text ) Chris@909: text.gsub!( MARKDOWN_RULE_RE ) do |blk| Chris@909: "
    " Chris@909: end Chris@909: end Chris@909: Chris@909: # XXX TODO XXX Chris@909: def block_markdown_lists( text ) Chris@909: end Chris@909: Chris@909: def inline_textile_span( text ) Chris@909: QTAGS.each do |qtag_rc, ht, qtag_re, rtype| Chris@909: text.gsub!( qtag_re ) do |m| Chris@909: Chris@909: case rtype Chris@909: when :limit Chris@909: sta,oqs,qtag,content,oqa = $~[1..6] Chris@909: atts = nil Chris@909: if content =~ /^(#{C})(.+)$/ Chris@909: atts, content = $~[1..2] Chris@909: end Chris@909: else Chris@909: qtag,atts,cite,content = $~[1..4] Chris@909: sta = '' Chris@909: end Chris@909: atts = pba( atts ) Chris@909: atts = shelve( atts ) if atts Chris@909: Chris@909: "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }#{ oqa }" Chris@909: Chris@909: end Chris@909: end Chris@909: end Chris@909: Chris@909: LINK_RE = / Chris@909: ( Chris@909: ([\s\[{(]|[#{PUNCT}])? # $pre Chris@909: " # start Chris@909: (#{C}) # $atts Chris@909: ([^"\n]+?) # $text Chris@909: \s? Chris@909: (?:\(([^)]+?)\)(?="))? # $title Chris@909: ": Chris@909: ( # $url Chris@909: (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto Chris@909: [\w\/]\S+? Chris@909: ) Chris@909: (\/)? # $slash Chris@909: ([^\w\=\/;\(\)]*?) # $post Chris@909: ) Chris@909: (?=<|\s|$) Chris@909: /x Chris@909: #" Chris@909: def inline_textile_link( text ) Chris@909: text.gsub!( LINK_RE ) do |m| Chris@909: all,pre,atts,text,title,url,proto,slash,post = $~[1..9] Chris@909: if text.include?('
    ') Chris@909: all Chris@909: else Chris@909: url, url_title = check_refs( url ) Chris@909: title ||= url_title Chris@909: Chris@909: # Idea below : an URL with unbalanced parethesis and Chris@909: # ending by ')' is put into external parenthesis Chris@909: if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) Chris@909: url=url[0..-2] # discard closing parenth from url Chris@909: post = ")"+post # add closing parenth to post Chris@909: end Chris@909: atts = pba( atts ) Chris@909: atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }" Chris@909: atts << " title=\"#{ htmlesc title }\"" if title Chris@909: atts = shelve( atts ) if atts Chris@909: Chris@909: external = (url =~ /^https?:\/\//) ? ' class="external"' : '' Chris@909: Chris@909: "#{ pre }#{ text }#{ post }" Chris@909: end Chris@909: end Chris@909: end Chris@909: Chris@909: MARKDOWN_REFLINK_RE = / Chris@909: \[([^\[\]]+)\] # $text Chris@909: [ ]? # opt. space Chris@909: (?:\n[ ]*)? # one optional newline followed by spaces Chris@909: \[(.*?)\] # $id Chris@909: /x Chris@909: Chris@909: def inline_markdown_reflink( text ) Chris@909: text.gsub!( MARKDOWN_REFLINK_RE ) do |m| Chris@909: text, id = $~[1..2] Chris@909: Chris@909: if id.empty? Chris@909: url, title = check_refs( text ) Chris@909: else Chris@909: url, title = check_refs( id ) Chris@909: end Chris@909: Chris@909: atts = " href=\"#{ url }\"" Chris@909: atts << " title=\"#{ title }\"" if title Chris@909: atts = shelve( atts ) Chris@909: Chris@909: "#{ text }" Chris@909: end Chris@909: end Chris@909: Chris@909: MARKDOWN_LINK_RE = / Chris@909: \[([^\[\]]+)\] # $text Chris@909: \( # open paren Chris@909: [ \t]* # opt space Chris@909: ? # $href Chris@909: [ \t]* # opt space Chris@909: (?: # whole title Chris@909: (['"]) # $quote Chris@909: (.*?) # $title Chris@909: \3 # matching quote Chris@909: )? # title is optional Chris@909: \) Chris@909: /x Chris@909: Chris@909: def inline_markdown_link( text ) Chris@909: text.gsub!( MARKDOWN_LINK_RE ) do |m| Chris@909: text, url, quote, title = $~[1..4] Chris@909: Chris@909: atts = " href=\"#{ url }\"" Chris@909: atts << " title=\"#{ title }\"" if title Chris@909: atts = shelve( atts ) Chris@909: Chris@909: "#{ text }" Chris@909: end Chris@909: end Chris@909: Chris@909: TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/ Chris@909: MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m Chris@909: Chris@909: def refs( text ) Chris@909: @rules.each do |rule_name| Chris@909: method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/ Chris@909: end Chris@909: end Chris@909: Chris@909: def refs_textile( text ) Chris@909: text.gsub!( TEXTILE_REFS_RE ) do |m| Chris@909: flag, url = $~[2..3] Chris@909: @urlrefs[flag.downcase] = [url, nil] Chris@909: nil Chris@909: end Chris@909: end Chris@909: Chris@909: def refs_markdown( text ) Chris@909: text.gsub!( MARKDOWN_REFS_RE ) do |m| Chris@909: flag, url = $~[2..3] Chris@909: title = $~[6] Chris@909: @urlrefs[flag.downcase] = [url, title] Chris@909: nil Chris@909: end Chris@909: end Chris@909: Chris@909: def check_refs( text ) Chris@909: ret = @urlrefs[text.downcase] if text Chris@909: ret || [text, nil] Chris@909: end Chris@909: Chris@909: IMAGE_RE = / Chris@909: (>|\s|^) # start of line? Chris@909: \! # opening Chris@909: (\<|\=|\>)? # optional alignment atts Chris@909: (#{C}) # optional style,class atts Chris@909: (?:\. )? # optional dot-space Chris@909: ([^\s(!]+?) # presume this is the src Chris@909: \s? # optional space Chris@909: (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title Chris@909: \! # closing Chris@909: (?::#{ HYPERLINK })? # optional href Chris@909: /x Chris@909: Chris@909: def inline_textile_image( text ) Chris@909: text.gsub!( IMAGE_RE ) do |m| Chris@909: stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8] Chris@909: htmlesc title Chris@909: atts = pba( atts ) Chris@909: atts = " src=\"#{ htmlesc url.dup }\"#{ atts }" Chris@909: atts << " title=\"#{ title }\"" if title Chris@909: atts << " alt=\"#{ title }\"" Chris@909: # size = @getimagesize($url); Chris@909: # if($size) $atts.= " $size[3]"; Chris@909: Chris@909: href, alt_title = check_refs( href ) if href Chris@909: url, url_title = check_refs( url ) Chris@909: Chris@909: out = '' Chris@909: out << "" if href Chris@909: out << "" Chris@909: out << "#{ href_a1 }#{ href_a2 }" if href Chris@909: Chris@909: if algn Chris@909: algn = h_align( algn ) Chris@909: if stln == "

    " Chris@909: out = "

    #{ out }" Chris@909: else Chris@909: out = "#{ stln }

    #{ out }
    " Chris@909: end Chris@909: else Chris@909: out = stln + out Chris@909: end Chris@909: Chris@909: out Chris@909: end Chris@909: end Chris@909: Chris@909: def shelve( val ) Chris@909: @shelf << val Chris@909: " :redsh##{ @shelf.length }:" Chris@909: end Chris@909: Chris@909: def retrieve( text ) Chris@909: @shelf.each_with_index do |r, i| Chris@909: text.gsub!( " :redsh##{ i + 1 }:", r ) Chris@909: end Chris@909: end Chris@909: Chris@909: def incoming_entities( text ) Chris@909: ## turn any incoming ampersands into a dummy character for now. Chris@909: ## This uses a negative lookahead for alphanumerics followed by a semicolon, Chris@909: ## implying an incoming html entity, to be skipped Chris@909: Chris@909: text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) Chris@909: end Chris@909: Chris@909: def no_textile( text ) Chris@909: text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/, Chris@909: '\1\2\3' ) Chris@909: text.gsub!( /^ *==([^=]+.*?)==/m, Chris@909: '\1\2\3' ) Chris@909: end Chris@909: Chris@909: def clean_white_space( text ) Chris@909: # normalize line breaks Chris@909: text.gsub!( /\r\n/, "\n" ) Chris@909: text.gsub!( /\r/, "\n" ) Chris@909: text.gsub!( /\t/, ' ' ) Chris@909: text.gsub!( /^ +$/, '' ) Chris@909: text.gsub!( /\n{3,}/, "\n\n" ) Chris@909: text.gsub!( /"$/, "\" " ) Chris@909: Chris@909: # if entire document is indented, flush Chris@909: # to the left side Chris@909: flush_left text Chris@909: end Chris@909: Chris@909: def flush_left( text ) Chris@909: indt = 0 Chris@909: if text =~ /^ / Chris@909: while text !~ /^ {#{indt}}\S/ Chris@909: indt += 1 Chris@909: end unless text.empty? Chris@909: if indt.nonzero? Chris@909: text.gsub!( /^ {#{indt}}/, '' ) Chris@909: end Chris@909: end Chris@909: end Chris@909: Chris@909: def footnote_ref( text ) Chris@909: text.gsub!( /\b\[([0-9]+?)\](\s)?/, Chris@909: '\1\2' ) Chris@909: end Chris@909: Chris@909: OFFTAGS = /(code|pre|kbd|notextile)/ Chris@909: OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi Chris@909: OFFTAG_OPEN = /<#{ OFFTAGS }/ Chris@909: OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ Chris@909: HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m Chris@909: ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m Chris@909: Chris@909: def glyphs_textile( text, level = 0 ) Chris@909: if text !~ HASTAG_MATCH Chris@909: pgl text Chris@909: footnote_ref text Chris@909: else Chris@909: codepre = 0 Chris@909: text.gsub!( ALLTAG_MATCH ) do |line| Chris@909: ## matches are off if we're between ,
     etc.
    Chris@909:                 if $1
    Chris@909:                     if line =~ OFFTAG_OPEN
    Chris@909:                         codepre += 1
    Chris@909:                     elsif line =~ OFFTAG_CLOSE
    Chris@909:                         codepre -= 1
    Chris@909:                         codepre = 0 if codepre < 0
    Chris@909:                     end 
    Chris@909:                 elsif codepre.zero?
    Chris@909:                     glyphs_textile( line, level + 1 )
    Chris@909:                 else
    Chris@909:                     htmlesc( line, :NoQuotes )
    Chris@909:                 end
    Chris@909:                 # p [level, codepre, line]
    Chris@909: 
    Chris@909:                 line
    Chris@909:             end
    Chris@909:         end
    Chris@909:     end
    Chris@909: 
    Chris@909:     def rip_offtags( text, escape_aftertag=true, escape_line=true )
    Chris@909:         if text =~ /<.*>/
    Chris@909:             ## strip and encode 
     content
    Chris@909:             codepre, used_offtags = 0, {}
    Chris@909:             text.gsub!( OFFTAG_MATCH ) do |line|
    Chris@909:                 if $3
    Chris@909:                     first, offtag, aftertag = $3, $4, $5
    Chris@909:                     codepre += 1
    Chris@909:                     used_offtags[offtag] = true
    Chris@909:                     if codepre - used_offtags.length > 0
    Chris@909:                         htmlesc( line, :NoQuotes ) if escape_line
    Chris@909:                         @pre_list.last << line
    Chris@909:                         line = ""
    Chris@909:                     else
    Chris@909:                         ### htmlesc is disabled between CODE tags which will be parsed with highlighter
    Chris@909:                         ### Regexp in formatter.rb is : /\s?(.+)/m
    Chris@909:                         ### NB: some changes were made not to use $N variables, because we use "match"
    Chris@909:                         ###   and it breaks following lines
    Chris@909:                         htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(//)
    Chris@909:                         line = ""
    Chris@909:                         first.match(/<#{ OFFTAGS }([^>]*)>/)
    Chris@909:                         tag = $1
    Chris@909:                         $2.to_s.match(/(class\=("[^"]+"|'[^']+'))/i)
    Chris@909:                         tag << " #{$1}" if $1
    Chris@909:                         @pre_list << "<#{ tag }>#{ aftertag }"
    Chris@909:                     end
    Chris@909:                 elsif $1 and codepre > 0
    Chris@909:                     if codepre - used_offtags.length > 0
    Chris@909:                         htmlesc( line, :NoQuotes ) if escape_line
    Chris@909:                         @pre_list.last << line
    Chris@909:                         line = ""
    Chris@909:                     end
    Chris@909:                     codepre -= 1 unless codepre.zero?
    Chris@909:                     used_offtags = {} if codepre.zero?
    Chris@909:                 end 
    Chris@909:                 line
    Chris@909:             end
    Chris@909:         end
    Chris@909:         text
    Chris@909:     end
    Chris@909: 
    Chris@909:     def smooth_offtags( text )
    Chris@909:         unless @pre_list.empty?
    Chris@909:             ## replace 
     content
    Chris@909:             text.gsub!( // ) { @pre_list[$1.to_i] }
    Chris@909:         end
    Chris@909:     end
    Chris@909: 
    Chris@909:     def inline( text ) 
    Chris@909:         [/^inline_/, /^glyphs_/].each do |meth_re|
    Chris@909:             @rules.each do |rule_name|
    Chris@909:                 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
    Chris@909:             end
    Chris@909:         end
    Chris@909:     end
    Chris@909: 
    Chris@909:     def h_align( text ) 
    Chris@909:         H_ALGN_VALS[text]
    Chris@909:     end
    Chris@909: 
    Chris@909:     def v_align( text ) 
    Chris@909:         V_ALGN_VALS[text]
    Chris@909:     end
    Chris@909: 
    Chris@909:     def textile_popup_help( name, windowW, windowH )
    Chris@909:         ' ' + name + '
    ' Chris@909: end Chris@909: Chris@909: # HTML cleansing stuff Chris@909: BASIC_TAGS = { Chris@909: 'a' => ['href', 'title'], Chris@909: 'img' => ['src', 'alt', 'title'], Chris@909: 'br' => [], Chris@909: 'i' => nil, Chris@909: 'u' => nil, Chris@909: 'b' => nil, Chris@909: 'pre' => nil, Chris@909: 'kbd' => nil, Chris@909: 'code' => ['lang'], Chris@909: 'cite' => nil, Chris@909: 'strong' => nil, Chris@909: 'em' => nil, Chris@909: 'ins' => nil, Chris@909: 'sup' => nil, Chris@909: 'sub' => nil, Chris@909: 'del' => nil, Chris@909: 'table' => nil, Chris@909: 'tr' => nil, Chris@909: 'td' => ['colspan', 'rowspan'], Chris@909: 'th' => nil, Chris@909: 'ol' => nil, Chris@909: 'ul' => nil, Chris@909: 'li' => nil, Chris@909: 'p' => nil, Chris@909: 'h1' => nil, Chris@909: 'h2' => nil, Chris@909: 'h3' => nil, Chris@909: 'h4' => nil, Chris@909: 'h5' => nil, Chris@909: 'h6' => nil, Chris@909: 'blockquote' => ['cite'] Chris@909: } Chris@909: Chris@909: def clean_html( text, tags = BASIC_TAGS ) Chris@909: text.gsub!( /]*)>/ ) do Chris@909: raw = $~ Chris@909: tag = raw[2].downcase Chris@909: if tags.has_key? tag Chris@909: pcs = [tag] Chris@909: tags[tag].each do |prop| Chris@909: ['"', "'", ''].each do |q| Chris@909: q2 = ( q != '' ? q : '\s' ) Chris@909: if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i Chris@909: attrv = $1 Chris@909: next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} Chris@909: pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" Chris@909: break Chris@909: end Chris@909: end Chris@909: end if tags[tag] Chris@909: "<#{raw[1]}#{pcs.join " "}>" Chris@909: else Chris@909: " " Chris@909: end Chris@909: end Chris@909: end Chris@909: Chris@909: ALLOWED_TAGS = %w(redpre pre code notextile) Chris@909: Chris@909: def escape_html_tags(text) Chris@909: text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" } Chris@909: end Chris@909: end Chris@909: