2 # = RedCloth - Textile and Markdown Hybrid for Ruby
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.)
9 # (see http://hobix.com/textile/ for a Textile Reference.)
11 # Based on (and also inspired by) both:
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14 # Textism for PHP: http://www.textism.com/tools/textile/
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.
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.
29 # Textile is a simple formatting style for text
30 # documents, loosely based on some HTML conventions.
32 # == Sample Textile Text
36 # h3. This is a subhead
38 # This is a bit of paragraph.
40 # bq. This is a blockquote.
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.
48 # h[n]. Header of size [n].
53 # == Quick Phrase Modifiers
55 # Quick phrase modifiers are also included, to allow formatting
56 # of small portions of text within a paragraph.
70 # ==notextile== (leave text alone)
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.
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.
83 # "This is a link (This is a title) ":http://www.textism.com
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
91 # To insert an image, put the URL for the image inside exclamation marks.
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
98 # Optional: place a colon followed by a URL immediately after the
99 # closing ! to make the image into a link.
103 # !http://www.textism.com/common/textist.gif(Textist)!
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
111 # !/common/textist.gif(Textist)!:http://textism.com
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
117 # == Defining Acronyms
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.
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
124 # immediately following the acronym.
128 # ACLU(American Civil Liberties Union)
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
136 # In Textile, simple tables can be added by seperating each column by
139 # |a|simple|table|row|
140 # |And|Another|table|row|
142 # Attributes are defined by style definitions in parentheses.
144 # table(border:1px solid black).
145 # (background:#ddd;color:red). |{}| | | |
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.
153 # doc = RedCloth.new "
157 # Just a simple test."
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:
165 # class RedCloth::Textile.new( str )
167 class RedCloth3 < String
170 DEFAULT_RULES = [:textile, :markdown]
173 # Two accessor for setting security restrictions.
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.
179 # If +:filter_html+ is set, HTML which wasn't
180 # created by the Textile processor will be escaped.
182 # If +:filter_styles+ is set, it will also disable
183 # the style markup specifier. ('{color: red}')
185 attr_accessor :filter_html, :filter_styles
188 # Accessor for toggling hard breaks.
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.
194 attr_accessor :hard_breaks
196 # Accessor for toggling lite mode.
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.
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
204 # #=> "And then? She <strong>fell</strong>!"
206 attr_accessor :lite_mode
209 # Accessor for toggling span caps.
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
216 attr_accessor :no_span_caps
219 # Establishes the markup predence. Available rules include:
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:
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)
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
247 # Returns a new RedCloth object, based on _string_ and
248 # enforcing all the included _restrictions_.
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
252 # #=>"<h1>A <b>bold</b> man</h1>"
254 def initialize( string, restrictions = [] )
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
260 # Generates HTML from the Textile contents.
262 # r = RedCloth.new( "And then? She *fell*!" )
264 # #=>"And then? She <strong>fell</strong>!"
266 def to_html( *rules )
267 rules = DEFAULT_RULES if rules.empty?
268 # make our working copy
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|
291 incoming_entities text
292 clean_white_space text
298 escape_html_tags text
299 # need to do this before #hard_break and #blocks
300 block_textile_quotes text unless @lite_mode
311 text.gsub!( /<\/?notextile>/, '' )
312 text.gsub!( /x%x%/, '&' )
313 clean_html text if filter_html
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
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]].
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
339 # Regular expressions to convert to HTML.
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
343 C_CLAS = '(?:\([^)]+\))'
344 C_LNGE = '(?:\[[^\[\]]+\])'
345 C_STYL = '(?:\{[^}]+\})'
346 S_CSPN = '(?:\\\\\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|<|$)'
357 # Text markup tags, don't conflict with block 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'
366 ['*', 'strong', :limit],
367 ['??', 'cite', :limit],
368 ['-', 'del', :limit],
371 ['%', 'span', :limit],
372 ['+', 'ins', :limit],
373 ['^', 'sup', :limit],
376 QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|')
378 QTAGS.collect! do |rc, ht, rtype|
379 rcq = Regexp::quote rc
385 (#{QTAGS_JOIN}|) # oqs
387 (\w|[^\s].*?[^\s]) # content
390 (#{QTAGS_JOIN}|) # oqa
391 (?=[[:punct:]]|<|\s|\)|$)/x
396 (\w|[^\s\-].*?[^\s\-])
404 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing
405 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing
406 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing
407 # [ /\'/, '‘' ], # single opening
408 # [ /</, '<' ], # less-than
409 # [ />/, '>' ], # greater-than
410 # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing
411 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing
412 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing
413 # [ /"/, '“' ], # double opening
414 # [ /\b( )?\.{3}/, '\1…' ], # 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—' ], # em dash
418 # [ /\s->\s/, ' → ' ], # right arrow
419 # [ /\s-\s/, ' – ' ], # en dash
420 # [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign
421 # [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark
422 # [ /\b ?[(\[]R[\])]/i, '®' ], # registered
423 # [ /\b ?[(\[]C[\])]/i, '©' ] # copyright
440 # Flexible HTML escaping
442 def htmlesc( str, mode=:Quotes )
444 str.gsub!( '&', '&' )
445 str.gsub!( '"', '"' ) if mode != :NoQuotes
446 str.gsub!( "'", ''' ) if mode == :Quotes
447 str.gsub!( '<', '<')
448 str.gsub!( '>', '>')
453 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
455 #GLYPHS.each do |re, resub, tog|
456 # next if tog and method( tog ).call
457 # text.gsub! re, resub
459 text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m|
460 "<acronym title=\"#{htmlesc $2}\">#{$1}</acronym>"
464 # Parses Textile attribute lists and builds an HTML attribute string
465 def pba( text_in, element = "" )
467 return '' unless text_in
472 colspan = $1 if text =~ /\\(\d+)/
473 rowspan = $1 if text =~ /\/(\d+)/
474 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
477 style << "#{ htmlesc $1 };" if text.sub!( /\{([^}]*)\}/, '' ) && !filter_styles
480 text.sub!( /\[([^)]+?)\]/, '' )
483 text.sub!( /\(([^()]+?)\)/, '' )
485 style << "padding-left:#{ $1.length }em;" if
486 text.sub!( /([(]+)/, '' )
488 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
490 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
492 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
495 atts << " style=\"#{ style.join }\"" unless style.empty?
496 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
497 atts << " lang=\"#{ lang }\"" if lang
498 atts << " id=\"#{ id }\"" if id
499 atts << " colspan=\"#{ colspan }\"" if colspan
500 atts << " rowspan=\"#{ rowspan }\"" if rowspan
505 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
507 # Parses a Textile table block, building HTML from the result.
508 def block_textile_table( text )
509 text.gsub!( TABLE_RE ) do |matches|
511 tatts, fullrow = $~[1..2]
512 tatts = pba( tatts, 'table' )
513 tatts = shelve( tatts ) if tatts
516 fullrow.each_line do |row|
517 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
519 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
522 ctyp = 'h' if cell =~ /^_/
525 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
527 catts = shelve( catts ) if catts
528 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
530 ratts = shelve( ratts ) if ratts
531 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
533 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
537 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
538 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
540 # Parses Textile lists and generates HTML
541 def block_textile_lists( text )
542 text.gsub!( LISTS_RE ) do |match|
543 lines = match.split( /\n/ )
546 lines.each_with_index do |line, line_id|
547 if line =~ LISTS_CONTENT_RE
548 tl,atts,content = $~[1..3]
550 if depth.last.length > tl.length
551 (depth.length - 1).downto(0) do |i|
552 break if depth[i].length == tl.length
553 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
557 if depth.last and depth.last.length == tl.length
558 lines[line_id - 1] << '</li>'
561 unless depth.last == tl
564 atts = shelve( atts ) if atts
565 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
567 lines[line_id] = "\t\t<li>#{ content }"
574 if line_id - last_line > 1 or line_id == lines.length - 1
575 depth.delete_if do |v|
576 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
584 QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m
585 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
587 def block_textile_quotes( text )
588 text.gsub!( QUOTES_RE ) do |match|
589 lines = match.split( /\n/ )
593 line =~ QUOTES_CONTENT_RE
597 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
600 quotes << (content + "\n")
602 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
614 def inline_textile_code( text )
615 text.gsub!( CODE_RE ) do |m|
616 before,lang,code,after = $~[1..4]
617 lang = " lang=\"#{ lang }\"" if lang
618 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }", false )
623 text =~ /\#$/ ? 'o' : 'u'
626 def hard_break( text )
627 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
630 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
632 def blocks( text, deep_code = false )
633 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
634 plain = blk !~ /\A[#*> ]/
636 # skip blocks that are complex HTML
637 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
640 # search for indentation levels
646 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
649 iblk.gsub( /^(\S)/, "\t\\1" )
658 @rules.each do |rule_name|
659 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
661 if block_applied.zero?
663 blk = "\t<pre><code>#{ blk }</code></pre>"
665 blk = "\t<p>#{ blk }</p>"
669 blk + "\n#{ code_blk }"
676 def textile_bq( tag, atts, cite, content )
677 cite, cite_title = check_refs( cite )
678 cite = " cite=\"#{ cite }\"" if cite
679 atts = shelve( atts ) if atts
680 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
683 def textile_p( tag, atts, cite, content )
684 atts = shelve( atts ) if atts
685 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
688 alias textile_h1 textile_p
689 alias textile_h2 textile_p
690 alias textile_h3 textile_p
691 alias textile_h4 textile_p
692 alias textile_h5 textile_p
693 alias textile_h6 textile_p
695 def textile_fn_( tag, num, atts, cite, content )
696 atts << " id=\"fn#{ num }\" class=\"footnote\""
697 content = "<sup>#{ num }</sup> #{ content }"
698 atts = shelve( atts ) if atts
699 "\t<p#{ atts }>#{ content }</p>"
702 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
704 def block_textile_prefix( text )
706 tag,tagpre,num,atts,cite,content = $~[1..6]
709 # pass to prefix handler
711 if respond_to? "textile_#{ tag }", true
712 replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content )
713 elsif respond_to? "textile_#{ tagpre }_", true
714 replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content )
716 text.gsub!( $& ) { replacement } if replacement
720 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
721 def block_markdown_setext( text )
723 tag = if $2 == "="; "h1"; else; "h2"; end
724 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
726 text.replace( blk + cont )
730 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
732 (.+?) # $2 = Header text
734 \#* # optional closing #'s (not counted)
736 def block_markdown_atx( text )
738 tag = "h#{ $1.length }"
739 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
741 text.replace( blk + cont )
745 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
747 def block_markdown_bq( text )
748 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
749 blk.gsub!( /^ *> ?/, '' )
752 blk.gsub!( /^(\S)/, "\t\\1" )
753 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
757 MARKDOWN_RULE_RE = /^(#{
758 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
761 def block_markdown_rule( text )
762 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
768 def block_markdown_lists( text )
771 def inline_textile_span( text )
772 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
773 text.gsub!( qtag_re ) do |m|
777 sta,oqs,qtag,content,oqa = $~[1..6]
779 if content =~ /^(#{C})(.+)$/
780 atts, content = $~[1..2]
783 qtag,atts,cite,content = $~[1..4]
787 atts = shelve( atts ) if atts
789 "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }</#{ ht }>#{ oqa }"
797 ([\s\[{(]|[#{PUNCT}])? # $pre
802 (?:\(([^)]+?)\)(?="))? # $title
805 (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto
809 ([^\w\=\/;\(\)]*?) # $post
814 def inline_textile_link( text )
815 text.gsub!( LINK_RE ) do |m|
816 all,pre,atts,text,title,url,proto,slash,post = $~[1..9]
817 if text.include?('<br />')
820 url, url_title = check_refs( url )
823 # Idea below : an URL with unbalanced parethesis and
824 # ending by ')' is put into external parenthesis
825 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
826 url=url[0..-2] # discard closing parenth from url
827 post = ")"+post # add closing parenth to post
830 atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }"
831 atts << " title=\"#{ htmlesc title }\"" if title
832 atts = shelve( atts ) if atts
834 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
836 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
841 MARKDOWN_REFLINK_RE = /
842 \[([^\[\]]+)\] # $text
844 (?:\n[ ]*)? # one optional newline followed by spaces
848 def inline_markdown_reflink( text )
849 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
853 url, title = check_refs( text )
855 url, title = check_refs( id )
858 atts = " href=\"#{ url }\""
859 atts << " title=\"#{ title }\"" if title
860 atts = shelve( atts )
862 "<a#{ atts }>#{ text }</a>"
867 \[([^\[\]]+)\] # $text
876 )? # title is optional
880 def inline_markdown_link( text )
881 text.gsub!( MARKDOWN_LINK_RE ) do |m|
882 text, url, quote, title = $~[1..4]
884 atts = " href=\"#{ url }\""
885 atts << " title=\"#{ title }\"" if title
886 atts = shelve( atts )
888 "<a#{ atts }>#{ text }</a>"
892 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
893 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
896 @rules.each do |rule_name|
897 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
901 def refs_textile( text )
902 text.gsub!( TEXTILE_REFS_RE ) do |m|
904 @urlrefs[flag.downcase] = [url, nil]
909 def refs_markdown( text )
910 text.gsub!( MARKDOWN_REFS_RE ) do |m|
913 @urlrefs[flag.downcase] = [url, title]
918 def check_refs( text )
919 ret = @urlrefs[text.downcase] if text
924 (>|\s|^) # start of line?
926 (\<|\=|\>)? # optional alignment atts
927 (#{C}) # optional style,class atts
928 (?:\. )? # optional dot-space
929 ([^\s(!]+?) # presume this is the src
931 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
933 (?::#{ HYPERLINK })? # optional href
936 def inline_textile_image( text )
937 text.gsub!( IMAGE_RE ) do |m|
938 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
941 atts = " src=\"#{ url }\"#{ atts }"
942 atts << " title=\"#{ title }\"" if title
943 atts << " alt=\"#{ title }\""
944 # size = @getimagesize($url);
945 # if($size) $atts.= " $size[3]";
947 href, alt_title = check_refs( href ) if href
948 url, url_title = check_refs( url )
951 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
952 out << "<img#{ shelve( atts ) } />"
953 out << "</a>#{ href_a1 }#{ href_a2 }" if href
956 algn = h_align( algn )
958 out = "<p style=\"float:#{ algn }\">#{ out }"
960 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
972 " :redsh##{ @shelf.length }:"
976 @shelf.each_with_index do |r, i|
977 text.gsub!( " :redsh##{ i + 1 }:", r )
981 def incoming_entities( text )
982 ## turn any incoming ampersands into a dummy character for now.
983 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
984 ## implying an incoming html entity, to be skipped
986 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
989 def no_textile( text )
990 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
991 '\1<notextile>\2</notextile>\3' )
992 text.gsub!( /^ *==([^=]+.*?)==/m,
993 '\1<notextile>\2</notextile>\3' )
996 def clean_white_space( text )
997 # normalize line breaks
998 text.gsub!( /\r\n/, "\n" )
999 text.gsub!( /\r/, "\n" )
1000 text.gsub!( /\t/, ' ' )
1001 text.gsub!( /^ +$/, '' )
1002 text.gsub!( /\n{3,}/, "\n\n" )
1003 text.gsub!( /"$/, "\" " )
1005 # if entire document is indented, flush
1010 def flush_left( text )
1013 while text !~ /^ {#{indt}}\S/
1015 end unless text.empty?
1017 text.gsub!( /^ {#{indt}}/, '' )
1022 def footnote_ref( text )
1023 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1024 '<sup><a href="#fn\1">\1</a></sup>\2' )
1027 OFFTAGS = /(code|pre|kbd|notextile)/
1028 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi
1029 OFFTAG_OPEN = /<#{ OFFTAGS }/
1030 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1031 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1032 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1034 def glyphs_textile( text, level = 0 )
1035 if text !~ HASTAG_MATCH
1040 text.gsub!( ALLTAG_MATCH ) do |line|
1041 ## matches are off if we're between <code>, <pre> etc.
1043 if line =~ OFFTAG_OPEN
1045 elsif line =~ OFFTAG_CLOSE
1047 codepre = 0 if codepre < 0
1050 glyphs_textile( line, level + 1 )
1052 htmlesc( line, :NoQuotes )
1054 # p [level, codepre, line]
1061 def rip_offtags( text, escape_aftertag=true )
1063 ## strip and encode <pre> content
1064 codepre, used_offtags = 0, {}
1065 text.gsub!( OFFTAG_MATCH ) do |line|
1067 first, offtag, aftertag = $3, $4, $5
1069 used_offtags[offtag] = true
1070 if codepre - used_offtags.length > 0
1071 htmlesc( line, :NoQuotes )
1072 @pre_list.last << line
1075 ### htmlesc is disabled between CODE tags which will be parsed with highlighter
1076 ### Regexp in formatter.rb is : /<code\s+class="(\w+)">\s?(.+)/m
1077 ### NB: some changes were made not to use $N variables, because we use "match"
1078 ### and it breaks following lines
1079 htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(/<code\s+class="(\w+)">/)
1080 line = "<redpre##{ @pre_list.length }>"
1081 first.match(/<#{ OFFTAGS }([^>]*)>/)
1083 $2.to_s.match(/(class\=("[^"]+"|'[^']+'))/i)
1084 tag << " #{$1}" if $1
1085 @pre_list << "<#{ tag }>#{ aftertag }"
1087 elsif $1 and codepre > 0
1088 if codepre - used_offtags.length > 0
1089 htmlesc( line, :NoQuotes )
1090 @pre_list.last << line
1093 codepre -= 1 unless codepre.zero?
1094 used_offtags = {} if codepre.zero?
1102 def smooth_offtags( text )
1103 unless @pre_list.empty?
1104 ## replace <pre> content
1105 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1110 [/^inline_/, /^glyphs_/].each do |meth_re|
1111 @rules.each do |rule_name|
1112 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1125 def textile_popup_help( name, windowW, windowH )
1126 ' <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 />'
1129 # HTML cleansing stuff
1131 'a' => ['href', 'title'],
1132 'img' => ['src', 'alt', 'title'],
1149 'td' => ['colspan', 'rowspan'],
1161 'blockquote' => ['cite']
1164 def clean_html( text, tags = BASIC_TAGS )
1165 text.gsub!( /<!\[CDATA\[/, '' )
1166 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1168 tag = raw[2].downcase
1169 if tags.has_key? tag
1171 tags[tag].each do |prop|
1172 ['"', "'", ''].each do |q|
1173 q2 = ( q != '' ? q : '\s' )
1174 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1176 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1177 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1182 "<#{raw[1]}#{pcs.join " "}>"
1189 ALLOWED_TAGS = %w(redpre pre code notextile)
1191 def escape_html_tags(text)
1192 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" }