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 RedCloth < 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
197 # Accessor for toggling span caps.
199 # Textile places `span' tags around capitalized
200 # words by default, but this wreaks havoc on Wikis.
201 # If +:no_span_caps+ is set, this will be
204 attr_accessor :no_span_caps
207 # Establishes the markup predence. Available rules include:
211 # The following textile rules can be set individually. Or add the complete
212 # set of rules with the single :textile rule, which supplies the rule set in
213 # the following precedence:
215 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
216 # block_textile_table:: Textile table block structures
217 # block_textile_lists:: Textile list structures
218 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
219 # inline_textile_image:: Textile inline images
220 # inline_textile_link:: Textile inline links
221 # inline_textile_span:: Textile inline spans
222 # inline_textile_glyphs:: Textile entities (such as em-dashes and smart quotes)
226 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
227 # block_markdown_setext:: Markdown setext headers
228 # block_markdown_atx:: Markdown atx headers
229 # block_markdown_rule:: Markdown horizontal rules
230 # block_markdown_bq:: Markdown blockquotes
231 # block_markdown_lists:: Markdown lists
232 # inline_markdown_link:: Markdown links
235 # Returns a new RedCloth object, based on _string_ and
236 # enforcing all the included _restrictions_.
238 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
240 # #=>"<h1>A <b>bold</b> man</h1>"
242 def initialize( string, restrictions = [] )
243 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
248 # Generates HTML from the Textile contents.
250 # r = RedCloth.new( "And then? She *fell*!" )
252 # #=>"And then? She <strong>fell</strong>!"
254 def to_html( *rules )
255 rules = DEFAULT_RULES if rules.empty?
256 # make our working copy
261 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
262 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
263 :inline_textile_code, :inline_textile_glyphs, :inline_textile_span]
264 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
265 :block_markdown_bq, :block_markdown_lists,
266 :inline_markdown_reflink, :inline_markdown_link]
267 @rules = rules.collect do |rule|
279 incoming_entities text
280 clean_white_space text
294 text.gsub!( /<\/?notextile>/, '' )
295 text.gsub!( /x%x%/, '&' )
296 clean_html text if filter_html
306 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
311 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
312 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
313 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
314 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
315 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
318 [a.chr, ( b.zero? and "" or "&#{ b };" )]
322 # Regular expressions to convert to HTML.
324 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
326 C_CLAS = '(?:\([^)]+\))'
327 C_LNGE = '(?:\[[^\]]+\])'
328 C_STYL = '(?:\{[^}]+\})'
329 S_CSPN = '(?:\\\\\d+)'
331 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
332 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
333 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
334 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
335 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
336 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
338 # Text markup tags, don't conflict with block tags
340 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
341 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
342 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
347 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing
348 [ /([^\s\[{(>])\'/, '\1’' ], # single closing
349 [ /\'(?=\s|s\b|[#{PUNCT}])/, '’' ], # single closing
350 [ /\'/, '‘' ], # single opening
351 # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing
352 [ /([^\s\[{(>])"/, '\1”' ], # double closing
353 [ /"(?=\s|[#{PUNCT}])/, '”' ], # double closing
354 [ /"/, '“' ], # double opening
355 [ /\b( )?\.{3}/, '\1…' ], # ellipsis
356 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
357 [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^<a-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
358 [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash
359 [ /\s->\s/, ' → ' ], # right arrow
360 [ /\s-\s/, ' – ' ], # en dash
361 [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign
362 [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark
363 [ /\b ?[(\[]R[\])]/i, '®' ], # registered
364 [ /\b ?[(\[]C[\])]/i, '©' ] # copyright
383 ['??', 'cite', :limit],
384 ['-', 'del', :limit],
387 ['%', 'span', :limit],
388 ['+', 'ins', :limit],
392 QTAGS.collect! do |rc, ht, rtype|
393 rcq = Regexp::quote rc
415 # Flexible HTML escaping
417 def htmlesc( str, mode )
418 str.gsub!( '&', '&' )
419 str.gsub!( '"', '"' ) if mode != :NoQuotes
420 str.gsub!( "'", ''' ) if mode == :Quotes
421 str.gsub!( '<', '<')
422 str.gsub!( '>', '>')
425 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
427 GLYPHS.each do |re, resub, tog|
428 next if tog and method( tog ).call
433 # Parses Textile attribute lists and builds an HTML attribute string
434 def pba( text_in, element = "" )
436 return '' unless text_in
441 colspan = $1 if text =~ /\\(\d+)/
442 rowspan = $1 if text =~ /\/(\d+)/
443 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
446 style << "#{ $1 };" if not filter_styles and
447 text.sub!( /\{([^}]*)\}/, '' )
450 text.sub!( /\[([^)]+?)\]/, '' )
453 text.sub!( /\(([^()]+?)\)/, '' )
455 style << "padding-left:#{ $1.length }em;" if
456 text.sub!( /([(]+)/, '' )
458 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
460 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
462 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
465 atts << " style=\"#{ style.join }\"" unless style.empty?
466 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
467 atts << " lang=\"#{ lang }\"" if lang
468 atts << " id=\"#{ id }\"" if id
469 atts << " colspan=\"#{ colspan }\"" if colspan
470 atts << " rowspan=\"#{ rowspan }\"" if rowspan
475 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
477 # Parses a Textile table block, building HTML from the result.
478 def block_textile_table( text )
479 text.gsub!( TABLE_RE ) do |matches|
481 tatts, fullrow = $~[1..2]
482 tatts = pba( tatts, 'table' )
483 tatts = shelve( tatts ) if tatts
488 delete_if { |x| x.empty? }.
491 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
494 row.split( '|' ).each do |cell|
496 ctyp = 'h' if cell =~ /^_/
499 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
501 unless cell.strip.empty?
502 catts = shelve( catts ) if catts
503 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
506 ratts = shelve( ratts ) if ratts
507 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
509 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
513 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
514 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
516 # Parses Textile lists and generates HTML
517 def block_textile_lists( text )
518 text.gsub!( LISTS_RE ) do |match|
519 lines = match.split( /\n/ )
522 lines.each_with_index do |line, line_id|
523 if line =~ LISTS_CONTENT_RE
524 tl,atts,content = $~[1..3]
526 if depth.last.length > tl.length
527 (depth.length - 1).downto(0) do |i|
528 break if depth[i].length == tl.length
529 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
533 if depth.last.length == tl.length
534 lines[line_id - 1] << '</li>'
537 unless depth.last == tl
540 atts = shelve( atts ) if atts
541 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
543 lines[line_id] = "\t\t<li>#{ content }"
550 if line_id - last_line > 1 or line_id == lines.length - 1
551 depth.delete_if do |v|
552 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
567 def inline_textile_code( text )
568 text.gsub!( CODE_RE ) do |m|
569 before,lang,code,after = $~[1..4]
570 lang = " lang=\"#{ lang }\"" if lang
571 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
576 text =~ /\#$/ ? 'o' : 'u'
579 def hard_break( text )
580 text.gsub!( /(.)\n(?! *[#*\s|]|$)/, "\\1<br />" ) if hard_breaks
583 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
585 def blocks( text, deep_code = false )
586 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
587 plain = blk !~ /\A[#*> ]/
589 # skip blocks that are complex HTML
590 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
593 # search for indentation levels
599 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
602 iblk.gsub( /^(\S)/, "\t\\1" )
611 @rules.each do |rule_name|
612 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
614 if block_applied.zero?
616 blk = "\t<pre><code>#{ blk }</code></pre>"
618 blk = "\t<p>#{ blk }</p>"
622 blk + "\n#{ code_blk }"
629 def textile_bq( tag, atts, cite, content )
630 cite, cite_title = check_refs( cite )
631 cite = " cite=\"#{ cite }\"" if cite
632 atts = shelve( atts ) if atts
633 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
636 def textile_p( tag, atts, cite, content )
637 atts = shelve( atts ) if atts
638 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
641 alias textile_h1 textile_p
642 alias textile_h2 textile_p
643 alias textile_h3 textile_p
644 alias textile_h4 textile_p
645 alias textile_h5 textile_p
646 alias textile_h6 textile_p
648 def textile_fn_( tag, num, atts, cite, content )
649 atts << " id=\"fn#{ num }\""
650 content = "<sup>#{ num }</sup> #{ content }"
651 atts = shelve( atts ) if atts
652 "\t<p#{ atts }>#{ content }</p>"
655 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
657 def block_textile_prefix( text )
659 tag,tagpre,num,atts,cite,content = $~[1..6]
662 # pass to prefix handler
663 if respond_to? "textile_#{ tag }", true
664 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
665 elsif respond_to? "textile_#{ tagpre }_", true
666 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
671 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
672 def block_markdown_setext( text )
674 tag = if $2 == "="; "h1"; else; "h2"; end
675 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
677 text.replace( blk + cont )
681 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
683 (.+?) # $2 = Header text
685 \#* # optional closing #'s (not counted)
687 def block_markdown_atx( text )
689 tag = "h#{ $1.length }"
690 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
692 text.replace( blk + cont )
696 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
698 def block_markdown_bq( text )
699 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
700 blk.gsub!( /^ *> ?/, '' )
703 blk.gsub!( /^(\S)/, "\t\\1" )
704 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
708 MARKDOWN_RULE_RE = /^#{
709 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
712 def block_markdown_rule( text )
713 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
719 def block_markdown_lists( text )
722 def inline_markdown_link( text )
725 def inline_textile_span( text )
726 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
727 text.gsub!( qtag_re ) do |m|
731 sta,qtag,atts,cite,content = $~[1..5]
733 qtag,atts,cite,content = $~[1..4]
737 atts << " cite=\"#{ cite }\"" if cite
738 atts = shelve( atts ) if atts
740 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
747 ([\s\[{(]|[#{PUNCT}])? # $pre
752 (?:\(([^)]+?)\)(?="))? # $title
760 def inline_textile_link( text )
761 text.gsub!( LINK_RE ) do |m|
762 pre,atts,text,title,url,slash,post = $~[1..7]
764 url, url_title = check_refs( url )
768 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
769 atts << " title=\"#{ title }\"" if title
770 atts = shelve( atts ) if atts
772 "#{ pre }<a#{ atts }>#{ text }</a>#{ post }"
776 MARKDOWN_REFLINK_RE = /
777 \[([^\[\]]+)\] # $text
779 (?:\n[ ]*)? # one optional newline followed by spaces
783 def inline_markdown_reflink( text )
784 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
788 url, title = check_refs( text )
790 url, title = check_refs( id )
793 atts = " href=\"#{ url }\""
794 atts << " title=\"#{ title }\"" if title
795 atts = shelve( atts )
797 "<a#{ atts }>#{ text }</a>"
802 \[([^\[\]]+)\] # $text
811 )? # title is optional
815 def inline_markdown_link( text )
816 text.gsub!( MARKDOWN_LINK_RE ) do |m|
817 text, url, quote, title = $~[1..4]
819 atts = " href=\"#{ url }\""
820 atts << " title=\"#{ title }\"" if title
821 atts = shelve( atts )
823 "<a#{ atts }>#{ text }</a>"
827 TEXTILE_REFS_RE = /(^ *)\[([^\n]+?)\](#{HYPERLINK})(?=\s|$)/
828 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
831 @rules.each do |rule_name|
832 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
836 def refs_textile( text )
837 text.gsub!( TEXTILE_REFS_RE ) do |m|
839 @urlrefs[flag.downcase] = [url, nil]
844 def refs_markdown( text )
845 text.gsub!( MARKDOWN_REFS_RE ) do |m|
848 @urlrefs[flag.downcase] = [url, title]
853 def check_refs( text )
854 ret = @urlrefs[text.downcase] if text
859 (<p>|.|^) # start of line?
861 (\<|\=|\>)? # optional alignment atts
862 (#{C}) # optional style,class atts
863 (?:\. )? # optional dot-space
864 ([^\s(!]+?) # presume this is the src
866 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
868 (?::#{ HYPERLINK })? # optional href
871 def inline_textile_image( text )
872 text.gsub!( IMAGE_RE ) do |m|
873 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
875 atts = " src=\"#{ url }\"#{ atts }"
876 atts << " title=\"#{ title }\"" if title
877 atts << " alt=\"#{ title }\""
878 # size = @getimagesize($url);
879 # if($size) $atts.= " $size[3]";
881 href, alt_title = check_refs( href ) if href
882 url, url_title = check_refs( url )
885 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
886 out << "<img#{ shelve( atts ) } />"
887 out << "</a>#{ href_a1 }#{ href_a2 }" if href
890 algn = h_align( algn )
892 out = "<p style=\"float:#{ algn }\">#{ out }"
894 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
906 " <#{ @shelf.length }>"
910 @shelf.each_with_index do |r, i|
911 text.gsub!( " <#{ i + 1 }>", r )
915 def incoming_entities( text )
916 ## turn any incoming ampersands into a dummy character for now.
917 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
918 ## implying an incoming html entity, to be skipped
920 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
923 def no_textile( text )
924 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
925 '\1<notextile>\2</notextile>\3' )
926 text.gsub!( /^ *==([^=]+.*?)==/m,
927 '\1<notextile>\2</notextile>\3' )
930 def clean_white_space( text )
931 # normalize line breaks
932 text.gsub!( /\r\n/, "\n" )
933 text.gsub!( /\r/, "\n" )
934 text.gsub!( /\t/, ' ' )
935 text.gsub!( /^ +$/, '' )
936 text.gsub!( /\n{3,}/, "\n\n" )
937 text.gsub!( /"$/, "\" " )
939 # if entire document is indented, flush
944 def flush_left( text )
947 while text !~ /^ {#{indt}}\S/
949 end unless text.empty?
951 text.gsub!( /^ {#{indt}}/, '' )
956 def footnote_ref( text )
957 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
958 '<sup><a href="#fn\1">\1</a></sup>\2' )
961 OFFTAGS = /(code|pre|kbd|notextile)/
962 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
963 OFFTAG_OPEN = /<#{ OFFTAGS }/
964 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
965 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
966 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
968 def inline_textile_glyphs( text, level = 0 )
969 if text !~ HASTAG_MATCH
974 text.gsub!( ALLTAG_MATCH ) do |line|
975 ## matches are off if we're between <code>, <pre> etc.
977 if line =~ OFFTAG_OPEN
979 elsif line =~ OFFTAG_CLOSE
981 codepre = 0 if codepre < 0
984 inline_textile_glyphs( line, level + 1 )
986 htmlesc( line, :NoQuotes )
988 ## p [level, codepre, orig_line, line]
995 def rip_offtags( text )
997 ## strip and encode <pre> content
998 codepre, used_offtags = 0, {}
999 text.gsub!( OFFTAG_MATCH ) do |line|
1001 offtag, aftertag = $4, $5
1003 used_offtags[offtag] = true
1004 if codepre - used_offtags.length > 0
1005 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1006 @pre_list.last << line
1009 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1010 line = "<redpre##{ @pre_list.length }>"
1011 @pre_list << "#{ $3 }#{ aftertag }"
1013 elsif $1 and codepre > 0
1014 if codepre - used_offtags.length > 0
1015 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1016 @pre_list.last << line
1019 codepre -= 1 unless codepre.zero?
1020 used_offtags = {} if codepre.zero?
1028 def smooth_offtags( text )
1029 unless @pre_list.empty?
1030 ## replace <pre> content
1031 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1036 @rules.each do |rule_name|
1037 method( rule_name ).call( text ) if rule_name.to_s.match /^inline_/
1049 def textile_popup_help( name, windowW, windowH )
1050 ' <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 />'
1053 # HTML cleansing stuff
1055 'a' => ['href', 'title'],
1056 'img' => ['src', 'alt', 'title'],
1073 'td' => ['colspan', 'rowspan'],
1085 'blockquote' => ['cite']
1088 def clean_html( text, tags = BASIC_TAGS )
1089 text.gsub!( /<!\[CDATA\[/, '' )
1090 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1092 tag = raw[2].downcase
1093 if tags.has_key? tag
1095 tags[tag].each do |prop|
1096 ['"', "'", ''].each do |q|
1097 q2 = ( q != '' ? q : '\s' )
1098 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1100 next if prop == 'src' and attrv !~ /^http/
1101 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1106 "<#{raw[1]}#{pcs.join " "}>"