~ubuntu-branches/ubuntu/precise/moin/precise-updates

« back to all changes in this revision

Viewing changes to MoinMoin/script/migration/_conv160a_wiki.py

  • Committer: Bazaar Package Importer
  • Author(s): Michael Vogt
  • Date: 2008-11-13 16:45:52 UTC
  • mfrom: (0.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20081113164552-49t6zf2t2o5bqigh
Tags: 1.8.0-1ubuntu1
* Merge from debian unstable, remaining changes:
  - Drop recommendation of python-xml, the packages isn't anymore in
    sys.path.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: iso-8859-1 -*-
 
2
"""
 
3
    MoinMoin - convert content in 1.6.0alpha (rev 1844: 58ebb64243cc) wiki markup to 1.6.0 style
 
4
               by using a modified 1.6.0alpha parser as translator.
 
5
 
 
6
    Assuming we have this "renames" map:
 
7
    -------------------------------------------------------
 
8
    'PAGE', 'some_page'        -> 'some page'
 
9
    'FILE', 'with%20blank.txt' -> 'with blank.txt'
 
10
 
 
11
    Markup transformations needed:
 
12
    -------------------------------------------------------
 
13
    ["some_page"]           -> [[some page]] # renamed
 
14
    [:some_page:some text]  -> [[some page|some text]]
 
15
    [:page:text]            -> [[page|text]]
 
16
                               (with a page not being renamed)
 
17
 
 
18
    attachment:with%20blank.txt -> [[attachment:with blank.txt]]
 
19
    attachment:some_page/with%20blank.txt -> [[attachment:some page/with blank.txt]]
 
20
    The attachment processing should also urllib.unquote the filename (or at
 
21
    least replace %20 by space) and put it into "quotes" if it contains spaces.
 
22
 
 
23
    @copyright: 2007 MoinMoin:JohannesBerg,
 
24
                2007 MoinMoin:ThomasWaldmann
 
25
    @license: GNU GPL, see COPYING for details.
 
26
"""
 
27
 
 
28
import re
 
29
 
 
30
from MoinMoin import i18n
 
31
i18n.wikiLanguages = lambda: {}
 
32
 
 
33
from MoinMoin import config, macro, wikiutil
 
34
from MoinMoin.action import AttachFile
 
35
from MoinMoin.Page import Page
 
36
from MoinMoin.support.python_compatibility import rsplit
 
37
 
 
38
import wikiutil160a
 
39
from text_moin160a_wiki import Parser
 
40
 
 
41
QUOTE_CHARS = u"'\""
 
42
 
 
43
def convert_wiki(request, pagename, intext, renames):
 
44
    """ Convert content written in wiki markup """
 
45
    noeol = False
 
46
    if not intext.endswith('\r\n'):
 
47
        intext += '\r\n'
 
48
        noeol = True
 
49
    c = Converter(request, pagename, intext, renames)
 
50
    result = request.redirectedOutput(c.convert, request)
 
51
    if noeol and result.endswith('\r\n'):
 
52
        result = result[:-2]
 
53
    return result
 
54
 
 
55
 
 
56
STONEAGE_IMAGELINK = False # True for ImageLink(target,image), False for ImageLink(image,target)
 
57
 
 
58
# copied from moin 1.6.0 macro/ImageLink.py (to be safe in case we remove ImageLink some day)
 
59
# ... and slightly modified/refactored for our needs here.
 
60
# hint: using parse_quoted_separated from wikiutil does NOT work here, because we do not have
 
61
#       quoted urls when they contain a '=' char in the 1.5 data input.
 
62
def explore_args(args):
 
63
    """ explore args for positional and keyword parameters """
 
64
    if args:
 
65
        args = args.split(',')
 
66
        args = [arg.strip() for arg in args]
 
67
    else:
 
68
        args = []
 
69
 
 
70
    kw_count = 0
 
71
    kw = {} # keyword args
 
72
    pp = [] # positional parameters
 
73
 
 
74
    kwAllowed = ('width', 'height', 'alt')
 
75
 
 
76
    for arg in args:
 
77
        if '=' in arg:
 
78
            key, value = arg.split('=', 1)
 
79
            key_lowerstr = str(key.lower())
 
80
            # avoid that urls with "=" are interpreted as keyword
 
81
            if key_lowerstr in kwAllowed:
 
82
                kw_count += 1
 
83
                kw[key_lowerstr] = value
 
84
            elif not kw_count and '://' in arg:
 
85
                # assuming that this is the image
 
86
                pp.append(arg)
 
87
        else:
 
88
            pp.append(arg)
 
89
 
 
90
    if STONEAGE_IMAGELINK and len(pp) >= 2:
 
91
        pp[0], pp[1] = pp[1], pp[0]
 
92
 
 
93
    return pp, kw
 
94
 
 
95
 
 
96
class Converter(Parser):
 
97
    def __init__(self, request, pagename, raw, renames):
 
98
        self.pagename = pagename
 
99
        self.raw = raw
 
100
        self.renames = renames
 
101
        self.request = request
 
102
        self._ = None
 
103
        self.in_pre = 0
 
104
 
 
105
        self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(['ImageLink', ] + macro.getNames(self.request.cfg))}
 
106
 
 
107
    # no change
 
108
    def return_word(self, word):
 
109
        return word
 
110
    _emph_repl = return_word
 
111
    _emph_ibb_repl = return_word
 
112
    _emph_ibi_repl = return_word
 
113
    _emph_ib_or_bi_repl = return_word
 
114
    _u_repl = return_word
 
115
    _strike_repl = return_word
 
116
    _sup_repl = return_word
 
117
    _sub_repl = return_word
 
118
    _small_repl = return_word
 
119
    _big_repl = return_word
 
120
    _tt_repl = return_word
 
121
    _tt_bt_repl = return_word
 
122
    _remark_repl = return_word
 
123
    _table_repl = return_word
 
124
    _tableZ_repl = return_word
 
125
    _rule_repl = return_word
 
126
    _smiley_repl = return_word
 
127
    _smileyA_repl = return_word
 
128
    _ent_repl = return_word
 
129
    _ent_numeric_repl = return_word
 
130
    _ent_symbolic_repl = return_word
 
131
    _heading_repl = return_word
 
132
    _email_repl = return_word
 
133
    _notword_repl = return_word
 
134
    _indent_repl = return_word
 
135
    _li_none_repl = return_word
 
136
    _li_repl = return_word
 
137
    _ol_repl = return_word
 
138
    _dl_repl = return_word
 
139
    _comment_repl = return_word
 
140
 
 
141
    # translate pagenames using pagename translation map
 
142
 
 
143
    def _replace(self, key):
 
144
        """ replace a item_name if it is in the renames dict
 
145
            key is either a 2-tuple ('PAGE', pagename)
 
146
            or a 3-tuple ('FILE', pagename, filename)
 
147
        """
 
148
        current_page = self.pagename
 
149
        item_type, page_name, file_name = (key + (None, ))[:3]
 
150
        abs_page_name = wikiutil.AbsPageName(current_page, page_name)
 
151
        if item_type == 'PAGE':
 
152
            key = (item_type, abs_page_name)
 
153
            new_name = self.renames.get(key)
 
154
            if new_name is None:
 
155
                # we don't have an entry in rename map - apply the same magic
 
156
                # to the page name as 1.5 did (" " -> "_") and try again:
 
157
                abs_magic_name = abs_page_name.replace(u' ', u'_')
 
158
                key = (item_type, abs_magic_name)
 
159
                new_name = self.renames.get(key)
 
160
                if new_name is None:
 
161
                    # we didn't find it under the magic name either -
 
162
                    # that means we do not rename it!
 
163
                    new_name = page_name
 
164
            if new_name != page_name and abs_page_name != page_name:
 
165
                # we have to fix the (absolute) new_name to be a relative name (as it was before)
 
166
                new_name = wikiutil.RelPageName(current_page, new_name)
 
167
        elif item_type == 'FILE':
 
168
            key = (item_type, abs_page_name, file_name)
 
169
            new_name = self.renames.get(key)
 
170
            if new_name is None:
 
171
                # we don't have an entry in rename map - apply the same magic
 
172
                # to the page name as 1.5 did (" " -> "_") and try again:
 
173
                abs_magic_name = abs_page_name.replace(u' ', u'_')
 
174
                key = (item_type, abs_magic_name, file_name)
 
175
                new_name = self.renames.get(key)
 
176
                if new_name is None:
 
177
                    # we didn't find it under the magic name either -
 
178
                    # that means we do not rename it!
 
179
                    new_name = file_name
 
180
        return new_name
 
181
 
 
182
    def _replace_target(self, target):
 
183
        target_and_anchor = rsplit(target, '#', 1)
 
184
        if len(target_and_anchor) > 1:
 
185
            target, anchor = target_and_anchor
 
186
            target = self._replace(('PAGE', target))
 
187
            return '%s#%s' % (target, anchor)
 
188
        else:
 
189
            target = self._replace(('PAGE', target))
 
190
            return target
 
191
 
 
192
    # markup conversion
 
193
 
 
194
    def _macro_repl(self, word):
 
195
        # we use [[...]] for links now, macros will be <<...>>
 
196
        macro_rule = ur"""
 
197
            \[\[
 
198
            (?P<macro_name>\w+)
 
199
            (\((?P<macro_args>.*?)\))?
 
200
            \]\]
 
201
        """
 
202
        word = unicode(word) # XXX why is word not unicode before???
 
203
        m = re.match(macro_rule, word, re.X|re.U)
 
204
        macro_name = m.group('macro_name')
 
205
        macro_args = m.group('macro_args')
 
206
        if macro_name == 'ImageLink':
 
207
            fixed, kw = explore_args(macro_args)
 
208
            #print "macro_args=%r" % macro_args
 
209
            #print "fixed=%r, kw=%r" % (fixed, kw)
 
210
            image, target = (fixed + ['', ''])[:2]
 
211
            if image is None:
 
212
                image = ''
 
213
            if target is None:
 
214
                target = ''
 
215
            if '://' not in image:
 
216
                # if it is not a URL, it is meant as attachment
 
217
                image = u'attachment:%s' % image
 
218
            if not target:
 
219
                target = image
 
220
            elif target.startswith('inline:'):
 
221
                target = 'attachment:' + target[7:] # we don't support inline:
 
222
            elif target.startswith('wiki:'):
 
223
                target = target[5:] # drop wiki:
 
224
            image_attrs = []
 
225
            alt = kw.get('alt') or ''
 
226
            width = kw.get('width')
 
227
            if width is not None:
 
228
                image_attrs.append(u"width=%s" % width)
 
229
            height = kw.get('height')
 
230
            if height is not None:
 
231
                image_attrs.append(u"height=%s" % height)
 
232
            image_attrs = u", ".join(image_attrs)
 
233
            if image_attrs:
 
234
                image_attrs = u'|' + image_attrs
 
235
            if alt or image_attrs:
 
236
                alt = u'|' + alt
 
237
            result = u'[[%s|{{%s%s%s}}]]' % (target, image, alt, image_attrs)
 
238
        else:
 
239
            if macro_args:
 
240
                macro_args = u"(%s)" % macro_args
 
241
            else:
 
242
                macro_args = u''
 
243
            result = u"<<%s%s>>" % (macro_name, macro_args)
 
244
        # XXX later check whether some to be renamed pagename is used as macro param
 
245
        return result
 
246
 
 
247
    def _word_repl(self, word, text=None):
 
248
        """Handle WikiNames."""
 
249
        if not text:
 
250
            if wikiutil.isStrictWikiname(word):
 
251
                return word
 
252
            else:
 
253
                return '[[%s]]' % word
 
254
        else: # internal use:
 
255
            return '[[%s|%s]]' % (word, text)
 
256
 
 
257
    def _wikiname_bracket_repl(self, text):
 
258
        """Handle special-char wikinames with link text, like:
 
259
           ["Jim O'Brian" Jim's home page] or ['Hello "world"!' a page with doublequotes]
 
260
        """
 
261
        word = text[1:-1] # strip brackets
 
262
        first_char = word[0]
 
263
        if first_char in QUOTE_CHARS:
 
264
            # split on closing quote
 
265
            target, linktext = word[1:].split(first_char, 1)
 
266
        else: # not quoted
 
267
            # split on whitespace
 
268
            target, linktext = word.split(None, 1)
 
269
        if target:
 
270
            target = self._replace(('PAGE', target))
 
271
            linktext = linktext.strip()
 
272
            if linktext and linktext != target:
 
273
                return '[[%s|%s]]' % (target, linktext)
 
274
            else:
 
275
                return '[[%s]]' % target
 
276
        else:
 
277
            return text
 
278
 
 
279
    ''' old:
 
280
    def _interwiki_repl(self, word):
 
281
        """Handle InterWiki links."""
 
282
        wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, word)
 
283
        if wikitag_bad:
 
284
            return word
 
285
        else:
 
286
            wikiname, pagename = word.split(':', 1)
 
287
            pagename = wikiutil.url_unquote(pagename) # maybe someone has used %20 for blanks in pagename
 
288
            camelcase = wikiutil.isStrictWikiname(pagename)
 
289
            if wikiname in ('Self', self.request.cfg.interwikiname):
 
290
                pagename = self._replace(('PAGE', pagename))
 
291
                if camelcase:
 
292
                    return '%s' % pagename # optimize special case
 
293
                else:
 
294
                    return '[[%s]]' % pagename # optimize special case
 
295
            else:
 
296
                if ' ' in pagename: # we could get a ' '  by urlunquoting
 
297
                    return '[[%s:%s]]' % (wikiname, pagename)
 
298
                else:
 
299
                    return '%s:%s' % (wikiname, pagename)
 
300
    '''
 
301
 
 
302
    def _interwiki_repl(self, word):
 
303
        """Handle InterWiki links."""
 
304
        wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, word)
 
305
        if wikitag_bad:
 
306
            return word
 
307
        else:
 
308
            return self.interwiki("wiki:" + word)
 
309
 
 
310
    def interwiki(self, target_and_text, **kw):
 
311
        scheme, rest = target_and_text.split(':', 1)
 
312
        wikiname, pagename, text = wikiutil160a.split_wiki(rest)
 
313
        if text:
 
314
            text = '|' + text
 
315
 
 
316
        if (pagename.startswith(wikiutil.CHILD_PREFIX) or # fancy link to subpage [wiki:/SubPage text]
 
317
            Page(self.request, pagename).exists()): # fancy link to local page [wiki:LocalPage text]
 
318
            pagename = wikiutil.url_unquote(pagename)
 
319
            pagename = self._replace_target(pagename)
 
320
            return '[[%s%s]]' % (pagename, text)
 
321
 
 
322
        if wikiname in ('Self', self.request.cfg.interwikiname, ''): # [wiki:Self:LocalPage text] or [:LocalPage:text]
 
323
            pagename = wikiutil.url_unquote(pagename)
 
324
            pagename = self._replace_target(pagename)
 
325
            camelcase = wikiutil.isStrictWikiname(pagename)
 
326
            if camelcase and text == pagename:
 
327
                return '%s' % pagename # optimize special case
 
328
            else:
 
329
                return '[[%s%s]]' % (pagename, text)
 
330
 
 
331
        wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, wikiname+':')
 
332
        if wikitag_bad: # likely we got some /InterWiki as wikitail, we don't want that!
 
333
            pagename = wikiutil.url_unquote(pagename)
 
334
            pagename = self._replace_target(pagename)
 
335
            wikitail = pagename
 
336
        else: # good
 
337
            wikitail = wikiutil.url_unquote(pagename)
 
338
 
 
339
        # link to self?
 
340
        if wikiutil.isPicture(wikitail):
 
341
            return '{{%s:%s%s}}' % (wikitag, wikitail, text)
 
342
        else:
 
343
            if ' ' not in wikitail and not text:
 
344
                return '%s:%s' % (wikitag, wikitail)
 
345
            else:
 
346
                return '[[%s:%s%s]]' % (wikitag, wikitail, text)
 
347
 
 
348
    ''' old:
 
349
    def interwiki(self, url_and_text):
 
350
        # keep track of whether this is a self-reference, so links
 
351
        # are always shown even the page doesn't exist.
 
352
        wikiname, pagename = wikiutil.split_wiki(url)
 
353
    '''
 
354
    '''
 
355
    def attachment(self, url_and_text):
 
356
        """ This gets called on attachment URLs. """
 
357
        if len(url_and_text) == 1:
 
358
            url = url_and_text[0]
 
359
            text = ''
 
360
        else:
 
361
            url, text = url_and_text
 
362
            text = '|' + text
 
363
 
 
364
        scheme, fname = url.split(":", 1)
 
365
        #scheme, fname, text = wikiutil.split_wiki(target_and_text)
 
366
 
 
367
        pagename, fname = AttachFile.absoluteName(fname, self.pagename)
 
368
        from_this_page = pagename == self.pagename
 
369
        fname = self._replace(('FILE', pagename, fname))
 
370
        fname = wikiutil.url_unquote(fname, want_unicode=True)
 
371
        fname = self._replace(('FILE', pagename, fname))
 
372
        pagename = self._replace(('PAGE', pagename))
 
373
        if from_this_page:
 
374
            name = fname
 
375
        else:
 
376
            name = "%s/%s" % (pagename, fname)
 
377
 
 
378
        if scheme == 'drawing':
 
379
            return "{{drawing:%s%s}}" % (name, text)
 
380
 
 
381
        # check for image URL, and possibly return IMG tag
 
382
        # (images are always inlined, just like for other URLs)
 
383
        if wikiutil.isPicture(name):
 
384
            return "{{attachment:%s%s}}" % (name, text)
 
385
 
 
386
        # inline the attachment
 
387
        if scheme == 'inline':
 
388
            return '{{attachment:%s%s}}' % (name, text)
 
389
        else: # 'attachment'
 
390
            return '[[attachment:%s%s]]' % (name, text)
 
391
    '''
 
392
 
 
393
    def attachment(self, target_and_text, **kw):
 
394
        """ This gets called on attachment URLs """
 
395
        _ = self._
 
396
        scheme, fname, text = wikiutil160a.split_wiki(target_and_text)
 
397
        fn_txt = fname
 
398
        if text:
 
399
            fn_txt += '|' + text
 
400
 
 
401
        if scheme == 'drawing':
 
402
            return "{{drawing:%s}}" % fn_txt
 
403
 
 
404
        # check for image, and possibly return IMG tag (images are always inlined)
 
405
        if not kw.get('pretty_url', 0) and wikiutil.isPicture(fname):
 
406
            return "{{attachment:%s}}" % fn_txt
 
407
 
 
408
        # inline the attachment
 
409
        if scheme == 'inline':
 
410
            return '{{attachment:%s}}' % fn_txt
 
411
 
 
412
        return '[[attachment:%s]]' % fn_txt
 
413
 
 
414
    def _url_repl(self, word):
 
415
        """Handle literal URLs including inline images."""
 
416
        scheme = word.split(":", 1)[0]
 
417
 
 
418
        if scheme == 'wiki':
 
419
            return self.interwiki(word)
 
420
        if scheme in self.attachment_schemas:
 
421
            return '%s' % self.attachment(word)
 
422
 
 
423
        if wikiutil.isPicture(word): # magic will go away in 1.6!
 
424
            return '{{%s}}' % word # new markup for inline images
 
425
        else:
 
426
            return word
 
427
 
 
428
 
 
429
    def _url_bracket_repl(self, word):
 
430
        """Handle bracketed URLs."""
 
431
        word = word[1:-1] # strip brackets
 
432
 
 
433
        # Local extended link? [:page name:link text] XXX DEPRECATED
 
434
        if word[0] == ':':
 
435
            words = word[1:].split(':', 1)
 
436
            pagename = self._replace(('PAGE', words[0]))
 
437
            if len(words) == 1 or len(words) == 2 and not words[1]:
 
438
                return '[[%s]]' % (pagename, )
 
439
            else:
 
440
                return '[[%s|%s]]' % (pagename, words[1])
 
441
 
 
442
        scheme_and_rest = word.split(":", 1)
 
443
        if len(scheme_and_rest) == 1: # no scheme
 
444
            # Traditional split on space
 
445
            words = word.split(None, 1)
 
446
            if len(words) == 1:
 
447
                words = words * 2
 
448
 
 
449
            if words[0].startswith('#'): # anchor link
 
450
                if words[0] == words[1]:
 
451
                    return '[[%s]]' % words[0]
 
452
                else:
 
453
                    return '[[%s|%s]]' % tuple(words)
 
454
        else:
 
455
            scheme, rest = scheme_and_rest
 
456
            if scheme == "wiki":
 
457
                return self.interwiki(word, pretty_url=1)
 
458
            if scheme in self.attachment_schemas:
 
459
                return self.attachment(word)
 
460
 
 
461
            words = word.split(None, 1)
 
462
            if len(words) == 1:
 
463
                words = words * 2
 
464
 
 
465
        target, text = words
 
466
        if wikiutil.isPicture(text) and re.match(self.url_rule, text):
 
467
            return '[[%s|{{%s}}]]' % (target, text)
 
468
        else:
 
469
            if target == text:
 
470
                return '[[%s]]' % target
 
471
            else:
 
472
                return '[[%s|%s]]' % (target, text)
 
473
 
 
474
 
 
475
    '''
 
476
    def _url_bracket_repl(self, word):
 
477
        """Handle bracketed URLs."""
 
478
        word = word[1:-1] # strip brackets
 
479
 
 
480
        # Local extended link?
 
481
        if word[0] == ':':
 
482
            words = word[1:].split(':', 1)
 
483
            link, text = (words + ['', ''])[:2]
 
484
            if link.strip() == text.strip():
 
485
                text = ''
 
486
            link = self._replace_target(link)
 
487
            if text:
 
488
                text = '|' + text
 
489
            return '[[%s%s]]' % (link, text)
 
490
 
 
491
        # Traditional split on space
 
492
        words = word.split(None, 1)
 
493
        if words[0][0] == '#':
 
494
            # anchor link
 
495
            link, text = (words + ['', ''])[:2]
 
496
            if link.strip() == text.strip():
 
497
                text = ''
 
498
            #link = self._replace_target(link)
 
499
            if text:
 
500
                text = '|' + text
 
501
            return '[[%s%s]]' % (link, text)
 
502
 
 
503
        scheme = words[0].split(":", 1)[0]
 
504
        if scheme == "wiki":
 
505
            return self.interwiki(words)
 
506
            #scheme, wikiname, pagename, text = self.interwiki(word)
 
507
            #print "%r %r %r %r" % (scheme, wikiname, pagename, text)
 
508
            #if wikiname in ('Self', self.request.cfg.interwikiname, ''):
 
509
            #    if text:
 
510
            #        text = '|' + text
 
511
            #    return '[[%s%s]]' % (pagename, text)
 
512
            #else:
 
513
            #    if text:
 
514
            #        text = '|' + text
 
515
            #    return "[[%s:%s%s]]" % (wikiname, pagename, text)
 
516
        if scheme in self.attachment_schemas:
 
517
            m = self.attachment(words)
 
518
            if m.startswith('{{') and m.endswith('}}'):
 
519
                # with url_bracket markup, 1.5.8 parser does not embed, but link!
 
520
                m = '[[%s]]' % m[2:-2]
 
521
            return m
 
522
 
 
523
        target, desc = (words + ['', ''])[:2]
 
524
        if wikiutil.isPicture(desc) and re.match(self.url_rule, desc):
 
525
            #return '[[%s|{{%s|%s}}]]' % (words[0], words[1], words[0])
 
526
            return '[[%s|{{%s}}]]' % (target, desc)
 
527
        else:
 
528
            if desc:
 
529
                desc = '|' + desc
 
530
            return '[[%s%s]]' % (target, desc)
 
531
    '''
 
532
 
 
533
    def _pre_repl(self, word):
 
534
        w = word.strip()
 
535
        if w == '{{{' and not self.in_pre:
 
536
            self.in_pre = True
 
537
        elif w == '}}}' and self.in_pre:
 
538
            self.in_pre = False
 
539
        return word
 
540
 
 
541
    def _processor_repl(self, word):
 
542
        self.in_pre = True
 
543
        return word
 
544
 
 
545
    def scan(self, scan_re, line):
 
546
        """ Scans one line - append text before match, invoke replace() with match, and add text after match.  """
 
547
        result = []
 
548
        lastpos = 0
 
549
 
 
550
        for match in scan_re.finditer(line):
 
551
            # Add text before the match
 
552
            if lastpos < match.start():
 
553
                result.append(line[lastpos:match.start()])
 
554
            # Replace match with markup
 
555
            result.append(self.replace(match))
 
556
            lastpos = match.end()
 
557
 
 
558
        # Add remainder of the line
 
559
        result.append(line[lastpos:])
 
560
        return u''.join(result)
 
561
 
 
562
 
 
563
    def replace(self, match):
 
564
        """ Replace match using type name """
 
565
        result = []
 
566
        for _type, hit in match.groupdict().items():
 
567
            if hit is not None and not _type in ["hmarker", ]:
 
568
                # Get replace method and replace hit
 
569
                replace = getattr(self, '_' + _type + '_repl')
 
570
                # print _type, hit
 
571
                result.append(replace(hit))
 
572
                return ''.join(result)
 
573
        else:
 
574
            # We should never get here
 
575
            import pprint
 
576
            raise Exception("Can't handle match %r\n%s\n%s" % (
 
577
                match,
 
578
                pprint.pformat(match.groupdict()),
 
579
                pprint.pformat(match.groups()),
 
580
            ))
 
581
 
 
582
        return ""
 
583
 
 
584
    def convert(self, request):
 
585
        """ For each line, scan through looking for magic
 
586
            strings, outputting verbatim any intervening text.
 
587
        """
 
588
        self.request = request
 
589
        # prepare regex patterns
 
590
        rules = self.formatting_rules.replace('\n', '|')
 
591
        if self.request.cfg.bang_meta:
 
592
            rules = ur'(?P<notword>!%(word_rule)s)|%(rules)s' % {
 
593
                'word_rule': self.word_rule,
 
594
                'rules': rules,
 
595
            }
 
596
        pre_rules = r'''(?P<pre>\}\}\})'''
 
597
        pre_scan_re = re.compile(pre_rules, re.UNICODE)
 
598
        scan_re = re.compile(rules, re.UNICODE)
 
599
        eol_re = re.compile(r'\r?\n', re.UNICODE)
 
600
 
 
601
        rawtext = self.raw
 
602
 
 
603
        # remove last item because it's guaranteed to be empty
 
604
        self.lines = eol_re.split(rawtext)[:-1]
 
605
        self.in_processing_instructions = True
 
606
 
 
607
        # Main loop
 
608
        for line in self.lines:
 
609
            # ignore processing instructions
 
610
            if self.in_processing_instructions:
 
611
                found = False
 
612
                for pi in ("##", "#format", "#refresh", "#redirect", "#deprecated",
 
613
                           "#pragma", "#form", "#acl", "#language"):
 
614
                    if line.lower().startswith(pi):
 
615
                        self.request.write(line + '\r\n')
 
616
                        found = True
 
617
                        break
 
618
                if not found:
 
619
                    self.in_processing_instructions = False
 
620
                else:
 
621
                    continue # do not parse this line
 
622
            if not line.strip():
 
623
                self.request.write(line + '\r\n')
 
624
            else:
 
625
                # Scan line, format and write
 
626
                scanning_re = self.in_pre and pre_scan_re or scan_re
 
627
                formatted_line = self.scan(scanning_re, line)
 
628
                self.request.write(formatted_line + '\r\n')
 
629