Project

General

Profile

Feature #32938 » 0005-Fix-modules-where-folder-structure-does-not-meet-Zei.patch

Mizuki ISHIKAWA, 2021-10-19 08:50

View differences:

lib/redmine.rb
17 17
# along with this program; if not, write to the Free Software
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20
require 'diff'
20 21
require 'core_ext'
21 22

  
22 23
begin
lib/redmine/wiki_formatting/textile/formatter.rb
23 23
module Redmine
24 24
  module WikiFormatting
25 25
    module Textile
26
      class Formatter < RedCloth3
26
      class Formatter < Redmine::WikiFormatting::Textile::RedCloth3
27 27
        include ActionView::Helpers::TagHelper
28 28
        include Redmine::WikiFormatting::LinksHelper
29 29

  
lib/redmine/wiki_formatting/textile/redcloth3.rb
166 166
#
167 167
#  class RedCloth::Textile.new( str )
168 168

  
169
class RedCloth3 < String
170
    include Redmine::Helpers::URL
171

  
172
    VERSION = '3.0.4'
173
    DEFAULT_RULES = [:textile, :markdown]
174

  
175
    #
176
    # Two accessor for setting security restrictions.
177
    #
178
    # This is a nice thing if you're using RedCloth for
179
    # formatting in public places (e.g. Wikis) where you
180
    # don't want users to abuse HTML for bad things.
181
    #
182
    # If +:filter_html+ is set, HTML which wasn't
183
    # created by the Textile processor will be escaped.
184
    #
185
    # If +:filter_styles+ is set, it will also disable
186
    # the style markup specifier. ('{color: red}')
187
    #
188
    attr_accessor :filter_html, :filter_styles
189

  
190
    #
191
    # Accessor for toggling hard breaks.
192
    #
193
    # If +:hard_breaks+ is set, single newlines will
194
    # be converted to HTML break tags.  This is the
195
    # default behavior for traditional RedCloth.
196
    #
197
    attr_accessor :hard_breaks
198

  
199
    # Accessor for toggling lite mode.
200
    #
201
    # In lite mode, block-level rules are ignored.  This means
202
    # that tables, paragraphs, lists, and such aren't available.
203
    # Only the inline markup for bold, italics, entities and so on.
204
    #
205
    #   r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
206
    #   r.to_html
207
    #   #=> "And then? She <strong>fell</strong>!"
208
    #
209
    attr_accessor :lite_mode
210

  
211
    #
212
    # Accessor for toggling span caps.
213
    #
214
    # Textile places `span' tags around capitalized
215
    # words by default, but this wreaks havoc on Wikis.
216
    # If +:no_span_caps+ is set, this will be
217
    # suppressed.
218
    #
219
    attr_accessor :no_span_caps
220

  
221
    #
222
    # Establishes the markup predence.  Available rules include:
223
    #
224
    # == Textile Rules
225
    #
226
    # The following textile rules can be set individually.  Or add the complete
227
    # set of rules with the single :textile rule, which supplies the rule set in
228
    # the following precedence:
229
    #
230
    # refs_textile::          Textile references (i.e. [hobix]http://hobix.com/)
231
    # block_textile_table::   Textile table block structures
232
    # block_textile_lists::   Textile list structures
233
    # block_textile_prefix::  Textile blocks with prefixes (i.e. bq., h2., etc.)
234
    # inline_textile_image::  Textile inline images
235
    # inline_textile_link::   Textile inline links
236
    # inline_textile_span::   Textile inline spans
237
    # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
238
    #
239
    # == Markdown
240
    #
241
    # refs_markdown::         Markdown references (for example: [hobix]: http://hobix.com/)
242
    # block_markdown_setext:: Markdown setext headers
243
    # block_markdown_atx::    Markdown atx headers
244
    # block_markdown_rule::   Markdown horizontal rules
245
    # block_markdown_bq::     Markdown blockquotes
246
    # block_markdown_lists::  Markdown lists
247
    # inline_markdown_link::  Markdown links
248
    attr_accessor :rules
249

  
250
    # Returns a new RedCloth object, based on _string_ and
251
    # enforcing all the included _restrictions_.
252
    #
253
    #   r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
254
    #   r.to_html
255
    #     #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
256
    #
257
    def initialize( string, restrictions = [] )
258
        restrictions.each { |r| method( "#{r}=" ).call( true ) }
259
        super( string )
260
    end
169
module Redmine
170
    module WikiFormatting
171
        module Textile
172
            class RedCloth3 < String
173
                include Redmine::Helpers::URL
174

  
175
                VERSION = '3.0.4'
176
                DEFAULT_RULES = [:textile, :markdown]
177

  
178
                #
179
                # Two accessor for setting security restrictions.
180
                #
181
                # This is a nice thing if you're using RedCloth for
182
                # formatting in public places (e.g. Wikis) where you
183
                # don't want users to abuse HTML for bad things.
184
                #
185
                # If +:filter_html+ is set, HTML which wasn't
186
                # created by the Textile processor will be escaped.
187
                #
188
                # If +:filter_styles+ is set, it will also disable
189
                # the style markup specifier. ('{color: red}')
190
                #
191
                attr_accessor :filter_html, :filter_styles
192

  
193
                #
194
                # Accessor for toggling hard breaks.
195
                #
196
                # If +:hard_breaks+ is set, single newlines will
197
                # be converted to HTML break tags.  This is the
198
                # default behavior for traditional RedCloth.
199
                #
200
                attr_accessor :hard_breaks
201

  
202
                # Accessor for toggling lite mode.
203
                #
204
                # In lite mode, block-level rules are ignored.  This means
205
                # that tables, paragraphs, lists, and such aren't available.
206
                # Only the inline markup for bold, italics, entities and so on.
207
                #
208
                #   r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
209
                #   r.to_html
210
                #   #=> "And then? She <strong>fell</strong>!"
211
                #
212
                attr_accessor :lite_mode
213

  
214
                #
215
                # Accessor for toggling span caps.
216
                #
217
                # Textile places `span' tags around capitalized
218
                # words by default, but this wreaks havoc on Wikis.
219
                # If +:no_span_caps+ is set, this will be
220
                # suppressed.
221
                #
222
                attr_accessor :no_span_caps
223

  
224
                #
225
                # Establishes the markup predence.  Available rules include:
226
                #
227
                # == Textile Rules
228
                #
229
                # The following textile rules can be set individually.  Or add the complete
230
                # set of rules with the single :textile rule, which supplies the rule set in
231
                # the following precedence:
232
                #
233
                # refs_textile::          Textile references (i.e. [hobix]http://hobix.com/)
234
                # block_textile_table::   Textile table block structures
235
                # block_textile_lists::   Textile list structures
236
                # block_textile_prefix::  Textile blocks with prefixes (i.e. bq., h2., etc.)
237
                # inline_textile_image::  Textile inline images
238
                # inline_textile_link::   Textile inline links
239
                # inline_textile_span::   Textile inline spans
240
                # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
241
                #
242
                # == Markdown
243
                #
244
                # refs_markdown::         Markdown references (for example: [hobix]: http://hobix.com/)
245
                # block_markdown_setext:: Markdown setext headers
246
                # block_markdown_atx::    Markdown atx headers
247
                # block_markdown_rule::   Markdown horizontal rules
248
                # block_markdown_bq::     Markdown blockquotes
249
                # block_markdown_lists::  Markdown lists
250
                # inline_markdown_link::  Markdown links
251
                attr_accessor :rules
252

  
253
                # Returns a new RedCloth object, based on _string_ and
254
                # enforcing all the included _restrictions_.
255
                #
256
                #   r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
257
                #   r.to_html
258
                #     #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
259
                #
260
                def initialize( string, restrictions = [] )
261
                    restrictions.each { |r| method( "#{r}=" ).call( true ) }
262
                    super( string )
263
                end
261 264

  
262
    #
263
    # Generates HTML from the Textile contents.
264
    #
265
    #   r = RedCloth.new( "And then? She *fell*!" )
266
    #   r.to_html( true )
267
    #     #=>"And then? She <strong>fell</strong>!"
268
    #
269
    def to_html( *rules )
270
        rules = DEFAULT_RULES if rules.empty?
271
        # make our working copy
272
        text = self.dup
273

  
274
        @urlrefs = {}
275
        @shelf = []
276
        textile_rules = [:block_textile_table, :block_textile_lists,
277
                         :block_textile_prefix, :inline_textile_image, :inline_textile_code,
278
                         :inline_textile_span, :inline_textile_link, :glyphs_textile]
279
        markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
280
                          :block_markdown_bq, :block_markdown_lists,
281
                          :inline_markdown_reflink, :inline_markdown_link]
282
        @rules = rules.collect do |rule|
283
            case rule
284
            when :markdown
285
                markdown_rules
286
            when :textile
287
                textile_rules
288
            else
289
                rule
290
            end
291
        end.flatten
292

  
293
        # standard clean up
294
        incoming_entities text
295
        clean_white_space text
296

  
297
        # start processor
298
        @pre_list = []
299
        rip_offtags text
300
        no_textile text
301
        escape_html_tags text
302
        # need to do this before #hard_break and #blocks
303
        block_textile_quotes text unless @lite_mode
304
        hard_break text
305
        unless @lite_mode
306
            refs text
307
            blocks text
308
        end
309
        inline text
310
        smooth_offtags text
265
                #
266
                # Generates HTML from the Textile contents.
267
                #
268
                #   r = RedCloth.new( "And then? She *fell*!" )
269
                #   r.to_html( true )
270
                #     #=>"And then? She <strong>fell</strong>!"
271
                #
272
                def to_html( *rules )
273
                    rules = DEFAULT_RULES if rules.empty?
274
                    # make our working copy
275
                    text = self.dup
276

  
277
                    @urlrefs = {}
278
                    @shelf = []
279
                    textile_rules = [:block_textile_table, :block_textile_lists,
280
                                    :block_textile_prefix, :inline_textile_image, :inline_textile_code,
281
                                    :inline_textile_span, :inline_textile_link, :glyphs_textile]
282
                    markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
283
                                    :block_markdown_bq, :block_markdown_lists,
284
                                    :inline_markdown_reflink, :inline_markdown_link]
285
                    @rules = rules.collect do |rule|
286
                        case rule
287
                        when :markdown
288
                            markdown_rules
289
                        when :textile
290
                            textile_rules
291
                        else
292
                            rule
293
                        end
294
                    end.flatten
295

  
296
                    # standard clean up
297
                    incoming_entities text
298
                    clean_white_space text
299

  
300
                    # start processor
301
                    @pre_list = []
302
                    rip_offtags text
303
                    no_textile text
304
                    escape_html_tags text
305
                    # need to do this before #hard_break and #blocks
306
                    block_textile_quotes text unless @lite_mode
307
                    hard_break text
308
                    unless @lite_mode
309
                        refs text
310
                        blocks text
311
                    end
312
                    inline text
313
                    smooth_offtags text
311 314

  
312
        retrieve text
315
                    retrieve text
313 316

  
314
        text.gsub!( /<\/?notextile>/, '' )
315
        text.gsub!( /x%x%/, '&#38;' )
316
        clean_html text if filter_html
317
        text.strip!
318
        text
319
    end
317
                    text.gsub!( /<\/?notextile>/, '' )
318
                    text.gsub!( /x%x%/, '&#38;' )
319
                    clean_html text if filter_html
320
                    text.strip!
321
                    text
322
                end
320 323

  
321
  private
322

  
323
    #
324
    # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
325
    # (from PyTextile)
326
    #
327
    TEXTILE_TAGS =
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]].
333
         collect! do |a, b|
334
           [a.chr, ( b.zero? and "" or "&#{b};" )]
335
         end
336
    #
337
    # Regular expressions to convert to HTML.
338
    #
339
    A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
340
    A_VLGN = /[\-^~]/
341
    C_CLAS = '(?:\([^")]+\))'
342
    C_LNGE = '(?:\[[a-z\-_]+\])'
343
    C_STYL = '(?:\{[^{][^"}]+\})'
344
    S_CSPN = '(?:\\\\\d+)'
345
    S_RSPN = '(?:/\d+)'
346
    A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
347
    S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
348
    C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
349
    # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
350
    PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
351
    PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
352
    PUNCT_Q = Regexp::quote( '*-_+^~%' )
353
    HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
354

  
355
    # Text markup tags, don't conflict with block tags
356
    SIMPLE_HTML_TAGS = [
357
      'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
358
      'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
359
      'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
360
    ]
361

  
362
    QTAGS = [
363
      ['**', 'b', :limit],
364
      ['*', 'strong', :limit],
365
      ['??', 'cite', :limit],
366
      ['-', 'del', :limit],
367
      ['__', 'i', :limit],
368
      ['_', 'em', :limit],
369
      ['%', 'span', :limit],
370
      ['+', 'ins', :limit],
371
      ['^', 'sup', :limit],
372
      ['~', 'sub', :limit]
373
    ]
374
    QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|')
375

  
376
    QTAGS.collect! do |rc, ht, rtype|
377
        rcq = Regexp::quote rc
378
        re =
379
          case rtype
380
          when :limit
381
                /(^|[>\s\(])          # sta
382
                (?!\-\-)
383
                (#{QTAGS_JOIN}|)      # oqs
384
                (#{rcq})              # qtag
385
                ([[:word:]]|[^\s].*?[^\s])    # content
386
                (?!\-\-)
387
                #{rcq}
388
                (#{QTAGS_JOIN}|)      # oqa
389
                (?=[[:punct:]]|<|\s|\)|$)/x
390
          else
391
                /(#{rcq})
392
                (#{C})
393
                (?::(\S+))?
394
                ([[:word:]]|[^\s\-].*?[^\s\-])
395
                #{rcq}/xm
396
          end
397
        [rc, ht, re, rtype]
398
    end
324
            private
325

  
326
                #
327
                # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
328
                # (from PyTextile)
329
                #
330
                TEXTILE_TAGS =
331
                [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
332
                [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
333
                [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
334
                [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
335
                [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
336
                    collect! do |a, b|
337
                    [a.chr, ( b.zero? and "" or "&#{b};" )]
338
                    end
339
                #
340
                # Regular expressions to convert to HTML.
341
                #
342
                A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
343
                A_VLGN = /[\-^~]/
344
                C_CLAS = '(?:\([^")]+\))'
345
                C_LNGE = '(?:\[[a-z\-_]+\])'
346
                C_STYL = '(?:\{[^{][^"}]+\})'
347
                S_CSPN = '(?:\\\\\d+)'
348
                S_RSPN = '(?:/\d+)'
349
                A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
350
                S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
351
                C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
352
                # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
353
                PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
354
                PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
355
                PUNCT_Q = Regexp::quote( '*-_+^~%' )
356
                HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
357

  
358
                # Text markup tags, don't conflict with block tags
359
                SIMPLE_HTML_TAGS = [
360
                'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
361
                'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
362
                'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
363
                ]
364

  
365
                QTAGS = [
366
                ['**', 'b', :limit],
367
                ['*', 'strong', :limit],
368
                ['??', 'cite', :limit],
369
                ['-', 'del', :limit],
370
                ['__', 'i', :limit],
371
                ['_', 'em', :limit],
372
                ['%', 'span', :limit],
373
                ['+', 'ins', :limit],
374
                ['^', 'sup', :limit],
375
                ['~', 'sub', :limit]
376
                ]
377
                QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|')
378

  
379
                QTAGS.collect! do |rc, ht, rtype|
380
                    rcq = Regexp::quote rc
381
                    re =
382
                    case rtype
383
                    when :limit
384
                            /(^|[>\s\(])          # sta
385
                            (?!\-\-)
386
                            (#{QTAGS_JOIN}|)      # oqs
387
                            (#{rcq})              # qtag
388
                            ([[:word:]]|[^\s].*?[^\s])    # content
389
                            (?!\-\-)
390
                            #{rcq}
391
                            (#{QTAGS_JOIN}|)      # oqa
392
                            (?=[[:punct:]]|<|\s|\)|$)/x
393
                    else
394
                            /(#{rcq})
395
                            (#{C})
396
                            (?::(\S+))?
397
                            ([[:word:]]|[^\s\-].*?[^\s\-])
398
                            #{rcq}/xm
399
                    end
400
                    [rc, ht, re, rtype]
401
                end
399 402

  
400
    # Elements to handle
401
    GLYPHS = [
402
      #   [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
403
      #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
404
      #   [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
405
      #   [ /\'/, '&#8216;' ], # single opening
406
      #   [ /</, '&lt;' ], # less-than
407
      #   [ />/, '&gt;' ], # greater-than
408
      #   [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
409
      #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
410
      #   [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
411
      #   [ /"/, '&#8220;' ], # double opening
412
      #   [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
413
      #   [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
414
      #   [ /(^|[^"][>\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
415
      #   [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
416
      #   [ /\s->\s/, ' &rarr; ' ], # right arrow
417
      #   [ /\s-\s/, ' &#8211; ' ], # en dash
418
      #   [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
419
      #   [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
420
      #   [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
421
      #   [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
422
    ]
423

  
424
    H_ALGN_VALS = {
425
        '<' => 'left',
426
        '=' => 'center',
427
        '>' => 'right',
428
        '<>' => 'justify'
429
    }
430

  
431
    V_ALGN_VALS = {
432
        '^' => 'top',
433
        '-' => 'middle',
434
        '~' => 'bottom'
435
    }
436

  
437
    #
438
    # Flexible HTML escaping
439
    #
440
    def htmlesc( str, mode=:Quotes )
441
      if str
442
        str.gsub!( '&', '&amp;' )
443
        str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
444
        str.gsub!( "'", '&#039;' ) if mode == :Quotes
445
        str.gsub!( '<', '&lt;')
446
        str.gsub!( '>', '&gt;')
447
      end
448
      str
449
    end
403
                # Elements to handle
404
                GLYPHS = [
405
                #   [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
406
                #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
407
                #   [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
408
                #   [ /\'/, '&#8216;' ], # single opening
409
                #   [ /</, '&lt;' ], # less-than
410
                #   [ />/, '&gt;' ], # greater-than
411
                #   [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
412
                #   [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
413
                #   [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
414
                #   [ /"/, '&#8220;' ], # double opening
415
                #   [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
416
                #   [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
417
                #   [ /(^|[^"][>\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
418
                #   [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
419
                #   [ /\s->\s/, ' &rarr; ' ], # right arrow
420
                #   [ /\s-\s/, ' &#8211; ' ], # en dash
421
                #   [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
422
                #   [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
423
                #   [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
424
                #   [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
425
                ]
426

  
427
                H_ALGN_VALS = {
428
                    '<' => 'left',
429
                    '=' => 'center',
430
                    '>' => 'right',
431
                    '<>' => 'justify'
432
                }
433

  
434
                V_ALGN_VALS = {
435
                    '^' => 'top',
436
                    '-' => 'middle',
437
                    '~' => 'bottom'
438
                }
439

  
440
                #
441
                # Flexible HTML escaping
442
                #
443
                def htmlesc( str, mode=:Quotes )
444
                if str
445
                    str.gsub!( '&', '&amp;' )
446
                    str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
447
                    str.gsub!( "'", '&#039;' ) if mode == :Quotes
448
                    str.gsub!( '<', '&lt;')
449
                    str.gsub!( '>', '&gt;')
450
                end
451
                str
452
                end
450 453

  
451
    # Search and replace for Textile glyphs (quotes, dashes, other symbols)
452
    def pgl( text )
453
        # GLYPHS.each do |re, resub, tog|
454
        #    next if tog and method( tog ).call
455
        #    text.gsub! re, resub
456
        # end
457
        text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m|
458
          "<abbr title=\"#{htmlesc $2}\">#{$1}</abbr>"
459
        end
460
    end
454
                # Search and replace for Textile glyphs (quotes, dashes, other symbols)
455
                def pgl( text )
456
                    # GLYPHS.each do |re, resub, tog|
457
                    #    next if tog and method( tog ).call
458
                    #    text.gsub! re, resub
459
                    # end
460
                    text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m|
461
                    "<abbr title=\"#{htmlesc $2}\">#{$1}</abbr>"
462
                    end
463
                end
461 464

  
462
    # Parses Textile attribute lists and builds an HTML attribute string
463
    def pba( text_in, element = "" )
464
        return +'' unless text_in
465
                # Parses Textile attribute lists and builds an HTML attribute string
466
                def pba( text_in, element = "" )
467
                    return +'' unless text_in
465 468

  
466
        style = []
467
        text = text_in.dup
468
        if element == 'td'
469
            colspan = $1 if text =~ /\\(\d+)/
470
            rowspan = $1 if text =~ /\/(\d+)/
471
            style << "vertical-align:#{v_align($&)};" if text =~ A_VLGN
472
        end
469
                    style = []
470
                    text = text_in.dup
471
                    if element == 'td'
472
                        colspan = $1 if text =~ /\\(\d+)/
473
                        rowspan = $1 if text =~ /\/(\d+)/
474
                        style << "vertical-align:#{v_align($&)};" if text =~ A_VLGN
475
                    end
473 476

  
474
        if text.sub!( /\{([^"}]*)\}/, '' ) && !filter_styles
475
          sanitized = sanitize_styles($1)
476
          style << "#{sanitized};" unless sanitized.blank?
477
        end
477
                    if text.sub!( /\{([^"}]*)\}/, '' ) && !filter_styles
478
                    sanitized = sanitize_styles($1)
479
                    style << "#{sanitized};" unless sanitized.blank?
480
                    end
478 481

  
479
        lang = $1 if
480
            text.sub!( /\[([a-z\-_]+?)\]/, '' )
482
                    lang = $1 if
483
                        text.sub!( /\[([a-z\-_]+?)\]/, '' )
481 484

  
482
        cls = $1 if
483
            text.sub!( /\(([^()]+?)\)/, '' )
485
                    cls = $1 if
486
                        text.sub!( /\(([^()]+?)\)/, '' )
484 487

  
485
        style << "padding-left:#{$1.length}em;" if
486
            text.sub!( /([(]+)/, '' )
488
                    style << "padding-left:#{$1.length}em;" if
489
                        text.sub!( /([(]+)/, '' )
487 490

  
488
        style << "padding-right:#{$1.length}em;" if text.sub!( /([)]+)/, '' )
491
                    style << "padding-right:#{$1.length}em;" if text.sub!( /([)]+)/, '' )
489 492

  
490
        style << "text-align:#{h_align($&)};" if text =~ A_HLGN
493
                    style << "text-align:#{h_align($&)};" if text =~ A_HLGN
491 494

  
492
        cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
495
                    cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
493 496

  
494
        # add wiki-class- and wiki-id- to classes and ids to prevent setting of
495
        # arbitrary classes and ids
496
        cls = cls.split(/\s+/).map do |c|
497
          c.starts_with?('wiki-class-') ? c : "wiki-class-#{c}"
498
        end.join(' ') if cls
497
                    # add wiki-class- and wiki-id- to classes and ids to prevent setting of
498
                    # arbitrary classes and ids
499
                    cls = cls.split(/\s+/).map do |c|
500
                    c.starts_with?('wiki-class-') ? c : "wiki-class-#{c}"
501
                    end.join(' ') if cls
499 502

  
500
        id = id.starts_with?('wiki-id-') ? id : "wiki-id-#{id}" if id
503
                    id = id.starts_with?('wiki-id-') ? id : "wiki-id-#{id}" if id
501 504

  
502
        atts = +''
503
        atts << " style=\"#{style.join}\"" unless style.empty?
504
        atts << " class=\"#{cls}\"" unless cls.to_s.empty?
505
        atts << " lang=\"#{lang}\"" if lang
506
        atts << " id=\"#{id}\"" if id
507
        atts << " colspan=\"#{colspan}\"" if colspan
508
        atts << " rowspan=\"#{rowspan}\"" if rowspan
505
                    atts = +''
506
                    atts << " style=\"#{style.join}\"" unless style.empty?
507
                    atts << " class=\"#{cls}\"" unless cls.to_s.empty?
508
                    atts << " lang=\"#{lang}\"" if lang
509
                    atts << " id=\"#{id}\"" if id
510
                    atts << " colspan=\"#{colspan}\"" if colspan
511
                    atts << " rowspan=\"#{rowspan}\"" if rowspan
509 512

  
510
        atts
511
    end
512

  
513
    STYLES_RE = /^(color|(min-|max-)?+(width|height)|border|background|padding|margin|font|text|float)(-[a-z]+)*:\s*((\d+%?|\d+px|\d+(\.\d+)?em|#[0-9a-f]+|[a-z]+)\s*)+$/i
513
                    atts
514
                end
514 515

  
515
    def sanitize_styles(str)
516
      styles = str.split(";").map(&:strip)
517
      styles.reject! do |style|
518
        !style.match(STYLES_RE)
519
      end
520
      styles.join(";")
521
    end
516
                STYLES_RE = /^(color|(min-|max-)?+(width|height)|border|background|padding|margin|font|text|float)(-[a-z]+)*:\s*((\d+%?|\d+px|\d+(\.\d+)?em|#[0-9a-f]+|[a-z]+)\s*)+$/i
522 517

  
523
    TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
524

  
525
    # Parses a Textile table block, building HTML from the result.
526
    def block_textile_table( text )
527
        text.gsub!( TABLE_RE ) do |matches|
528
            tatts, fullrow = $~[1..2]
529
            tatts = pba( tatts, 'table' )
530
            tatts = shelve( tatts ) if tatts
531
            rows = []
532
            fullrow.gsub!(/([^|\s])\s*\n/, "\\1<br />")
533
            fullrow.each_line do |row|
534
                ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
535
                cells = []
536
                # the regexp prevents wiki links with a | from being cut as cells
537
                row.scan(/\|(_?#{S}#{A}#{C}\. ?)?((\[\[[^|\]]*\|[^|\]]*\]\]|[^|])*?)(?=\|)/) do |modifiers, cell|
538
                    ctyp = 'd'
539
                    ctyp = 'h' if modifiers && modifiers =~ /^_/
540

  
541
                    catts = nil
542
                    catts = pba( modifiers, 'td' ) if modifiers
543

  
544
                    catts = shelve( catts ) if catts
545
                    cells << "\t\t\t<t#{ctyp}#{catts}>#{cell}</t#{ctyp}>"
518
                def sanitize_styles(str)
519
                styles = str.split(";").map(&:strip)
520
                styles.reject! do |style|
521
                    !style.match(STYLES_RE)
522
                end
523
                styles.join(";")
546 524
                end
547
                ratts = shelve( ratts ) if ratts
548
                rows << "\t\t<tr#{ratts}>\n#{cells.join("\n")}\n\t\t</tr>"
549
            end
550
            "\t<table#{tatts}>\n#{rows.join("\n")}\n\t</table>\n\n"
551
        end
552
    end
553 525

  
554
    LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
555
    LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
556

  
557
    # Parses Textile lists and generates HTML
558
    def block_textile_lists( text )
559
        text.gsub!( LISTS_RE ) do |match|
560
            lines = match.split( /\n/ )
561
            last_line = -1
562
            depth = []
563
            lines.each_with_index do |line, line_id|
564
                if line =~ LISTS_CONTENT_RE
565
                    tl,atts,content = $~[1..3]
566
                    if depth.last
567
                        if depth.last.length > tl.length
568
                            (depth.length - 1).downto(0) do |i|
569
                                break if depth[i].length == tl.length
570
                                lines[line_id - 1] << "</li>\n\t</#{lT(depth[i])}l>\n\t"
571
                                depth.pop
526
                TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
527

  
528
                # Parses a Textile table block, building HTML from the result.
529
                def block_textile_table( text )
530
                    text.gsub!( TABLE_RE ) do |matches|
531
                        tatts, fullrow = $~[1..2]
532
                        tatts = pba( tatts, 'table' )
533
                        tatts = shelve( tatts ) if tatts
534
                        rows = []
535
                        fullrow.gsub!(/([^|\s])\s*\n/, "\\1<br />")
536
                        fullrow.each_line do |row|
537
                            ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
538
                            cells = []
539
                            # the regexp prevents wiki links with a | from being cut as cells
540
                            row.scan(/\|(_?#{S}#{A}#{C}\. ?)?((\[\[[^|\]]*\|[^|\]]*\]\]|[^|])*?)(?=\|)/) do |modifiers, cell|
541
                                ctyp = 'd'
542
                                ctyp = 'h' if modifiers && modifiers =~ /^_/
543

  
544
                                catts = nil
545
                                catts = pba( modifiers, 'td' ) if modifiers
546

  
547
                                catts = shelve( catts ) if catts
548
                                cells << "\t\t\t<t#{ctyp}#{catts}>#{cell}</t#{ctyp}>"
572 549
                            end
550
                            ratts = shelve( ratts ) if ratts
551
                            rows << "\t\t<tr#{ratts}>\n#{cells.join("\n")}\n\t\t</tr>"
573 552
                        end
574
                        if depth.last and depth.last.length == tl.length
575
                            lines[line_id - 1] << '</li>'
553
                        "\t<table#{tatts}>\n#{rows.join("\n")}\n\t</table>\n\n"
554
                    end
555
                end
556

  
557
                LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
558
                LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
559

  
560
                # Parses Textile lists and generates HTML
561
                def block_textile_lists( text )
562
                    text.gsub!( LISTS_RE ) do |match|
563
                        lines = match.split( /\n/ )
564
                        last_line = -1
565
                        depth = []
566
                        lines.each_with_index do |line, line_id|
567
                            if line =~ LISTS_CONTENT_RE
568
                                tl,atts,content = $~[1..3]
569
                                if depth.last
570
                                    if depth.last.length > tl.length
571
                                        (depth.length - 1).downto(0) do |i|
572
                                            break if depth[i].length == tl.length
573
                                            lines[line_id - 1] << "</li>\n\t</#{lT(depth[i])}l>\n\t"
574
                                            depth.pop
575
                                        end
576
                                    end
577
                                    if depth.last and depth.last.length == tl.length
578
                                        lines[line_id - 1] << '</li>'
579
                                    end
580
                                end
581
                                if depth.last != tl
582
                                    depth << tl
583
                                    atts = pba( atts )
584
                                    atts = shelve( atts ) if atts
585
                                    lines[line_id] = +"\t<#{lT(tl)}l#{atts}>\n\t<li>#{content}"
586
                                else
587
                                    lines[line_id] = +"\t\t<li>#{content}"
588
                                end
589
                                last_line = line_id
590
                            else
591
                                last_line = line_id
592
                            end
593
                            if line_id - last_line > 1 or line_id == lines.length - 1
594
                                while v = depth.pop
595
                                    lines[last_line] << "</li>\n\t</#{lT(v)}l>"
596
                                end
597
                            end
576 598
                        end
599
                        lines.join( "\n" )
577 600
                    end
578
                    if depth.last != tl
579
                        depth << tl
580
                        atts = pba( atts )
581
                        atts = shelve( atts ) if atts
582
                        lines[line_id] = +"\t<#{lT(tl)}l#{atts}>\n\t<li>#{content}"
583
                    else
584
                        lines[line_id] = +"\t\t<li>#{content}"
601
                end
602

  
603
                QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m
604
                QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
605

  
606
                def block_textile_quotes( text )
607
                text.gsub!( QUOTES_RE ) do |match|
608
                    lines = match.split( /\n/ )
609
                    quotes = +''
610
                    indent = 0
611
                    lines.each do |line|
612
                    line =~ QUOTES_CONTENT_RE
613
                    bq,content = $1, $2
614
                    l = bq.count('>')
615
                    if l != indent
616
                        quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
617
                        indent = l
585 618
                    end
586
                    last_line = line_id
587
                else
588
                    last_line = line_id
619
                    quotes << (content + "\n")
620
                    end
621
                    quotes << ("\n" + '</blockquote>' * indent + "\n\n")
622
                    quotes
623
                end
589 624
                end
590
                if line_id - last_line > 1 or line_id == lines.length - 1
591
                    while v = depth.pop
592
                        lines[last_line] << "</li>\n\t</#{lT(v)}l>"
625

  
626
                CODE_RE = /(\W)
627
                    @
628
                    (?:\|(\w+?)\|)?
629
                    (.+?)
630
                    @
631
                    (?=\W)/x
632

  
633
                def inline_textile_code( text )
634
                    text.gsub!( CODE_RE ) do |m|
635
                        before,lang,code,after = $~[1..4]
636
                        lang = " lang=\"#{lang}\"" if lang
637
                        rip_offtags( +"#{before}<code#{lang}>#{code}</code>#{after}", false )
593 638
                    end
594 639
                end
595
            end
596
            lines.join( "\n" )
597
        end
598
    end
599 640

  
600
    QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m
601
    QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
602

  
603
    def block_textile_quotes( text )
604
      text.gsub!( QUOTES_RE ) do |match|
605
        lines = match.split( /\n/ )
606
        quotes = +''
607
        indent = 0
608
        lines.each do |line|
609
          line =~ QUOTES_CONTENT_RE
610
          bq,content = $1, $2
611
          l = bq.count('>')
612
          if l != indent
613
            quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
614
            indent = l
615
          end
616
          quotes << (content + "\n")
617
        end
618
        quotes << ("\n" + '</blockquote>' * indent + "\n\n")
619
        quotes
620
      end
621
    end
641
                def lT( text )
642
                    /\#$/.match?(text) ? 'o' : 'u'
643
                end
622 644

  
623
    CODE_RE = /(\W)
624
        @
625
        (?:\|(\w+?)\|)?
626
        (.+?)
627
        @
628
        (?=\W)/x
629

  
630
    def inline_textile_code( text )
631
        text.gsub!( CODE_RE ) do |m|
632
            before,lang,code,after = $~[1..4]
633
            lang = " lang=\"#{lang}\"" if lang
634
            rip_offtags( +"#{before}<code#{lang}>#{code}</code>#{after}", false )
635
        end
636
    end
645
                def hard_break( text )
646
                    text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
647
                end
637 648

  
638
    def lT( text )
639
        /\#$/.match?(text) ? 'o' : 'u'
640
    end
649
                BLOCKS_GROUP_RE = /\n{2,}(?! )/m
641 650

  
642
    def hard_break( text )
643
        text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
644
    end
651
                def blocks( text, deep_code = false )
652
                    text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
653
                        plain = blk !~ /\A[#*> ]/
645 654

  
646
    BLOCKS_GROUP_RE = /\n{2,}(?! )/m
647

  
648
    def blocks( text, deep_code = false )
649
        text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
650
            plain = blk !~ /\A[#*> ]/
651

  
652
            # skip blocks that are complex HTML
653
            if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
654
                blk
655
            else
656
                # search for indentation levels
657
                blk.strip!
658
                if blk.empty?
659
                    blk
660
                else
661
                    code_blk = nil
662
                    blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
663
                        flush_left iblk
664
                        blocks iblk, plain
665
                        iblk.gsub( /^(\S)/, "\t\\1" )
666
                        if plain
667
                            code_blk = iblk; ""
655
                        # skip blocks that are complex HTML
656
                        if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
657
                            blk
668 658
                        else
669
                            iblk
659
                            # search for indentation levels
660
                            blk.strip!
661
                            if blk.empty?
662
                                blk
663
                            else
664
                                code_blk = nil
665
                                blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
666
                                    flush_left iblk
667
                                    blocks iblk, plain
668
                                    iblk.gsub( /^(\S)/, "\t\\1" )
669
                                    if plain
670
                                        code_blk = iblk; ""
671
                                    else
672
                                        iblk
673
                                    end
674
                                end
675

  
676
                                block_applied = 0
677
                                @rules.each do |rule_name|
678
                                    block_applied += 1 if rule_name.to_s.match /^block_/ and method(rule_name).call(blk)
679
                                end
680
                                if block_applied.zero?
681
                                    if deep_code
682
                                        blk = "\t<pre><code>#{blk}</code></pre>"
683
                                    else
684
                                        blk = "\t<p>#{blk}</p>"
685
                                    end
686
                                end
687
                                # hard_break blk
688
                                blk + "\n#{code_blk}"
689
                            end
670 690
                        end
671
                    end
691
                    end.join( "\n\n" ) )
692
                end
672 693

  
673
                    block_applied = 0
674
                    @rules.each do |rule_name|
675
                        block_applied += 1 if rule_name.to_s.match /^block_/ and method(rule_name).call(blk)
676
                    end
677
                    if block_applied.zero?
678
                        if deep_code
679
                            blk = "\t<pre><code>#{blk}</code></pre>"
680
                        else
681
                            blk = "\t<p>#{blk}</p>"
682
                        end
683
                    end
684
                    # hard_break blk
685
                    blk + "\n#{code_blk}"
694
                def textile_bq( tag, atts, cite, content )
695
                    cite, cite_title = check_refs( cite )
696
                    cite = " cite=\"#{cite}\"" if cite
697
                    atts = shelve( atts ) if atts
698
                    "\t<blockquote#{cite}>\n\t\t<p#{atts}>#{content}</p>\n\t</blockquote>"
686 699
                end
687
            end
688
        end.join( "\n\n" ) )
689
    end
690 700

  
691
    def textile_bq( tag, atts, cite, content )
692
        cite, cite_title = check_refs( cite )
693
        cite = " cite=\"#{cite}\"" if cite
694
        atts = shelve( atts ) if atts
695
        "\t<blockquote#{cite}>\n\t\t<p#{atts}>#{content}</p>\n\t</blockquote>"
696
    end
701
                def textile_p( tag, atts, cite, content )
702
                    atts = shelve( atts ) if atts
703
                    "\t<#{tag}#{atts}>#{content}</#{tag}>"
704
                end
697 705

  
698
    def textile_p( tag, atts, cite, content )
699
        atts = shelve( atts ) if atts
700
        "\t<#{tag}#{atts}>#{content}</#{tag}>"
701
    end
706
                alias textile_h1 textile_p
707
                alias textile_h2 textile_p
708
                alias textile_h3 textile_p
709
                alias textile_h4 textile_p
710
                alias textile_h5 textile_p
711
                alias textile_h6 textile_p
712

  
713
                def textile_fn_( tag, num, atts, cite, content )
714
                    atts << " id=\"fn#{num}\" class=\"footnote\""
715
                    content = "<sup>#{num}</sup> #{content}"
716
                    atts = shelve( atts ) if atts
717
                    "\t<p#{atts}>#{content}</p>"
718
                end
702 719

  
703
    alias textile_h1 textile_p
704
    alias textile_h2 textile_p
705
    alias textile_h3 textile_p
706
    alias textile_h4 textile_p
707
    alias textile_h5 textile_p
708
    alias textile_h6 textile_p
709

  
710
    def textile_fn_( tag, num, atts, cite, content )
711
        atts << " id=\"fn#{num}\" class=\"footnote\""
712
        content = "<sup>#{num}</sup> #{content}"
713
        atts = shelve( atts ) if atts
714
        "\t<p#{atts}>#{content}</p>"
715
    end
720
                BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
716 721

  
717
    BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
722
                def block_textile_prefix( text )
723
                    if text =~ BLOCK_RE
724
                        tag,tagpre,num,atts,cite,content = $~[1..6]
725
                        atts = pba( atts )
718 726

  
719
    def block_textile_prefix( text )
720
        if text =~ BLOCK_RE
721
            tag,tagpre,num,atts,cite,content = $~[1..6]
722
            atts = pba( atts )
727
                        # pass to prefix handler
728
                        replacement = nil
729
                        if respond_to? "textile_#{tag}", true
730
                        replacement = method( "textile_#{tag}" ).call( tag, atts, cite, content )
731
                        elsif respond_to? "textile_#{tagpre}_", true
732
                        replacement = method( "textile_#{tagpre}_" ).call( tagpre, num, atts, cite, content )
733
                        end
734
                        text.gsub!( $& ) { replacement } if replacement
735
                    end
736
                end
723 737

  
724
            # pass to prefix handler
725
            replacement = nil
726
            if respond_to? "textile_#{tag}", true
727
              replacement = method( "textile_#{tag}" ).call( tag, atts, cite, content )
728
            elsif respond_to? "textile_#{tagpre}_", true
729
              replacement = method( "textile_#{tagpre}_" ).call( tagpre, num, atts, cite, content )
730
            end
731
            text.gsub!( $& ) { replacement } if replacement
732
        end
733
    end
738
                SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
739
                def block_markdown_setext( text )
740
                    if text =~ SETEXT_RE
741
                        tag = ($2 == "=" ? "h1" : "h2")
742
                        blk, cont = "<#{tag}>#{$1}</#{tag}>", $'
743
                        blocks cont
744
                        text.replace( blk + cont )
745
                    end
746
                end
734 747

  
735
    SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
736
    def block_markdown_setext( text )
737
        if text =~ SETEXT_RE
738
            tag = ($2 == "=" ? "h1" : "h2")
739
            blk, cont = "<#{tag}>#{$1}</#{tag}>", $'
740
            blocks cont
741
            text.replace( blk + cont )
742
        end
743
    end
748
                ATX_RE = /\A(\#{1,6})  # $1 = string of #'s
749
                        [ ]*
750
                        (.+?)       # $2 = Header text
751
                        [ ]*
752
                        \#*         # optional closing #'s (not counted)
753
                        $/x
754
                def block_markdown_atx( text )
755
                    if text =~ ATX_RE
756
                        tag = "h#{$1.length}"
757
                        blk, cont = "<#{tag}>#{$2}</#{tag}>\n\n", $'
758
                        blocks cont
759
                        text.replace( blk + cont )
760
                    end
761
                end
744 762

  
745
    ATX_RE = /\A(\#{1,6})  # $1 = string of #'s
746
              [ ]*
747
              (.+?)       # $2 = Header text
748
              [ ]*
749
              \#*         # optional closing #'s (not counted)
750
              $/x
751
    def block_markdown_atx( text )
752
        if text =~ ATX_RE
753
            tag = "h#{$1.length}"
754
            blk, cont = "<#{tag}>#{$2}</#{tag}>\n\n", $'
755
            blocks cont
756
            text.replace( blk + cont )
757
        end
758
    end
763
                MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
759 764

  
760
    MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
765
                def block_markdown_bq( text )
766
                    text.gsub!( MARKDOWN_BQ_RE ) do |blk|
767
                        blk.gsub!( /^ *> ?/, '' )
768
                        flush_left blk
769
                        blocks blk
770
                        blk.gsub!( /^(\S)/, "\t\\1" )
771
                        "<blockquote>\n#{blk}\n</blockquote>\n\n"
772
                    end
773
                end
761 774

  
762
    def block_markdown_bq( text )
763
        text.gsub!( MARKDOWN_BQ_RE ) do |blk|
764
            blk.gsub!( /^ *> ?/, '' )
765
            flush_left blk
766
            blocks blk
767
            blk.gsub!( /^(\S)/, "\t\\1" )
768
            "<blockquote>\n#{blk}\n</blockquote>\n\n"
769
        end
770
    end
775
                MARKDOWN_RULE_RE = /^(#{
776
                    ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
777
                })$/
771 778

  
772
    MARKDOWN_RULE_RE = /^(#{
773
        ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
774
    })$/
779
                def block_markdown_rule( text )
780
                    text.gsub!( MARKDOWN_RULE_RE ) do |blk|
781
                        "<hr />"
782
                    end
783
                end
775 784

  
776
    def block_markdown_rule( text )
777
        text.gsub!( MARKDOWN_RULE_RE ) do |blk|
778
            "<hr />"
779
        end
780
    end
785
                # XXX TODO XXX
786
                def block_markdown_lists( text )
787
                end
781 788

  
782
    # XXX TODO XXX
783
    def block_markdown_lists( text )
784
    end
789
                def inline_textile_span( text )
790
                    QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
791
                        text.gsub!( qtag_re ) do |m|
792
                            case rtype
793
                            when :limit
794
                                sta,oqs,qtag,content,oqa = $~[1..6]
795
                                atts = nil
796
                                if content =~ /^(#{C})(.+)$/
797
                                atts, content = $~[1..2]
798
                                end
799
                            else
800
                                qtag,atts,cite,content = $~[1..4]
801
                                sta = ''
802
                            end
803
                            atts = pba( atts )
804
                            atts = shelve( atts ) if atts
785 805

  
786
    def inline_textile_span( text )
787
        QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
788
            text.gsub!( qtag_re ) do |m|
789
                case rtype
790
                when :limit
791
                    sta,oqs,qtag,content,oqa = $~[1..6]
792
                    atts = nil
793
                    if content =~ /^(#{C})(.+)$/
794
                      atts, content = $~[1..2]
806
                            "#{sta}#{oqs}<#{ht}#{atts}>#{content}</#{ht}>#{oqa}"
807
                        end
795 808
                    end
796
                else
797
                    qtag,atts,cite,content = $~[1..4]
798
                    sta = ''
799 809
                end
800
                atts = pba( atts )
801
                atts = shelve( atts ) if atts
802

  
803
                "#{sta}#{oqs}<#{ht}#{atts}>#{content}</#{ht}>#{oqa}"
804
            end
805
        end
806
    end
807 810

  
808
    LINK_RE = /
809
            (
810
            ([\s\[{(]|[#{PUNCT}])?     # $pre
811
            "                          # start
812
            (#{C})                     # $atts
813
            ([^"\n]+?)                 # $text
814
            \s?
815
            (?:\(([^)]+?)\)(?="))?     # $title
816
            ":
817
            (                          # $url
818
            (\/|[a-zA-Z]+:\/\/|www\.|mailto:)  # $proto
819
            [[:alnum:]_\/]\S+?
820
            )
821
            (\/)?                      # $slash
822
            ([^[:alnum:]_\=\/;\(\)\-]*?)       # $post
823
            )
824
            (?=<|\s|$)
825
        /x
826

  
827
    def inline_textile_link( text )
828
        text.gsub!( LINK_RE ) do |m|
829
          all,pre,atts,text,title,url,proto,slash,post = $~[1..9]
830
          if text.include?('<br />')
831
            all
832
          else
833
            url, url_title = check_refs(url)
834
            title ||= url_title
835
            # Idea below : an URL with unbalanced parethesis and
836
            # ending by ')' is put into external parenthesis
837
            if url[-1] == ")" and ((url.count("(") - url.count(")")) < 0)
838
              url = url[0..-2] # discard closing parenth from url
839
              post = ")" + post # add closing parenth to post
840
            end
811
                LINK_RE = /
812
                        (
813
                        ([\s\[{(]|[#{PUNCT}])?     # $pre
814
                        "                          # start
815
                        (#{C})                     # $atts
816
                        ([^"\n]+?)                 # $text
817
                        \s?
818
                        (?:\(([^)]+?)\)(?="))?     # $title
819
                        ":
820
                        (                          # $url
821
                        (\/|[a-zA-Z]+:\/\/|www\.|mailto:)  # $proto
822
                        [[:alnum:]_\/]\S+?
823
                        )
824
                        (\/)?                      # $slash
825
                        ([^[:alnum:]_\=\/;\(\)\-]*?)       # $post
826
                        )
827
                        (?=<|\s|$)
828
                    /x
829

  
830
                def inline_textile_link( text )
831
                    text.gsub!( LINK_RE ) do |m|
832
                    all,pre,atts,text,title,url,proto,slash,post = $~[1..9]
833
                    if text.include?('<br />')
834
                        all
835
                    else
836
                        url, url_title = check_refs(url)
837
                        title ||= url_title
838
                        # Idea below : an URL with unbalanced parethesis and
839
                        # ending by ')' is put into external parenthesis
840
                        if url[-1] == ")" and ((url.count("(") - url.count(")")) < 0)
841
                        url = url[0..-2] # discard closing parenth from url
842
                        post = ")" + post # add closing parenth to post
843
                        end
841 844

  
842
            url = htmlesc(url.dup)
843
            next all if url.downcase.start_with?('javascript:')
845
                        url = htmlesc(url.dup)
846
                        next all if url.downcase.start_with?('javascript:')
844 847

  
845
            atts = pba(atts)
846
            atts = +" href=\"#{url}#{slash}\"#{atts}"
847
            atts << " title=\"#{htmlesc title}\"" if title
848
            atts = shelve(atts) if atts
849
            external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
850
            "#{pre}<a#{atts}#{external}>#{text}</a>#{post}"
851
          end
852
        end
853
    end
848
                        atts = pba(atts)
849
                        atts = +" href=\"#{url}#{slash}\"#{atts}"
850
                        atts << " title=\"#{htmlesc title}\"" if title
851
                        atts = shelve(atts) if atts
852
                        external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
853
                        "#{pre}<a#{atts}#{external}>#{text}</a>#{post}"
854
                    end
855
                    end
856
                end
854 857

  
855
    MARKDOWN_REFLINK_RE = /
856
            \[([^\[\]]+)\]      # $text
857
            [ ]?                # opt. space
858
            (?:\n[ ]*)?         # one optional newline followed by spaces
859
            \[(.*?)\]           # $id
860
        /x
861

  
862
    def inline_markdown_reflink( text )
863
        text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
864
            text, id = $~[1..2]
865

  
866
            if id.empty?
867
                url, title = check_refs( text )
868
            else
869
                url, title = check_refs( id )
870
            end
858
                MARKDOWN_REFLINK_RE = /
859
                        \[([^\[\]]+)\]      # $text
860
                        [ ]?                # opt. space
861
                        (?:\n[ ]*)?         # one optional newline followed by spaces
862
                        \[(.*?)\]           # $id
863
                    /x
871 864

  
872
            atts = " href=\"#{url}\""
873
            atts << " title=\"#{title}\"" if title
874
            atts = shelve( atts )
865
                def inline_markdown_reflink( text )
866
                    text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
867
                        text, id = $~[1..2]
875 868

  
876
            "<a#{atts}>#{text}</a>"
877
        end
878
    end
869
                        if id.empty?
870
                            url, title = check_refs( text )
871
                        else
872
                            url, title = check_refs( id )
873
                        end
879 874

  
880
    MARKDOWN_LINK_RE = /
881
            \[([^\[\]]+)\]      # $text
882
            \(                  # open paren
883
            [ \t]*              # opt space
884
            <?(.+?)>?           # $href
885
            [ \t]*              # opt space
886
            (?:                 # whole title
887
            (['"])              # $quote
888
            (.*?)               # $title
889
            \3                  # matching quote
890
            )?                  # title is optional
891
            \)
892
        /x
893

  
894
    def inline_markdown_link( text )
895
        text.gsub!( MARKDOWN_LINK_RE ) do |m|
896
            text, url, quote, title = $~[1..4]
897

  
898
            atts = " href=\"#{url}\""
899
            atts << " title=\"#{title}\"" if title
900
            atts = shelve( atts )
901

  
902
            "<a#{atts}>#{text}</a>"
903
        end
904
    end
875
                        atts = " href=\"#{url}\""
876
                        atts << " title=\"#{title}\"" if title
877
                        atts = shelve( atts )
905 878

  
906
    TEXTILE_REFS_RE =  /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
907
    MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
879
                        "<a#{atts}>#{text}</a>"
880
                    end
881
                end
908 882

  
909
    def refs( text )
910
        @rules.each do |rule_name|
911
            method( rule_name ).call( text ) if rule_name.to_s.match? /^refs_/
912
        end
913
    end
883
                MARKDOWN_LINK_RE = /
884
                        \[([^\[\]]+)\]      # $text
885
                        \(                  # open paren
886
                        [ \t]*              # opt space
887
                        <?(.+?)>?           # $href
888
                        [ \t]*              # opt space
889
                        (?:                 # whole title
890
                        (['"])              # $quote
891
                        (.*?)               # $title
892
                        \3                  # matching quote
893
                        )?                  # title is optional
894
                        \)
895
                    /x
896

  
897
                def inline_markdown_link( text )
898
                    text.gsub!( MARKDOWN_LINK_RE ) do |m|
899
                        text, url, quote, title = $~[1..4]
900

  
901
                        atts = " href=\"#{url}\""
902
                        atts << " title=\"#{title}\"" if title
903
                        atts = shelve( atts )
904

  
905
                        "<a#{atts}>#{text}</a>"
906
                    end
907
                end
914 908

  
915
    def refs_textile( text )
916
        text.gsub!( TEXTILE_REFS_RE ) do |m|
917
            flag, url = $~[2..3]
918
            @urlrefs[flag.downcase] = [url, nil]
919
            nil
920
        end
921
    end
909
                TEXTILE_REFS_RE =  /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
910
                MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
922 911

  
923
    def refs_markdown( text )
924
        text.gsub!( MARKDOWN_REFS_RE ) do |m|
925
            flag, url = $~[2..3]
926
            title = $~[6]
927
            @urlrefs[flag.downcase] = [url, title]
928
            nil
929
        end
930
    end
912
                def refs( text )
913
                    @rules.each do |rule_name|
914
                        method( rule_name ).call( text ) if rule_name.to_s.match? /^refs_/
915
                    end
916
                end
931 917

  
932
    def check_refs( text )
933
        ret = @urlrefs[text.downcase] if text
934
        ret || [text, nil]
935
    end
918
                def refs_textile( text )
919
                    text.gsub!( TEXTILE_REFS_RE ) do |m|
920
                        flag, url = $~[2..3]
921
                        @urlrefs[flag.downcase] = [url, nil]
922
                        nil
923
                    end
924
                end
936 925

  
... This diff was truncated because it exceeds the maximum size that can be displayed.
(6-6/16)