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

A <b>bold</b> man

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

    #{ blk }

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

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

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

    " Chris@0: out = "

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

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