RedCloth 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of RedCloth might be problematic. Click here for more details.

@@ -0,0 +1,3 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'redcloth'
3
+ puts RedCloth.new( ARGF.read ).to_html
@@ -0,0 +1,1018 @@
1
+ # vim:ts=4:sw=4:
2
+ # = RedCloth - Textile and Markdown Hybrid for Ruby
3
+ #
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.)
7
+ # License:: BSD
8
+ #
9
+ # (see http://hobix.com/textile/ for a Textile Reference.)
10
+ #
11
+ # Based on (and also inspired by) both:
12
+ #
13
+ # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14
+ # Textism for PHP: http://www.textism.com/tools/textile/
15
+ #
16
+ #
17
+ require 'uri'
18
+
19
+ # = RedCloth
20
+ #
21
+ # RedCloth is a Ruby library for converting Textile and/or Markdown
22
+ # into HTML. You can use either format, intermingled or separately.
23
+ # You can also extend RedCloth to honor your own custom text stylings.
24
+ #
25
+ # RedCloth users are encouraged to use Textile if they are generating
26
+ # HTML and to use Markdown if others will be viewing the plain text.
27
+ #
28
+ # == What is Textile?
29
+ #
30
+ # Textile is a simple formatting style for text
31
+ # documents, loosely based on some HTML conventions.
32
+ #
33
+ # == Sample Textile Text
34
+ #
35
+ # h2. This is a title
36
+ #
37
+ # h3. This is a subhead
38
+ #
39
+ # This is a bit of paragraph.
40
+ #
41
+ # bq. This is a blockquote.
42
+ #
43
+ # = Writing Textile
44
+ #
45
+ # A Textile document consists of paragraphs. Paragraphs
46
+ # can be specially formatted by adding a small instruction
47
+ # to the beginning of the paragraph.
48
+ #
49
+ # h[n]. Header of size [n].
50
+ # bq. Blockquote.
51
+ # # Numeric list.
52
+ # * Bulleted list.
53
+ #
54
+ # == Quick Phrase Modifiers
55
+ #
56
+ # Quick phrase modifiers are also included, to allow formatting
57
+ # of small portions of text within a paragraph.
58
+ #
59
+ # \_emphasis\_
60
+ # \_\_italicized\_\_
61
+ # \*strong\*
62
+ # \*\*bold\*\*
63
+ # ??citation??
64
+ # -deleted text-
65
+ # +inserted text+
66
+ # ^superscript^
67
+ # ~subscript~
68
+ # @code@
69
+ # %(classname)span%
70
+ #
71
+ # ==notextile== (leave text alone)
72
+ #
73
+ # == Links
74
+ #
75
+ # To make a hypertext link, put the link text in "quotation
76
+ # marks" followed immediately by a colon and the URL of the link.
77
+ #
78
+ # Optional: text in (parentheses) following the link text,
79
+ # but before the closing quotation mark, will become a Title
80
+ # attribute for the link, visible as a tool tip when a cursor is above it.
81
+ #
82
+ # Example:
83
+ #
84
+ # "This is a link (This is a title) ":http://www.textism.com
85
+ #
86
+ # Will become:
87
+ #
88
+ # <a href="http://www.textism.com" title="This is a title">This is a link</a>
89
+ #
90
+ # == Images
91
+ #
92
+ # To insert an image, put the URL for the image inside exclamation marks.
93
+ #
94
+ # Optional: text that immediately follows the URL in (parentheses) will
95
+ # be used as the Alt text for the image. Images on the web should always
96
+ # have descriptive Alt text for the benefit of readers using non-graphical
97
+ # browsers.
98
+ #
99
+ # Optional: place a colon followed by a URL immediately after the
100
+ # closing ! to make the image into a link.
101
+ #
102
+ # Example:
103
+ #
104
+ # !http://www.textism.com/common/textist.gif(Textist)!
105
+ #
106
+ # Will become:
107
+ #
108
+ # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
109
+ #
110
+ # With a link:
111
+ #
112
+ # !/common/textist.gif(Textist)!:http://textism.com
113
+ #
114
+ # Will become:
115
+ #
116
+ # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
117
+ #
118
+ # == Defining Acronyms
119
+ #
120
+ # HTML allows authors to define acronyms via the tag. The definition appears as a
121
+ # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
122
+ # this should be used at least once for each acronym in documents where they appear.
123
+ #
124
+ # To quickly define an acronym in Textile, place the full text in (parentheses)
125
+ # immediately following the acronym.
126
+ #
127
+ # Example:
128
+ #
129
+ # ACLU(American Civil Liberties Union)
130
+ #
131
+ # Will become:
132
+ #
133
+ # <acronym title="American Civil Liberties Union">ACLU</acronym>
134
+ #
135
+ # == Adding Tables
136
+ #
137
+ # In Textile, simple tables can be added by seperating each column by
138
+ # a pipe.
139
+ #
140
+ # |a|simple|table|row|
141
+ # |And|Another|table|row|
142
+ #
143
+ # Attributes are defined by style definitions in parentheses.
144
+ #
145
+ # table(border:1px solid black).
146
+ # (background:#ddd;color:red). |{}| | | |
147
+ #
148
+ # == Using RedCloth
149
+ #
150
+ # RedCloth is simply an extension of the String class, which can handle
151
+ # Textile formatting. Use it like a String and output HTML with its
152
+ # RedCloth#to_html method.
153
+ #
154
+ # doc = RedCloth.new "
155
+ #
156
+ # h2. Test document
157
+ #
158
+ # Just a simple test."
159
+ #
160
+ # puts doc.to_html
161
+ #
162
+ # By default, RedCloth uses both Textile and Markdown formatting, with
163
+ # Textile formatting taking precedence. If you want to turn off Markdown
164
+ # formatting, to boost speed and limit the processor:
165
+ #
166
+ # class RedCloth::Textile.new( str )
167
+
168
+ class RedCloth < String
169
+
170
+ VERSION = '3.0.0'
171
+
172
+ #
173
+ # Two accessor for setting security restrictions.
174
+ #
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.
178
+ #
179
+ # If +:filter_html+ is set, HTML which wasn't
180
+ # created by the Textile processor will be escaped.
181
+ #
182
+ # If +:filter_styles+ is set, it will also disable
183
+ # the style markup specifier. ('{color: red}')
184
+ #
185
+ attr_accessor :filter_html, :filter_styles
186
+
187
+ #
188
+ # Accessor for toggling hard breaks.
189
+ #
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.
193
+ #
194
+ attr_accessor :hard_breaks
195
+
196
+ #
197
+ # Establishes the markup predence. Available rules include:
198
+ #
199
+ # == Textile Rules
200
+ #
201
+ # The following textile rules can be set individually. Or add the complete
202
+ # set of rules with the single :textile rule, which supplies the rule set in
203
+ # the following precedence:
204
+ #
205
+ # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
206
+ # block_textile_table:: Textile table block structures
207
+ # block_textile_lists:: Textile list structures
208
+ # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
209
+ # inline_textile_image:: Textile inline images
210
+ # inline_textile_link:: Textile inline links
211
+ # inline_textile_span:: Textile inline spans
212
+ # inline_textile_glyphs:: Textile entities (such as em-dashes and smart quotes)
213
+ #
214
+ # == Markdown
215
+ #
216
+ # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
217
+ # block_markdown_setext:: Markdown setext headers
218
+ # block_markdown_atx:: Markdown atx headers
219
+ # block_markdown_rule:: Markdown horizontal rules
220
+ # block_markdown_bq:: Markdown blockquotes
221
+ # block_markdown_lists:: Markdown lists
222
+ # inline_markdown_link:: Markdown links
223
+ attr_accessor :rules
224
+
225
+ # Returns a new RedCloth object, based on _string_ and
226
+ # enforcing all the included _restrictions_.
227
+ #
228
+ # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
229
+ # r.to_html
230
+ # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
231
+ #
232
+ def initialize( string, restrictions = [] )
233
+ restrictions.each { |r| method( "#{ r }=" ).call( true ) }
234
+ @rules = [:textile, :markdown]
235
+ super( string )
236
+ end
237
+
238
+ #
239
+ # Generates HTML from the Textile contents.
240
+ #
241
+ # r = RedCloth.new( "And then? She *fell*!" )
242
+ # r.to_html( true )
243
+ # #=>"And then? She <strong>fell</strong>!"
244
+ #
245
+ def to_html( *rules )
246
+ rules = @rules if rules.empty?
247
+ # make our working copy
248
+ text = self.dup
249
+
250
+ @urlrefs = {}
251
+ @shelf = []
252
+ textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
253
+ :block_textile_prefix, :inline_textile_image, :inline_textile_link,
254
+ :inline_textile_code, :inline_textile_span, :inline_textile_glyphs]
255
+ markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
256
+ :block_markdown_bq, :block_markdown_lists,
257
+ :inline_markdown_reflink, :inline_markdown_link]
258
+ @rules = rules.collect do |rule|
259
+ case rule
260
+ when :markdown
261
+ markdown_rules
262
+ when :textile
263
+ textile_rules
264
+ else
265
+ rule
266
+ end
267
+ end.flatten
268
+
269
+ # standard clean up
270
+ incoming_entities text
271
+ clean_white_space text
272
+
273
+ # start processor
274
+ pre_list = rip_offtags text
275
+ refs text
276
+ blocks text
277
+ inline text
278
+ smooth_offtags text, pre_list
279
+
280
+ retrieve text
281
+
282
+ text.gsub!( /<\/?notextile>/, '' )
283
+ text.gsub!( /x%x%/, '&#38;' )
284
+ text.strip!
285
+ text
286
+
287
+ end
288
+
289
+ #######
290
+ private
291
+ #######
292
+ #
293
+ # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
294
+ # (from PyTextile)
295
+ #
296
+ TEXTILE_TAGS =
297
+
298
+ [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
299
+ [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
300
+ [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
301
+ [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
302
+ [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
303
+
304
+ collect! do |a, b|
305
+ [a.chr, ( b.zero? and "" or "&#{ b };" )]
306
+ end
307
+
308
+ #
309
+ # Regular expressions to convert to HTML.
310
+ #
311
+ A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
312
+ A_VLGN = /[\-^~]/
313
+ C_CLAS = '(?:\([^)]+\))'
314
+ C_LNGE = '(?:\[[^\]]+\])'
315
+ C_STYL = '(?:\{[^}]+\})'
316
+ S_CSPN = '(?:\\\\\d+)'
317
+ S_RSPN = '(?:/\d+)'
318
+ A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
319
+ S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
320
+ C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
321
+ # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
322
+ PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
323
+ HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
324
+
325
+ # Text markup tags, don't conflict with block tags
326
+ SIMPLE_HTML_TAGS = [
327
+ 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
328
+ 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
329
+ 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
330
+ ]
331
+
332
+ # Elements to handle
333
+ GLYPHS = [
334
+ # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
335
+ [ /([^\s\[{(>])\'/, '\1&#8217;' ], # single closing
336
+ [ /\'(?=\s|s\b|[#{PUNCT}])/, '&#8217;' ], # single closing
337
+ [ /\'/, '&#8216;' ], # single opening
338
+ # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
339
+ [ /([^\s\[{(>])"/, '\1&#8221;' ], # double closing
340
+ [ /"(?=\s|[#{PUNCT}])/, '&#8221;' ], # double closing
341
+ [ /"/, '&#8220;' ], # double opening
342
+ [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
343
+ [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
344
+ [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^<a-z0-9]|$)/, '\1<span class="caps">\2</span>\3' ], # 3+ uppercase caps
345
+ [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
346
+ [ /\s->\s/, ' &rarr; ' ], # en dash
347
+ [ /\s-\s/, ' &#8211; ' ], # en dash
348
+ [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
349
+ [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
350
+ [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
351
+ [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
352
+ ]
353
+
354
+ H_ALGN_VALS = {
355
+ '<' => 'left',
356
+ '=' => 'center',
357
+ '>' => 'right',
358
+ '<>' => 'justify'
359
+ }
360
+
361
+ V_ALGN_VALS = {
362
+ '^' => 'top',
363
+ '-' => 'middle',
364
+ '~' => 'bottom'
365
+ }
366
+
367
+ QTAGS = [
368
+ ['**', 'b'],
369
+ ['*', 'strong'],
370
+ ['??', 'cite'],
371
+ ['-', 'del'],
372
+ ['__', 'i'],
373
+ ['_', 'em'],
374
+ ['%', 'span'],
375
+ ['+', 'ins'],
376
+ ['^', 'sup'],
377
+ ['~', 'sub']
378
+ ].collect do |rc, ht|
379
+ ttr = Regexp.quote(rc)
380
+ punct = PUNCT.sub( Regexp::quote(rc), '' )
381
+ re = /(^|[\s\>#{punct}{(\[])
382
+ #{ttr}
383
+ (#{C})
384
+ (?::(\S+?))?
385
+ ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?)
386
+ ([#{punct}]*?)
387
+ #{ttr}
388
+ (?=[\s\])}<#{punct}]|$)/xm
389
+ [re, ht]
390
+ end
391
+
392
+ #
393
+ # Flexible HTML escaping
394
+ #
395
+ def htmlesc( str, mode )
396
+ str.gsub!( '&', '&amp;' )
397
+ str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
398
+ str.gsub!( "'", '&#039;' ) if mode == :Quotes
399
+ str.gsub!( '<', '&lt;')
400
+ str.gsub!( '>', '&gt;')
401
+ end
402
+
403
+ # Search and replace for Textile glyphs (quotes, dashes, other symbols)
404
+ def pgl( text )
405
+ GLYPHS.each do |re, resub|
406
+ text.gsub! re, resub
407
+ end
408
+ end
409
+
410
+ # Parses Textile attribute lists and builds an HTML attribute string
411
+ def pba( text_in, element = "" )
412
+
413
+ return '' unless text_in
414
+
415
+ style = []
416
+ text = text_in.dup
417
+ if element == 'td'
418
+ colspan = $1 if text =~ /\\(\d+)/
419
+ rowspan = $1 if text =~ /\/(\d+)/
420
+ style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
421
+ end
422
+
423
+ style << "#{ $1 };" if not @filter_styles and
424
+ text.sub!( /\{([^}]*)\}/, '' )
425
+
426
+ lang = $1 if
427
+ text.sub!( /\[([^)]+?)\]/, '' )
428
+
429
+ cls = $1 if
430
+ text.sub!( /\(([^()]+?)\)/, '' )
431
+
432
+ style << "padding-left:#{ $1.length }em;" if
433
+ text.sub!( /([(]+)/, '' )
434
+
435
+ style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
436
+
437
+ style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
438
+
439
+ cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
440
+
441
+ atts = ''
442
+ atts << " style=\"#{ style.join }\"" unless style.empty?
443
+ atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
444
+ atts << " lang=\"#{ lang }\"" if lang
445
+ atts << " id=\"#{ id }\"" if id
446
+ atts << " colspan=\"#{ colspan }\"" if colspan
447
+ atts << " rowspan=\"#{ rowspan }\"" if rowspan
448
+
449
+ atts
450
+ end
451
+
452
+ TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
453
+
454
+ # Parses a Textile table block, building HTML from the result.
455
+ def block_textile_table( text )
456
+ text.gsub!( TABLE_RE ) do |matches|
457
+
458
+ tatts, fullrow = $~[1..2]
459
+ tatts = pba( tatts, 'table' )
460
+ rows = []
461
+
462
+ fullrow.
463
+ split( /\|$/m ).
464
+ delete_if { |x| x.empty? }.
465
+ each do |row|
466
+
467
+ ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
468
+
469
+ cells = []
470
+ row.split( '|' ).each do |cell|
471
+ ctyp = 'd'
472
+ ctyp = 'h' if cell =~ /^_/
473
+
474
+ catts = ''
475
+ catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
476
+
477
+ unless cell.strip.empty?
478
+ cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
479
+ end
480
+ end
481
+ rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
482
+ end
483
+ "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
484
+ end
485
+ end
486
+
487
+ LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
488
+ LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
489
+
490
+ # Parses Textile lists and generates HTML
491
+ def block_textile_lists( text )
492
+ text.gsub!( LISTS_RE ) do |match|
493
+ lines = match.split( /\n/ )
494
+ last_line = -1
495
+ depth = []
496
+ lines.each_with_index do |line, line_id|
497
+ if line =~ LISTS_CONTENT_RE
498
+ tl,atts,content = $~[1..3]
499
+ if depth.last
500
+ if depth.last.length > tl.length
501
+ (depth.length - 1).downto(0) do |i|
502
+ break if depth[i].length == tl.length
503
+ lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
504
+ depth.pop
505
+ end
506
+ end
507
+ if depth.last.length == tl.length
508
+ lines[line_id - 1] << '</li>'
509
+ end
510
+ end
511
+ unless depth.last == tl
512
+ depth << tl
513
+ atts = pba( atts )
514
+ lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
515
+ else
516
+ lines[line_id] = "\t\t<li>#{ content }"
517
+ end
518
+ last_line = line_id
519
+
520
+ else
521
+ last_line = line_id
522
+ end
523
+ if line_id - last_line > 1 or line_id == lines.length - 1
524
+ depth.delete_if do |v|
525
+ lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
526
+ end
527
+ end
528
+ end
529
+ lines.join( "\n" )
530
+ end
531
+ end
532
+
533
+ CODE_RE = /
534
+ (^|[\s>#{PUNCT}{(\[]) # 1 open bracket?
535
+ @ # opening
536
+ (?:\|(\w+?)\|)? # 2 language
537
+ (\S(?:[^\n]|\n(?!\n))*?) # 3 code
538
+ @ # closing
539
+ (?=[\s\]}\)<#{PUNCT}]|$) # 4 closing bracket?
540
+ /x
541
+
542
+ def inline_textile_code( text )
543
+ text.gsub!( CODE_RE ) do |m|
544
+ before,lang,code,after = $~[1..4]
545
+ lang = " lang=\"#{ lang }\"" if lang
546
+ "#{ before }<code#{ lang }>#{ code }</code>#{ after }"
547
+ end
548
+ end
549
+
550
+ def lT( text )
551
+ text =~ /\#$/ ? 'o' : 'u'
552
+ end
553
+
554
+ def hard_break( text )
555
+ text.gsub!( /(.)\n(?! *[#*\s|])/, "\\1<br />" ) if @hard_breaks
556
+ end
557
+
558
+ BLOCKS_GROUP_RE = /(#{
559
+ ['#', '*', '>'].collect do |sym|
560
+ sym = Regexp::quote( sym )
561
+ '(?:\n*[' + sym + ' ](?:[^\n]|\n+[' + sym + ' ]|\n(?!\n|\Z))+)'
562
+ end.join '|'
563
+ })|((?:[^\n]+|\n+ +|\n(?![#*\n]|\Z))+)/m
564
+
565
+ def blocks( text, deep_code = false )
566
+ text.gsub!( BLOCKS_GROUP_RE ) do |blk|
567
+ plain = $2 ? true : false
568
+
569
+ # skip blocks that are complex HTML
570
+ if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
571
+ blk
572
+ else
573
+ # search for indentation levels
574
+ blk.strip!
575
+ if blk.empty?
576
+ blk
577
+ else
578
+ code_blk = nil
579
+ blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
580
+ flush_left iblk
581
+ blocks iblk, plain
582
+ iblk.gsub( /^(\S)/, "\t\\1" )
583
+ if plain
584
+ code_blk = iblk; ""
585
+ else
586
+ iblk
587
+ end
588
+ end
589
+
590
+ block_applied = nil
591
+ @rules.each do |rule_name|
592
+ break if block_applied = ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
593
+ end
594
+ unless block_applied
595
+ if deep_code
596
+ blk = "\t<pre><code>#{ blk }</code></pre>"
597
+ else
598
+ blk = "\t<p>#{ blk }</p>"
599
+ end
600
+ end
601
+ # hard_break blk
602
+ blk + "\n#{ code_blk }"
603
+ end
604
+ end
605
+
606
+ end
607
+ end
608
+
609
+ def textile_bq( tag, atts, cite, content )
610
+ cite, cite_title = check_refs( cite )
611
+ cite = " cite=\"#{ cite }\"" if cite
612
+ "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
613
+ end
614
+
615
+ def textile_p( tag, atts, cite, content )
616
+ "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
617
+ end
618
+
619
+ alias textile_h1 textile_p
620
+ alias textile_h2 textile_p
621
+ alias textile_h3 textile_p
622
+ alias textile_h4 textile_p
623
+ alias textile_h5 textile_p
624
+ alias textile_h6 textile_p
625
+
626
+ def textile_fn_( tag, num, atts, cite, content )
627
+ atts << " id=\"fn#{ num }\""
628
+ content = "<sup>#{ num }</sup> #{ content }"
629
+ "\t<p#{ atts }>#{ content }</p>"
630
+ end
631
+
632
+ BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
633
+
634
+ def block_textile_prefix( text )
635
+ if text =~ BLOCK_RE
636
+ tag,tagpre,num,atts,cite,content = $~[1..6]
637
+ atts = pba( atts )
638
+
639
+ # pass to prefix handler
640
+ if respond_to? "textile_#{ tag }", true
641
+ text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
642
+ elsif respond_to? "textile_#{ tagpre }_", true
643
+ text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
644
+ end
645
+ end
646
+ end
647
+
648
+ SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
649
+ def block_markdown_setext( text )
650
+ if text =~ SETEXT_RE
651
+ tag = if $2 == "="; "h1"; else; "h2"; end
652
+ blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
653
+ blocks cont
654
+ text.replace( blk + cont )
655
+ end
656
+ end
657
+
658
+ ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
659
+ [ ]*
660
+ (.+?) # $2 = Header text
661
+ [ ]*
662
+ \#* # optional closing #'s (not counted)
663
+ $/x
664
+ def block_markdown_atx( text )
665
+ if text =~ ATX_RE
666
+ tag = "h#{ $1.length }"
667
+ blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
668
+ blocks cont
669
+ text.replace( blk + cont )
670
+ end
671
+ end
672
+
673
+ MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
674
+
675
+ def block_markdown_bq( text )
676
+ text.gsub!( MARKDOWN_BQ_RE ) do |blk|
677
+ blk.gsub!( /^ *> ?/, '' )
678
+ flush_left blk
679
+ blocks blk
680
+ blk.gsub!( /^(\S)/, "\t\\1" )
681
+ "<blockquote>\n#{ blk }\n</blockquote>\n\n"
682
+ end
683
+ end
684
+
685
+ MARKDOWN_RULE_RE = /^#{
686
+ ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
687
+ }$/
688
+
689
+ def block_markdown_rule( text )
690
+ text.gsub!( MARKDOWN_RULE_RE ) do |blk|
691
+ "<hr />"
692
+ end
693
+ end
694
+
695
+ # XXX TODO XXX
696
+ def block_markdown_lists( text )
697
+ end
698
+
699
+ def inline_markdown_link( text )
700
+ end
701
+
702
+ def inline_textile_span( text )
703
+ QTAGS.each do |ttr, ht|
704
+ text.gsub!(ttr) do |m|
705
+
706
+ start,atts,cite,content,tend = $~[1..5]
707
+ atts = pba( atts )
708
+ atts << " cite=\"#{ cite }\"" if cite
709
+
710
+ "#{ start }<#{ ht }#{ atts }>#{ content }#{ tend }</#{ ht }>"
711
+
712
+ end
713
+ end
714
+ end
715
+
716
+ LINK_RE = /
717
+ ([\s\[{(]|[#{PUNCT}])? # $pre
718
+ " # start
719
+ (#{C}) # $atts
720
+ ([^"]+?) # $text
721
+ \s?
722
+ (?:\(([^)]+?)\)(?="))? # $title
723
+ ":
724
+ (\S+?) # $url
725
+ (\/)? # $slash
726
+ ([^\w\/;]*?) # $post
727
+ (?=<|\s|$)
728
+ /x
729
+
730
+ def inline_textile_link( text )
731
+ text.gsub!( LINK_RE ) do |m|
732
+ pre,atts,text,title,url,slash,post = $~[1..7]
733
+
734
+ url, url_title = check_refs( url )
735
+ title ||= url_title
736
+
737
+ atts = pba( atts )
738
+ atts = " href=\"#{ url }#{ slash }\"#{ atts }"
739
+ atts << " title=\"#{ title }\"" if title
740
+ atts = shelve( atts ) if atts
741
+
742
+ "#{ pre }<a#{ atts }>#{ text }</a>#{ post }"
743
+ end
744
+ end
745
+
746
+ MARKDOWN_REFLINK_RE = /
747
+ \[([^\[\]]+)\] # $text
748
+ [ ]? # opt. space
749
+ (?:\n[ ]*)? # one optional newline followed by spaces
750
+ \[(.*?)\] # $id
751
+ /x
752
+
753
+ def inline_markdown_reflink( text )
754
+ text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
755
+ text, id = $~[1..2]
756
+
757
+ if id.empty?
758
+ url, title = check_refs( text )
759
+ else
760
+ url, title = check_refs( id )
761
+ end
762
+
763
+ atts = " href=\"#{ url }\""
764
+ atts << " title=\"#{ title }\"" if title
765
+ atts = shelve( atts )
766
+
767
+ "<a#{ atts }>#{ text }</a>"
768
+ end
769
+ end
770
+
771
+ MARKDOWN_LINK_RE = /
772
+ \[([^\[\]]+)\] # $text
773
+ \( # open paren
774
+ [ \t]* # opt space
775
+ <?(.+?)>? # $href
776
+ [ \t]* # opt space
777
+ (?: # whole title
778
+ (['"]) # $quote
779
+ (.*?) # $title
780
+ \3 # matching quote
781
+ )? # title is optional
782
+ \)
783
+ /x
784
+
785
+ def inline_markdown_link( text )
786
+ text.gsub!( MARKDOWN_LINK_RE ) do |m|
787
+ text, url, quote, title = $~[1..4]
788
+
789
+ atts = " href=\"#{ url }\""
790
+ atts << " title=\"#{ title }\"" if title
791
+ atts = shelve( atts )
792
+
793
+ "<a#{ atts }>#{ text }</a>"
794
+ end
795
+ end
796
+
797
+ TEXTILE_REFS_RE = /(^ *)\[([^\n]+?)\](#{HYPERLINK})(?=\s|$)/
798
+ MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
799
+
800
+ def refs( text )
801
+ @rules.each do |rule_name|
802
+ method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
803
+ end
804
+ end
805
+
806
+ def refs_textile( text )
807
+ text.gsub!( TEXTILE_REFS_RE ) do |m|
808
+ flag, url = $~[2..3]
809
+ @urlrefs[flag.downcase] = [url, nil]
810
+ nil
811
+ end
812
+ end
813
+
814
+ def refs_markdown( text )
815
+ text.gsub!( MARKDOWN_REFS_RE ) do |m|
816
+ flag, url = $~[2..3]
817
+ title = $~[6]
818
+ @urlrefs[flag.downcase] = [url, title]
819
+ nil
820
+ end
821
+ end
822
+
823
+ def check_refs( text )
824
+ ret = @urlrefs[text.downcase] if text
825
+ ret || [text, nil]
826
+ end
827
+
828
+ IMAGE_RE = /
829
+ (<p>|.|^) # start of line?
830
+ \! # opening
831
+ (\<|\=|\>)? # optional alignment atts
832
+ (#{C}) # optional style,class atts
833
+ (?:\. )? # optional dot-space
834
+ ([^\s(!]+?) # presume this is the src
835
+ \s? # optional space
836
+ (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
837
+ \! # closing
838
+ (?::#{ HYPERLINK })? # optional href
839
+ /x
840
+
841
+ def inline_textile_image( text )
842
+ text.gsub!( IMAGE_RE ) do |m|
843
+ stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
844
+ atts = pba( atts )
845
+ atts = " src=\"#{ url }\"#{ atts }"
846
+ atts << " title=\"#{ title }\"" if title
847
+ atts << " alt=\"#{ title }\""
848
+ # size = @getimagesize($url);
849
+ # if($size) $atts.= " $size[3]";
850
+
851
+ href, alt_title = check_refs( href ) if href
852
+ url, url_title = check_refs( url )
853
+
854
+ out = ''
855
+ out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
856
+ out << "<img#{ shelve( atts ) } />"
857
+ out << "</a>#{ href_a1 }#{ href_a2 }" if href
858
+
859
+ if algn
860
+ algn = h_align( algn )
861
+ if stln == "<p>"
862
+ out = "<p style=\"float:#{ algn }\">#{ out }"
863
+ else
864
+ out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
865
+ end
866
+ else
867
+ out = stln + out
868
+ end
869
+
870
+ out
871
+ end
872
+ end
873
+
874
+ def shelve( val )
875
+ @shelf << val
876
+ " <#{ @shelf.length }>"
877
+ end
878
+
879
+ def retrieve( text )
880
+ @shelf.each_with_index do |r, i|
881
+ text.gsub!( " <#{ i + 1 }>", r )
882
+ end
883
+ end
884
+
885
+ def incoming_entities( text )
886
+ ## turn any incoming ampersands into a dummy character for now.
887
+ ## This uses a negative lookahead for alphanumerics followed by a semicolon,
888
+ ## implying an incoming html entity, to be skipped
889
+
890
+ text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
891
+ end
892
+
893
+ def clean_white_space( text )
894
+ # normalize line breaks
895
+ text.gsub!( /\r\n/, "\n" )
896
+ text.gsub!( /\r/, "\n" )
897
+ text.gsub!( /\t/, ' ' )
898
+ text.gsub!( /^ +$/, '' )
899
+ text.gsub!( /\n{3,}/, "\n\n" )
900
+ text.gsub!( /"$/, "\" " )
901
+
902
+ # if entire document is indented, flush
903
+ # to the left side
904
+ flush_left text
905
+ end
906
+
907
+ def flush_left( text )
908
+ indt = 0
909
+ while text !~ /^ {#{indt}}\S/
910
+ indt += 1
911
+ end
912
+ if indt.nonzero?
913
+ text.gsub!( /^ {#{indt}}/, '' )
914
+ end
915
+ end
916
+
917
+ def footnote_ref( text )
918
+ text.gsub!( /\b\[([0-9]+?)\](\s)?/,
919
+ '<sup><a href="#fn\1">\1</a></sup>\2' )
920
+ end
921
+
922
+ OFFTAGS = /(code|pre|kbd|notextile)/
923
+ OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
924
+ OFFTAG_OPEN = /<#{ OFFTAGS }/
925
+ OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
926
+ HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
927
+ ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
928
+
929
+ def inline_textile_glyphs( text, level = 0 )
930
+ if text !~ HASTAG_MATCH
931
+ pgl text
932
+ footnote_ref text
933
+ else
934
+ codepre = 0
935
+ text.gsub!( ALLTAG_MATCH ) do |line|
936
+ ## matches are off if we're between <code>, <pre> etc.
937
+ if $1
938
+ if @filter_html
939
+ htmlesc( line, :NoQuotes )
940
+ elsif line =~ OFFTAG_OPEN
941
+ codepre += 1
942
+ elsif line =~ OFFTAG_CLOSE
943
+ codepre -= 1
944
+ codepre = 0 if codepre < 0
945
+ end
946
+ elsif codepre.zero?
947
+ inline_textile_glyphs( line, level + 1 )
948
+ else
949
+ htmlesc( line, :NoQuotes )
950
+ end
951
+ ## p [level, codepre, orig_line, line]
952
+
953
+ line
954
+ end
955
+ end
956
+ end
957
+
958
+ def rip_offtags( text )
959
+ pre_list = []
960
+ if text =~ /<.*>/
961
+ ## strip and encode <pre> content
962
+ codepre, used_offtags = 0, {}
963
+ text.gsub!( OFFTAG_MATCH ) do |line|
964
+ if $3
965
+ offtag, aftertag = $4, $5
966
+ codepre += 1
967
+ used_offtags[offtag] = true
968
+ if codepre - used_offtags.length > 0
969
+ htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
970
+ pre_list.last << line
971
+ line = ""
972
+ else
973
+ htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
974
+ line = "<redpre##{ pre_list.length }>"
975
+ pre_list << "#{ $3 }#{ aftertag }"
976
+ end
977
+ elsif $1 and codepre > 0
978
+ if codepre - used_offtags.length > 0
979
+ htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
980
+ pre_list.last << line
981
+ line = ""
982
+ end
983
+ codepre -= 1 unless codepre.zero?
984
+ used_offtags = {} if codepre.zero?
985
+ end
986
+ line
987
+ end
988
+ end
989
+ pre_list
990
+ end
991
+
992
+ def smooth_offtags( text, pre_list )
993
+ unless pre_list.empty?
994
+ ## replace <pre> content
995
+ text.gsub!( /<redpre#(\d+)>/ ) { pre_list[$1.to_i] }
996
+ end
997
+ end
998
+
999
+ def inline( text )
1000
+ @rules.each do |rule_name|
1001
+ method( rule_name ).call( text ) if rule_name.to_s.match /^inline_/
1002
+ end
1003
+ end
1004
+
1005
+ def h_align( text )
1006
+ H_ALGN_VALS[text]
1007
+ end
1008
+
1009
+ def v_align( text )
1010
+ V_ALGN_VALS[text]
1011
+ end
1012
+
1013
+ def textile_popup_help( name, windowW, windowH )
1014
+ ' <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 />'
1015
+ end
1016
+
1017
+ end
1018
+