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
302 # need to do this before text is split by #blocks
303 block_textile_quotes text
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]{2,})\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
710 if respond_to? "textile_#{ tag }", true
711 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
712 elsif respond_to? "textile_#{ tagpre }_", true
713 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
718 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
719 def block_markdown_setext( text )
721 tag = if $2 == "="; "h1"; else; "h2"; end
722 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
724 text.replace( blk + cont )
728 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
730 (.+?) # $2 = Header text
732 \#* # optional closing #'s (not counted)
734 def block_markdown_atx( text )
736 tag = "h#{ $1.length }"
737 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
739 text.replace( blk + cont )
743 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
745 def block_markdown_bq( text )
746 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
747 blk.gsub!( /^ *> ?/, '' )
750 blk.gsub!( /^(\S)/, "\t\\1" )
751 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
755 MARKDOWN_RULE_RE = /^(#{
756 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
759 def block_markdown_rule( text )
760 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
766 def block_markdown_lists( text )
769 def inline_textile_span( text )
770 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
771 text.gsub!( qtag_re ) do |m|
775 sta,oqs,qtag,content,oqa = $~[1..6]
777 if content =~ /^(#{C})(.+)$/
778 atts, content = $~[1..2]
781 qtag,atts,cite,content = $~[1..4]
785 atts = shelve( atts ) if atts
787 "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }</#{ ht }>#{ oqa }"
795 ([\s\[{(]|[#{PUNCT}])? # $pre
800 (?:\(([^)]+?)\)(?="))? # $title
803 (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto
807 ([^\w\=\/;\(\)]*?) # $post
812 def inline_textile_link( text )
813 text.gsub!( LINK_RE ) do |m|
814 all,pre,atts,text,title,url,proto,slash,post = $~[1..9]
815 if text.include?('<br />')
818 url, url_title = check_refs( url )
821 # Idea below : an URL with unbalanced parethesis and
822 # ending by ')' is put into external parenthesis
823 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
824 url=url[0..-2] # discard closing parenth from url
825 post = ")"+post # add closing parenth to post
828 atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }"
829 atts << " title=\"#{ htmlesc title }\"" if title
830 atts = shelve( atts ) if atts
832 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
834 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
839 MARKDOWN_REFLINK_RE = /
840 \[([^\[\]]+)\] # $text
842 (?:\n[ ]*)? # one optional newline followed by spaces
846 def inline_markdown_reflink( text )
847 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
851 url, title = check_refs( text )
853 url, title = check_refs( id )
856 atts = " href=\"#{ url }\""
857 atts << " title=\"#{ title }\"" if title
858 atts = shelve( atts )
860 "<a#{ atts }>#{ text }</a>"
865 \[([^\[\]]+)\] # $text
874 )? # title is optional
878 def inline_markdown_link( text )
879 text.gsub!( MARKDOWN_LINK_RE ) do |m|
880 text, url, quote, title = $~[1..4]
882 atts = " href=\"#{ url }\""
883 atts << " title=\"#{ title }\"" if title
884 atts = shelve( atts )
886 "<a#{ atts }>#{ text }</a>"
890 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
891 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
894 @rules.each do |rule_name|
895 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
899 def refs_textile( text )
900 text.gsub!( TEXTILE_REFS_RE ) do |m|
902 @urlrefs[flag.downcase] = [url, nil]
907 def refs_markdown( text )
908 text.gsub!( MARKDOWN_REFS_RE ) do |m|
911 @urlrefs[flag.downcase] = [url, title]
916 def check_refs( text )
917 ret = @urlrefs[text.downcase] if text
922 (>|\s|^) # start of line?
924 (\<|\=|\>)? # optional alignment atts
925 (#{C}) # optional style,class atts
926 (?:\. )? # optional dot-space
927 ([^\s(!]+?) # presume this is the src
929 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
931 (?::#{ HYPERLINK })? # optional href
934 def inline_textile_image( text )
935 text.gsub!( IMAGE_RE ) do |m|
936 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
939 atts = " src=\"#{ url }\"#{ atts }"
940 atts << " title=\"#{ title }\"" if title
941 atts << " alt=\"#{ title }\""
942 # size = @getimagesize($url);
943 # if($size) $atts.= " $size[3]";
945 href, alt_title = check_refs( href ) if href
946 url, url_title = check_refs( url )
949 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
950 out << "<img#{ shelve( atts ) } />"
951 out << "</a>#{ href_a1 }#{ href_a2 }" if href
954 algn = h_align( algn )
956 out = "<p style=\"float:#{ algn }\">#{ out }"
958 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
970 " :redsh##{ @shelf.length }:"
974 @shelf.each_with_index do |r, i|
975 text.gsub!( " :redsh##{ i + 1 }:", r )
979 def incoming_entities( text )
980 ## turn any incoming ampersands into a dummy character for now.
981 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
982 ## implying an incoming html entity, to be skipped
984 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
987 def no_textile( text )
988 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
989 '\1<notextile>\2</notextile>\3' )
990 text.gsub!( /^ *==([^=]+.*?)==/m,
991 '\1<notextile>\2</notextile>\3' )
994 def clean_white_space( text )
995 # normalize line breaks
996 text.gsub!( /\r\n/, "\n" )
997 text.gsub!( /\r/, "\n" )
998 text.gsub!( /\t/, ' ' )
999 text.gsub!( /^ +$/, '' )
1000 text.gsub!( /\n{3,}/, "\n\n" )
1001 text.gsub!( /"$/, "\" " )
1003 # if entire document is indented, flush
1008 def flush_left( text )
1011 while text !~ /^ {#{indt}}\S/
1013 end unless text.empty?
1015 text.gsub!( /^ {#{indt}}/, '' )
1020 def footnote_ref( text )
1021 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1022 '<sup><a href="#fn\1">\1</a></sup>\2' )
1025 OFFTAGS = /(code|pre|kbd|notextile)/
1026 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi
1027 OFFTAG_OPEN = /<#{ OFFTAGS }/
1028 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1029 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1030 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1032 def glyphs_textile( text, level = 0 )
1033 if text !~ HASTAG_MATCH
1038 text.gsub!( ALLTAG_MATCH ) do |line|
1039 ## matches are off if we're between <code>, <pre> etc.
1041 if line =~ OFFTAG_OPEN
1043 elsif line =~ OFFTAG_CLOSE
1045 codepre = 0 if codepre < 0
1048 glyphs_textile( line, level + 1 )
1050 htmlesc( line, :NoQuotes )
1052 # p [level, codepre, line]
1059 def rip_offtags( text, escape_aftertag=true )
1061 ## strip and encode <pre> content
1062 codepre, used_offtags = 0, {}
1063 text.gsub!( OFFTAG_MATCH ) do |line|
1065 first, offtag, aftertag = $3, $4, $5
1067 used_offtags[offtag] = true
1068 if codepre - used_offtags.length > 0
1069 htmlesc( line, :NoQuotes )
1070 @pre_list.last << line
1073 ### htmlesc is disabled between CODE tags which will be parsed with highlighter
1074 ### Regexp in formatter.rb is : /<code\s+class="(\w+)">\s?(.+)/m
1075 ### NB: some changes were made not to use $N variables, because we use "match"
1076 ### and it breaks following lines
1077 htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(/<code\s+class="(\w+)">/)
1078 line = "<redpre##{ @pre_list.length }>"
1079 first.match(/<#{ OFFTAGS }([^>]*)>/)
1081 $2.to_s.match(/(class\=\S+)/i)
1082 tag << " #{$1}" if $1
1083 @pre_list << "<#{ tag }>#{ aftertag }"
1085 elsif $1 and codepre > 0
1086 if codepre - used_offtags.length > 0
1087 htmlesc( line, :NoQuotes )
1088 @pre_list.last << line
1091 codepre -= 1 unless codepre.zero?
1092 used_offtags = {} if codepre.zero?
1100 def smooth_offtags( text )
1101 unless @pre_list.empty?
1102 ## replace <pre> content
1103 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1108 [/^inline_/, /^glyphs_/].each do |meth_re|
1109 @rules.each do |rule_name|
1110 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1123 def textile_popup_help( name, windowW, windowH )
1124 ' <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 />'
1127 # HTML cleansing stuff
1129 'a' => ['href', 'title'],
1130 'img' => ['src', 'alt', 'title'],
1147 'td' => ['colspan', 'rowspan'],
1159 'blockquote' => ['cite']
1162 def clean_html( text, tags = BASIC_TAGS )
1163 text.gsub!( /<!\[CDATA\[/, '' )
1164 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1166 tag = raw[2].downcase
1167 if tags.has_key? tag
1169 tags[tag].each do |prop|
1170 ['"', "'", ''].each do |q|
1171 q2 = ( q != '' ? q : '\s' )
1172 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1174 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1175 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1180 "<#{raw[1]}#{pcs.join " "}>"
1187 ALLOWED_TAGS = %w(redpre pre code notextile)
1189 def escape_html_tags(text)
1190 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" }