junebug-wiki 0.0.25 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ = 0.0.26 2006-12-08
2
+
3
+ * Feed cleanup, fixes id issue with google reader
4
+ * Removed dependency on Redcloth. Added latest (128) revision of Redcloth to ext dir to fix autolinking issue.
5
+
1
6
  = 0.0.25 2006-12-01
2
7
 
3
8
  * Feed page url wrong. Fixes #6983. Thanks Robert Gogolok
@@ -19,6 +19,12 @@ lib/junebug/controllers.rb
19
19
  lib/junebug/ext/acts_as_versioned.rb
20
20
  lib/junebug/ext/diff.rb
21
21
  lib/junebug/ext/mosquito.rb
22
+ lib/junebug/ext/redcloth.rb
23
+ lib/junebug/ext/redcloth/all_formats.rb
24
+ lib/junebug/ext/redcloth/base.rb
25
+ lib/junebug/ext/redcloth/docbook.rb
26
+ lib/junebug/ext/redcloth/markdown.rb
27
+ lib/junebug/ext/redcloth/textile.rb
22
28
  lib/junebug/generator.rb
23
29
  lib/junebug/helpers.rb
24
30
  lib/junebug/models.rb
@@ -1,3 +1,18 @@
1
+ = 0.0.26 2006-12-18
2
+
3
+ A couple of changes to the config.yml parameters were made as part of the feed cleanup. If you have an existing Junebug wiki, you may need to make the following changes:
4
+
5
+ * feedurl is now the url of the public feed, whether through a proxy, or using a feed hosting service.
6
+ * siteurl is the base url of the wiki. The feed generator will use this to point to the feed entries to the wiki.
7
+
8
+ Both of these parameters are now optional, so if you just serve your site locally, just leave them commented out.
9
+
10
+ Example: for www.junebugwiki.com, which is served through an apache proxy, I'm using the following:
11
+
12
+ feedurl: http://www.junebugwiki.com/all/feed
13
+ siteurl: http://www.junebugwiki.com
14
+
15
+
1
16
  = 0.0.23 2006-11-28
2
17
 
3
18
  Robert Gogolok rightly pointed out that the 'url' parameter in config.yml was confusing, so I renamed it 'feedurl'. You will need to make this change to any existing junebug wikis.
data/Rakefile CHANGED
@@ -49,7 +49,6 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
49
49
  p.extra_deps = [
50
50
  ['mongrel', '>=0.3.13.3'],
51
51
  ['camping', '>=1.5'],
52
- ['RedCloth', '>=3.0.4'],
53
52
  ['daemons', '>=1.0.3'],
54
53
  ['sqlite3-ruby', '>=1.1.0.1'],
55
54
  ['activerecord', '>=1.14.4']
@@ -2,7 +2,7 @@
2
2
  startpage: Welcome_to_Junebug
3
3
 
4
4
  # server configuration
5
- host: 0.0.0.0
5
+ host: 127.0.0.1
6
6
  port: 3301
7
7
 
8
8
  # if you want to have the wiki root running on something other than /
@@ -30,8 +30,16 @@ dbconnection:
30
30
  # host: localhost
31
31
 
32
32
  # configuring rss
33
- # you should only need to change this is you have an external feed manager like feedburner
34
33
  feedtitle: Wiki Updates
35
- feedurl: http://localhost:3301
36
- feed: http://localhost:3301/all/feed
34
+ #
35
+ # Optional feed parameters:
36
+ #
37
+ # Example proxy config:
38
+ #feedurl: http://www.mywiki.com/all/feed
39
+ #siteurl: http://www.mywiki.com
40
+ #
41
+ # Example feedburner config:
42
+ #feedurl: http://feeds.feedburner.com/MyWiki
43
+ #siteurl: http://www.mywiki.com
44
+
37
45
 
@@ -128,7 +128,9 @@ module Junebug::Controllers
128
128
  class Feed < R '/all/feed'
129
129
  def get
130
130
  @headers['Content-Type'] = 'application/xml'
131
- return Junebug::Views.feed
131
+ @skip_layout = true
132
+ render :feed
133
+ #return Junebug::Views.feed
132
134
  end
133
135
  end
134
136
 
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'redcloth/base'
4
+ require 'redcloth/textile'
5
+ require 'redcloth/markdown'
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../")
2
+
3
+ require 'redcloth'
4
+ require 'redcloth/docbook'
@@ -0,0 +1,674 @@
1
+ class RedCloth < String
2
+
3
+ VERSION = '3.0.4'
4
+ DEFAULT_RULES = [] # let each class add to this array
5
+ TEXTILE_RULES = [:refs_textile, :block_textile_table, :block_textile_lists, :block_textile_defs,
6
+ :block_textile_prefix, :inline_textile_image, :inline_textile_link,
7
+ :inline_textile_code, :inline_textile_span, :glyphs_textile,
8
+ :inline_textile_autolink_urls, :inline_textile_autolink_emails]
9
+ MARKDOWN_RULES = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
10
+ :block_markdown_bq, :block_markdown_lists,
11
+ :inline_markdown_reflink, :inline_markdown_link]
12
+ DOCBOOK_RULES = [:refs_docbook, :block_docbook_table, :block_docbook_lists, :block_docbook_simple_lists,
13
+ :block_docbook_defs, :block_docbook_prefix, :inline_docbook_image, :inline_docbook_link,
14
+ :inline_docbook_code, :inline_docbook_glyphs, :inline_docbook_span,
15
+ :inline_docbook_wiki_words, :inline_docbook_wiki_links, :inline_docbook_autolink_urls,
16
+ :inline_docbook_autolink_emails]
17
+ @@escape_keyword ||= "redcloth"
18
+
19
+ #
20
+ # Two accessor for setting security restrictions.
21
+ #
22
+ # This is a nice thing if you're using RedCloth for
23
+ # formatting in public places (e.g. Wikis) where you
24
+ # don't want users to abuse HTML for bad things.
25
+ #
26
+ # If +:filter_html+ is set, HTML which wasn't
27
+ # created by the Textile processor will be escaped.
28
+ #
29
+ # If +:filter_styles+ is set, it will also disable
30
+ # the style markup specifier. ('{color: red}')
31
+ #
32
+ # If +:filter_classes+ is set, it will also disable
33
+ # class attributes. ('!(classname)image!')
34
+ #
35
+ # If +:filter_ids+ is set, it will also disable
36
+ # id attributes. ('!(classname#id)image!')
37
+ #
38
+ attr_accessor :filter_html, :filter_styles, :filter_classes, :filter_ids
39
+
40
+ #
41
+ # Accessor for toggling hard breaks.
42
+ #
43
+ # If +:hard_breaks+ is set, single newlines will
44
+ # be converted to HTML break tags. This is the
45
+ # default behavior for traditional RedCloth.
46
+ #
47
+ attr_accessor :hard_breaks
48
+
49
+ # Accessor for toggling lite mode.
50
+ #
51
+ # In lite mode, block-level rules are ignored. This means
52
+ # that tables, paragraphs, lists, and such aren't available.
53
+ # Only the inline markup for bold, italics, entities and so on.
54
+ #
55
+ # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
56
+ # r.to_html
57
+ # #=> "And then? She <strong>fell</strong>!"
58
+ #
59
+ attr_accessor :lite_mode
60
+
61
+ #
62
+ # Accessor for toggling span caps.
63
+ #
64
+ # Textile places `span' tags around capitalized
65
+ # words by default, but this wreaks havoc on Wikis.
66
+ # If +:no_span_caps+ is set, this will be
67
+ # suppressed.
68
+ #
69
+ attr_accessor :no_span_caps
70
+
71
+ #
72
+ # Establishes the markup predence.
73
+ #
74
+ attr_accessor :rules
75
+
76
+ # Returns a new RedCloth object, based on _string_ and
77
+ # enforcing all the included _restrictions_.
78
+ #
79
+ # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
80
+ # r.to_html
81
+ # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
82
+ #
83
+ def initialize( string, restrictions = [] )
84
+ restrictions.each { |r| method( "#{ r }=" ).call( true ) }
85
+ super( string )
86
+ end
87
+
88
+ #
89
+ # Generates HTML from the Textile contents.
90
+ #
91
+ # r = RedCloth.new( "And then? She *fell*!" )
92
+ # r.to_html( true )
93
+ # #=>"And then? She <strong>fell</strong>!"
94
+ #
95
+ def to_html( *rules )
96
+ rules = DEFAULT_RULES if rules.empty?
97
+ # make our working copy
98
+ text = self.dup
99
+
100
+ return "" if text == ""
101
+
102
+ @urlrefs = {}
103
+ @shelf = []
104
+ @rules = rules.collect do |rule|
105
+ case rule
106
+ when :markdown
107
+ MARKDOWN_RULES
108
+ when :textile
109
+ TEXTILE_RULES
110
+ else
111
+ rule
112
+ end
113
+ end.flatten
114
+
115
+ # standard clean up
116
+ @pre_list = []
117
+ pre_process text
118
+ DEFAULT_RULES.each {|ruleset| send("#{ruleset}_pre_process", text) if private_methods.include? "#{ruleset}_pre_process"}
119
+ incoming_entities text
120
+ clean_white_space text
121
+
122
+ # start processor
123
+ no_textile text
124
+ rip_offtags text
125
+ hard_break text
126
+ unless @lite_mode
127
+ refs text
128
+ blocks text
129
+ end
130
+ inline text
131
+ smooth_offtags text
132
+ retrieve text
133
+
134
+ post_process text
135
+ DEFAULT_RULES.each {|ruleset| send("#{ruleset}_post_process", text) if private_methods.include? "#{ruleset}_post_process"}
136
+
137
+ clean_html text if filter_html
138
+
139
+ return text.strip
140
+
141
+ end
142
+
143
+ #######
144
+ private
145
+ #######
146
+ #
147
+ # Regular expressions to convert to HTML.
148
+ #
149
+ LB = "0docbook0line0break0"
150
+ NB = "0docbook0no0break0\n\n"
151
+ A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
152
+ A_VLGN = /[\-^~]/
153
+ C_CLAS = '(?:\([^)]+\))'
154
+ C_LNGE = '(?:\[[^\]]+\])'
155
+ C_STYL = '(?:\{[^}]+\})'
156
+ S_CSPN = '(?:\\\\\d+)'
157
+ S_RSPN = '(?:/\d+)'
158
+ A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
159
+ S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
160
+ C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
161
+ PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
162
+ PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
163
+ PUNCT_Q = Regexp::quote( '*-_+^~%' )
164
+ HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
165
+
166
+ TABLE_RE = /^(?:caption ?\{(.*?)\}\. ?\n)?^(?:id ?\{(.*?)\}\. ?\n)?^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
167
+ LISTS_RE = /^([#*_0-9]+?#{C} .*?)$(?![^#*])/m
168
+ LISTS_CONTENT_RE = /^([#*]+)([_0-9]*)(#{A}#{C}) (.*)$/m
169
+ DEFS_RE = /^(-#{C}\s.*?\:\=.*?)$(?![^-])/m
170
+ DEFS_CONTENT_RE = /^(-)(#{A}#{C})\s+(.*?):=(.*)$/m
171
+ BACKTICK_CODE_RE = /(.*?)
172
+ ```
173
+ (?:\|(\w+?)\|)?
174
+ (.*?[^\\])
175
+ ```
176
+ (.*?)/mx
177
+ CODE_RE = /(.*?)
178
+ @@?
179
+ (?:\|(\w+?)\|)?
180
+ (.*?[^\\])
181
+ @@?
182
+ (.*?)/x
183
+ BLOCKS_GROUP_RE = /\n{2,}(?! )/m
184
+ BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/
185
+ SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
186
+ ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
187
+ [ ]*
188
+ (.+?) # $2 = Header text
189
+ [ ]*
190
+ \#* # optional closing #'s (not counted)
191
+ $/x
192
+ LINK_RE = /
193
+ ([\s\[{(]|[#{PUNCT}])? # $pre
194
+ " # start
195
+ (#{C}) # $atts
196
+ ([^"]+?) # $text
197
+ \s?
198
+ (?:\(([^)]+?)\)(?="))? # $title
199
+ ":
200
+ ([^\s<]+?) # $url
201
+ (\/)? # $slash
202
+ ([^\w\/;]*?) # $post
203
+ (?=<|\s|$)
204
+ /x
205
+ IMAGE_RE = /
206
+ (<p>|.|^) # start of line?
207
+ \! # opening
208
+ (\<|\=|\>)? # optional alignment atts
209
+ (#{C}) # optional style,class atts
210
+ (?:\. )? # optional dot-space
211
+ ([^\s(!]+?) # presume this is the src
212
+ \s? # optional space
213
+ (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
214
+ \! # closing
215
+ (?::#{ HYPERLINK })? # optional href
216
+ /x
217
+
218
+ # Text markup tags, don't conflict with block tags
219
+ SIMPLE_HTML_TAGS = [
220
+ 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
221
+ 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
222
+ 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
223
+ ]
224
+
225
+ QTAGS = [
226
+ ['**', 'b'],
227
+ ['*', 'strong'],
228
+ ['??', 'cite', :limit],
229
+ ['-', 'del', :limit],
230
+ ['__', 'i'],
231
+ ['_', 'em', :limit],
232
+ ['%', 'span', :limit],
233
+ ['+', 'ins', :limit],
234
+ ['^', 'sup'],
235
+ ['~', 'sub']
236
+ ]
237
+ QTAGS.collect! do |rc, ht, rtype|
238
+ rcq = Regexp::quote rc
239
+ re =
240
+ case rtype
241
+ when :limit
242
+ /(\W)
243
+ (#{rcq})
244
+ (#{C})
245
+ (?::(\S+?))?
246
+ (\S.*?\S|\S)
247
+ #{rcq}
248
+ (?=\W)/x
249
+ else
250
+ /(#{rcq})
251
+ (#{C})
252
+ (?::(\S+))?
253
+ (\S.*?\S|\S)
254
+ #{rcq}/xm
255
+ end
256
+ escaped_re =
257
+ case rtype
258
+ when :limit
259
+ /(\W)
260
+ (#{@@escape_keyword}#{rcq})
261
+ (#{C})
262
+ (?::(\S+?))?
263
+ (\S.*?\S|\S)
264
+ #{rcq}#{@@escape_keyword}
265
+ (?=\W)/x
266
+ else
267
+ /(#{@@escape_keyword}#{rcq})
268
+ (#{C})
269
+ (?::(\S+))?
270
+ (\S.*?\S|\S)
271
+ #{rcq}#{@@escape_keyword}/xm
272
+ end
273
+ [rc, ht, re, rtype, escaped_re]
274
+ end
275
+
276
+ # Elements to handle
277
+ GLYPHS = [
278
+ # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
279
+ [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
280
+ [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
281
+ [ /\'/, '&#8216;' ], # single opening
282
+ # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
283
+ [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
284
+ [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
285
+ [ /"/, '&#8220;' ], # double opening
286
+ [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
287
+ [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
288
+ [ /(^|[^"][>\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
289
+ [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
290
+ [ /(^|\s)->(\s|$)/, ' &rarr; ' ], # right arrow
291
+ [ /(^|\s)-(\s|$)/, ' &#8211; ' ], # en dash
292
+ [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
293
+ [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
294
+ [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
295
+ [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
296
+ ]
297
+
298
+ H_ALGN_VALS = {
299
+ '<' => 'left',
300
+ '=' => 'center',
301
+ '>' => 'right',
302
+ '<>' => 'justify'
303
+ }
304
+
305
+ V_ALGN_VALS = {
306
+ '^' => 'top',
307
+ '-' => 'middle',
308
+ '~' => 'bottom'
309
+ }
310
+
311
+ OFFTAGS = /(code|pre|kbd|notextile)/i
312
+ OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
313
+ OFFTAG_OPEN = /<#{ OFFTAGS }/
314
+ OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
315
+
316
+ HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
317
+ ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
318
+
319
+ def pre_process( text )
320
+ text.gsub!( /={2}\`\`\`={2}/, "XXXpreformatted_backticksXXX" )
321
+ end
322
+
323
+ def post_process( text )
324
+ text.gsub!( /XXXpreformatted_backticksXXX/, '```' )
325
+ text.gsub!( LB, "\n" )
326
+ text.gsub!( NB, "" )
327
+ text.gsub!( /<\/?notextile>/, '' )
328
+ text.gsub!( /x%x%/, '&#38;' )
329
+ text << "</div>" if @div_atts
330
+ end
331
+
332
+ # Search and replace for glyphs (quotes, dashes, other symbols)
333
+ def pgl( text )
334
+ GLYPHS.each do |re, resub, tog|
335
+ next if tog and method( tog ).call
336
+ text.gsub! re, resub
337
+ end
338
+ end
339
+
340
+ # Parses attribute lists and builds an HTML attribute string
341
+ def pba( text_in, element = "" )
342
+
343
+ return '' unless text_in
344
+
345
+ style = []
346
+ text = text_in.dup
347
+ if element == 'td'
348
+ colspan = $1 if text =~ /\\(\d+)/
349
+ rowspan = $1 if text =~ /\/(\d+)/
350
+ style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
351
+ end
352
+
353
+ style << "#{ $1 };" if not filter_styles and
354
+ text.sub!( /\{([^}]*)\}/, '' )
355
+
356
+ lang = $1 if
357
+ text.sub!( /\[([^)]+?)\]/, '' )
358
+
359
+ cls = $1 if
360
+ text.sub!( /\(([^()]+?)\)/, '' )
361
+
362
+ style << "padding-left:#{ $1.length }em;" if
363
+ text.sub!( /([(]+)/, '' )
364
+
365
+ style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
366
+
367
+ style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
368
+
369
+ cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
370
+
371
+ atts = ''
372
+ atts << " style=\"#{ style.join }\"" unless style.empty?
373
+ atts << " class=\"#{ cls }\"" unless cls.to_s.empty? or filter_classes
374
+ atts << " lang=\"#{ lang }\"" if lang
375
+ atts << " id=\"#{ id }\"" if id and not filter_ids
376
+ atts << " colspan=\"#{ colspan }\"" if colspan
377
+ atts << " rowspan=\"#{ rowspan }\"" if rowspan
378
+
379
+ atts
380
+ end
381
+
382
+ #
383
+ # Flexible HTML escaping
384
+ #
385
+ def htmlesc( str, mode )
386
+ str.gsub!( '&', '&amp;' )
387
+ str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
388
+ str.gsub!( "'", '&#039;' ) if mode == :Quotes
389
+ str.gsub!( '<', '&lt;')
390
+ str.gsub!( '>', '&gt;')
391
+ end
392
+
393
+ def hard_break( text )
394
+ text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
395
+ end
396
+
397
+ def lT( text )
398
+ text =~ /\#$/ ? 'o' : 'u'
399
+ end
400
+
401
+ BLOCK_GROUP_SPLITTER = "XXX_BLOCK_GROUP_XXX\n\n"
402
+ def blocks( text, deep_code = false )
403
+ @current_class ||= nil
404
+
405
+ # Find all occurences of div(class). and process them as blocks
406
+ text.gsub!( /^div\((.*?)\)\.\s*(.*?)(?=div\([^\)]+\)\.\s*)/m ) do |blk|
407
+ block_class = (@current_class == $1) ? nil : %{ class=#{$1.inspect}}
408
+ @current_class = $1
409
+ BLOCK_GROUP_SPLITTER + ( ($2.strip.empty? || block_class.nil?) ? $2 : textile_p('div', block_class, nil, "\n\n#{$2.strip}\n\n") )
410
+ end
411
+
412
+ # Take care of the very last div
413
+ text.sub!( /div\((.*?)\)\.\s*(.*)/m ) do |blk|
414
+ block_class = (@current_class == $1) ? nil : %{ class=#{$1.inspect}}
415
+ @current_class = $1
416
+ BLOCK_GROUP_SPLITTER + ( ($2.strip.empty? || block_class.nil?) ? $2 : textile_p('div', block_class, nil, "\n\n#{$2.strip}\n\n") )
417
+ end
418
+
419
+ # Handle the text now that the placeholders for divs are set, splitting at BLOCK_GROUP_SPLITTER
420
+ text.replace(text.strip.split(BLOCK_GROUP_SPLITTER.strip).map do |chunk|
421
+ block_groups(chunk, deep_code)
422
+ end.join)
423
+ end
424
+
425
+ def block_groups( text, deep_code = false )
426
+ text.replace text.split( BLOCKS_GROUP_RE ).collect { |blk| blk(blk, deep_code) }.join("\n")
427
+ end
428
+
429
+ # Surrounds blocks with paragraphs and shelves them when necessary
430
+ def blk( text, deep_code = false )
431
+ return text if text =~ /<[0-9]+>/
432
+
433
+ plain = text !~ /\A[#*> ]/
434
+
435
+ # skip blocks that are complex HTML
436
+ if text =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
437
+ text
438
+ else
439
+ # search for indentation levels
440
+ text.strip!
441
+ if text.empty?
442
+ text
443
+ else
444
+ code_blk = nil
445
+ text.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
446
+ flush_left iblk
447
+ blocks iblk, plain
448
+ iblk.gsub( /^(\S)/, "\\1" )
449
+ if plain
450
+ code_blk = iblk; ""
451
+ else
452
+ iblk
453
+ end
454
+ end
455
+ block_applied = 0
456
+ @rules.each do |rule_name|
457
+ block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( text ) )
458
+ end
459
+ if block_applied.zero?
460
+ if deep_code
461
+ text = "\t<pre><code>#{ text }</code></pre>\n"
462
+ else
463
+ text = "\t<p>#{ text }</p>\n"
464
+ end
465
+ end
466
+ # hard_break text
467
+ text << "\n#{ code_blk }"
468
+ end
469
+ return text
470
+ end
471
+
472
+ end
473
+
474
+ def refs( text )
475
+ @rules.each do |rule_name|
476
+ method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
477
+ end
478
+ end
479
+
480
+ def check_refs( text )
481
+ ret = @urlrefs[text.downcase] if text
482
+ ret || [text, nil]
483
+ end
484
+
485
+ # Puts text in storage and returns is placeholder
486
+ # e.g. shelve("some text") => <1>
487
+ def shelve( val )
488
+ @shelf << val
489
+ " <#{ @shelf.length }>"
490
+ end
491
+
492
+ # Retrieves text from storage using its placeholder
493
+ # e.g. retrieve("<1>") => "some text"
494
+ def retrieve( text )
495
+ @shelf.each_with_index do |r, i|
496
+ text.gsub!( " <#{ i + 1 }>" ){|m| r }
497
+ end
498
+ end
499
+
500
+ def incoming_entities( text )
501
+ ## turn any incoming ampersands into a dummy character for now.
502
+ ## This uses a negative lookahead for alphanumerics followed by a semicolon,
503
+ ## implying an incoming html entity, to be skipped
504
+
505
+ text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
506
+ end
507
+
508
+ def clean_white_space( text )
509
+ # normalize line breaks
510
+ text.gsub!( /\r\n/, "\n" )
511
+ text.gsub!( /\r/, "\n" )
512
+ text.gsub!( /\t/, ' ' )
513
+ text.gsub!( /^ +$/, '' )
514
+ text.gsub!( /\n{3,}/, "\n\n" )
515
+ text.gsub!( /"$/, "\" " )
516
+
517
+ # if entire document is indented, flush
518
+ # to the left side
519
+ flush_left text
520
+ end
521
+
522
+ def flush_left( text )
523
+ indt = 0
524
+ if text =~ /^ /
525
+ while text !~ /^ {#{indt}}\S/
526
+ indt += 1
527
+ end unless text.empty?
528
+ if indt.nonzero?
529
+ text.gsub!( /^ {#{indt}}/, '' )
530
+ end
531
+ end
532
+ end
533
+
534
+ def footnote_ref( text )
535
+ text.gsub!( /\b\[([0-9]+?)\](\s)?/,
536
+ '<sup><a href="#fn\1">\1</a></sup>\2' )
537
+ end
538
+
539
+ def rip_offtags( text )
540
+ if text =~ /<.*>/
541
+ ## strip and encode <pre> content
542
+ codepre, used_offtags = 0, {}
543
+ text.gsub!( OFFTAG_MATCH ) do |line|
544
+ if $3
545
+ offtag, aftertag = $4, $5
546
+ codepre += 1
547
+ used_offtags[offtag] = true
548
+ if codepre - used_offtags.length > 0
549
+ htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
550
+ @pre_list.last << line
551
+ line = ""
552
+ else
553
+ htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
554
+ line = "<redpre##{ @pre_list.length }>"
555
+ @pre_list << "#{ $3 }#{ aftertag }"
556
+ end
557
+ elsif $1 and codepre > 0
558
+ if codepre - used_offtags.length > 0
559
+ htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
560
+ @pre_list.last << line
561
+ line = ""
562
+ end
563
+ codepre -= 1 unless codepre.zero?
564
+ used_offtags = {} if codepre.zero?
565
+ end
566
+ line
567
+ end
568
+ end
569
+ text
570
+ end
571
+
572
+ def smooth_offtags( text )
573
+ unless @pre_list.empty?
574
+ ## replace <pre> content
575
+ text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
576
+ end
577
+ end
578
+
579
+ def inline( text )
580
+ [/^inline_/, /^glyphs_/].each do |meth_re|
581
+ @rules.each do |rule_name|
582
+ method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
583
+ end
584
+ end
585
+ end
586
+
587
+ def h_align( text )
588
+ H_ALGN_VALS[text]
589
+ end
590
+
591
+ def v_align( text )
592
+ V_ALGN_VALS[text]
593
+ end
594
+
595
+ # HTML cleansing stuff
596
+ BASIC_TAGS = {
597
+ 'a' => ['href', 'title'],
598
+ 'img' => ['src', 'alt', 'title'],
599
+ 'br' => [],
600
+ 'i' => nil,
601
+ 'u' => nil,
602
+ 'b' => nil,
603
+ 'pre' => nil,
604
+ 'kbd' => nil,
605
+ 'code' => ['lang'],
606
+ 'cite' => nil,
607
+ 'strong' => nil,
608
+ 'em' => nil,
609
+ 'ins' => nil,
610
+ 'sup' => nil,
611
+ 'sub' => nil,
612
+ 'del' => nil,
613
+ 'table' => nil,
614
+ 'tr' => nil,
615
+ 'td' => ['colspan', 'rowspan'],
616
+ 'th' => nil,
617
+ 'ol' => ['start'],
618
+ 'ul' => nil,
619
+ 'li' => nil,
620
+ 'p' => nil,
621
+ 'h1' => nil,
622
+ 'h2' => nil,
623
+ 'h3' => nil,
624
+ 'h4' => nil,
625
+ 'h5' => nil,
626
+ 'h6' => nil,
627
+ 'blockquote' => ['cite']
628
+ }
629
+
630
+ def clean_html( text, tags = BASIC_TAGS )
631
+ text.gsub!( /<!\[CDATA\[/, '' )
632
+ text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
633
+ raw = $~
634
+ tag = raw[2].downcase
635
+ if tags.has_key? tag
636
+ pcs = [tag]
637
+ tags[tag].each do |prop|
638
+ ['"', "'", ''].each do |q|
639
+ q2 = ( q != '' ? q : '\s' )
640
+ if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
641
+ attrv = $1
642
+ next if (prop == 'src' or prop == 'href') and not attrv =~ %r{^(http|https|ftp):}
643
+ pcs << "#{prop}=\"#{attrv.gsub('"', '\\"')}\""
644
+ break
645
+ end
646
+ end
647
+ end if tags[tag]
648
+ "<#{raw[1]}#{pcs.join " "}>"
649
+ else
650
+ " "
651
+ end
652
+ end
653
+ end
654
+
655
+ AUTO_LINK_RE = /
656
+ ( # leading text
657
+ <\w+.*?>| # leading HTML tag, or
658
+ [^=!:'"\/]| # leading punctuation, or
659
+ ^ # beginning of line
660
+ )
661
+ (
662
+ (?:http[s]?:\/\/)| # protocol spec, or
663
+ (?:www\.) # www.*
664
+ )
665
+ (
666
+ ([\w]+[=?&:%\/\.\~\-]*)* # url segment
667
+ \w+[\/]? # url tail
668
+ (?:\#\w*)? # trailing anchor
669
+ )
670
+ ([[:punct:]]|\s|<|$) # trailing text
671
+ /x
672
+
673
+ end
674
+