~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/parser/text_creole.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mfrom: (0.9.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080622211713-fpo2zrq3s5dfecxg
Tags: 1.7.0-3
Simplify /etc/moin/wikilist format: "USER URL" (drop unneeded middle
CONFIG_DIR that was wrongly advertised as DATA_DIR).  Make
moin-mass-migrate handle both formats and warn about deprecation of
the old one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: iso-8859-1 -*-
 
2
"""
 
3
    MoinMoin - Creole wiki markup parser
 
4
 
 
5
    See http://wikicreole.org/ for latest specs.
 
6
 
 
7
    Notes:
 
8
    * No markup allowed in headings.
 
9
      Creole 1.0 does not require us to support this.
 
10
    * No markup allowed in table headings.
 
11
      Creole 1.0 does not require us to support this.
 
12
    * No (non-bracketed) generic url recognition: this is "mission impossible"
 
13
      except if you want to risk lots of false positives. Only known protocols
 
14
      are recognized.
 
15
    * We do not allow ":" before "//" italic markup to avoid urls with
 
16
      unrecognized schemes (like wtf://server/path) triggering italic rendering
 
17
      for the rest of the paragraph.
 
18
 
 
19
    @copyright: 2007 MoinMoin:RadomirDopieralski (creole 0.5 implementation),
 
20
                2007 MoinMoin:ThomasWaldmann (updates)
 
21
    @license: GNU GPL, see COPYING for details.
 
22
"""
 
23
 
 
24
import re
 
25
import StringIO
 
26
from MoinMoin import config, wikiutil
 
27
from MoinMoin.macro import Macro
 
28
from MoinMoin.support.python_compatibility import rsplit # Needed for python 2.3
 
29
from _creole import Parser as CreoleParser
 
30
 
 
31
Dependencies = []
 
32
 
 
33
class Parser:
 
34
    """
 
35
    Glue the DocParser and DocEmitter with the
 
36
    MoinMoin current API.
 
37
    """
 
38
 
 
39
    # Enable caching
 
40
    caching = 1
 
41
    Dependencies = Dependencies
 
42
 
 
43
    def __init__(self, raw, request, **kw):
 
44
        """Create a minimal Parser object with required attributes."""
 
45
 
 
46
        self.request = request
 
47
        self.form = request.form
 
48
        self.raw = raw
 
49
 
 
50
    def format(self, formatter):
 
51
        """Create and call the true parser and emitter."""
 
52
 
 
53
        document = CreoleParser(self.raw).parse()
 
54
        result = Emitter(document, formatter, self.request, Macro(self)).emit()
 
55
        self.request.write(result)
 
56
 
 
57
class Rules:
 
58
    # For the link targets:
 
59
    proto = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
 
60
    extern = r'(?P<extern_addr>(?P<extern_proto>%s):.*)' % proto
 
61
    attach = r'''
 
62
            (?P<attach_scheme> attachment | drawing | image ):
 
63
            (?P<attach_addr> .* )
 
64
        '''
 
65
    interwiki = r'''
 
66
            (?P<inter_wiki> [A-Z][a-zA-Z]+ ) :
 
67
            (?P<inter_page> .* )
 
68
        '''
 
69
    page = r'(?P<page_name> .* )'
 
70
 
 
71
 
 
72
class Emitter:
 
73
    """
 
74
    Generate the output for the document
 
75
    tree consisting of DocNodes.
 
76
    """
 
77
 
 
78
    addr_re = re.compile('|'.join([
 
79
            Rules.extern,
 
80
            Rules.attach,
 
81
            Rules.interwiki,
 
82
            Rules.page
 
83
        ]), re.X | re.U) # for addresses
 
84
 
 
85
    def __init__(self, root, formatter, request, macro):
 
86
        self.root = root
 
87
        self.formatter = formatter
 
88
        self.request = request
 
89
        self.form = request.form
 
90
        self.macro = macro
 
91
 
 
92
    def get_text(self, node):
 
93
        """Try to emit whatever text is in the node."""
 
94
 
 
95
        try:
 
96
            return node.children[0].content or ''
 
97
        except:
 
98
            return node.content or ''
 
99
 
 
100
    # *_emit methods for emitting nodes of the document:
 
101
 
 
102
    def document_emit(self, node):
 
103
        return self.emit_children(node)
 
104
 
 
105
    def text_emit(self, node):
 
106
        return self.formatter.text(node.content or '')
 
107
 
 
108
    def separator_emit(self, node):
 
109
        return self.formatter.rule()
 
110
 
 
111
    def paragraph_emit(self, node):
 
112
        return ''.join([
 
113
            self.formatter.paragraph(1),
 
114
            self.emit_children(node),
 
115
            self.formatter.paragraph(0),
 
116
        ])
 
117
 
 
118
    def bullet_list_emit(self, node):
 
119
        return ''.join([
 
120
            self.formatter.bullet_list(1),
 
121
            self.emit_children(node),
 
122
            self.formatter.bullet_list(0),
 
123
        ])
 
124
 
 
125
    def number_list_emit(self, node):
 
126
        return ''.join([
 
127
            self.formatter.number_list(1),
 
128
            self.emit_children(node),
 
129
            self.formatter.number_list(0),
 
130
        ])
 
131
 
 
132
    def list_item_emit(self, node):
 
133
        return ''.join([
 
134
            self.formatter.listitem(1),
 
135
            self.emit_children(node),
 
136
            self.formatter.listitem(0),
 
137
        ])
 
138
 
 
139
# Not used
 
140
#    def definition_list_emit(self, node):
 
141
#        return ''.join([
 
142
#            self.formatter.definition_list(1),
 
143
#            self.emit_children(node),
 
144
#            self.formatter.definition_list(0),
 
145
#        ])
 
146
 
 
147
# Not used
 
148
#    def term_emit(self, node):
 
149
#        return ''.join([
 
150
#            self.formatter.definition_term(1),
 
151
#            self.emit_children(node),
 
152
#            self.formatter.definition_term(0),
 
153
#        ])
 
154
 
 
155
# Not used
 
156
#    def definition_emit(self, node):
 
157
#        return ''.join([
 
158
#            self.formatter.definition_desc(1),
 
159
#            self.emit_children(node),
 
160
#            self.formatter.definition_desc(0),
 
161
#        ])
 
162
 
 
163
    def table_emit(self, node):
 
164
        return ''.join([
 
165
            self.formatter.table(1, attrs=getattr(node, 'attrs', '')),
 
166
            self.emit_children(node),
 
167
            self.formatter.table(0),
 
168
        ])
 
169
 
 
170
    def table_row_emit(self, node):
 
171
        return ''.join([
 
172
            self.formatter.table_row(1, attrs=getattr(node, 'attrs', '')),
 
173
            self.emit_children(node),
 
174
            self.formatter.table_row(0),
 
175
        ])
 
176
 
 
177
    def table_cell_emit(self, node):
 
178
        return ''.join([
 
179
            self.formatter.table_cell(1, attrs=getattr(node, 'attrs', '')),
 
180
            self.emit_children(node),
 
181
            self.formatter.table_cell(0),
 
182
        ])
 
183
 
 
184
    def table_head_emit(self, node):
 
185
        return ''.join([
 
186
            self.formatter.rawHTML('<th>'),
 
187
            self.emit_children(node),
 
188
            self.formatter.rawHTML('</th>'),
 
189
        ])
 
190
 
 
191
    def emphasis_emit(self, node):
 
192
        return ''.join([
 
193
            self.formatter.emphasis(1),
 
194
            self.emit_children(node),
 
195
            self.formatter.emphasis(0),
 
196
        ])
 
197
 
 
198
# Not used
 
199
#    def quote_emit(self, node):
 
200
#        return ''.join([
 
201
#            self.formatter.rawHTML('<q>'),
 
202
#            self.emit_children(node),
 
203
#            self.formatter.rawHTML('</q>'),
 
204
#        ])
 
205
 
 
206
    def strong_emit(self, node):
 
207
        return ''.join([
 
208
            self.formatter.strong(1),
 
209
            self.emit_children(node),
 
210
            self.formatter.strong(0),
 
211
        ])
 
212
 
 
213
# Not used
 
214
#    def smiley_emit(self, node):
 
215
#        return self.formatter.smiley(node.content)
 
216
 
 
217
    def header_emit(self, node):
 
218
        import sha
 
219
        pntt = '%s%s%d' % (self.formatter.page.page_name,
 
220
            self.get_text(node), node.level)
 
221
        ident = "head-%s" % sha.new(pntt.encode(config.charset)).hexdigest()
 
222
        return ''.join([
 
223
            self.formatter.heading(1, node.level, id=ident),
 
224
            self.formatter.text(node.content or ''),
 
225
            self.formatter.heading(0, node.level),
 
226
        ])
 
227
 
 
228
    def code_emit(self, node):
 
229
# XXX The current formatter will replace all spaces with &nbsp;, so we need
 
230
# to use rawHTML instead, until that is fixed.
 
231
#        return ''.join([
 
232
#            self.formatter.code(1),
 
233
#            self.formatter.text(node.content or ''),
 
234
#            self.formatter.code(0),
 
235
#        ])
 
236
        return ''.join([
 
237
            self.formatter.rawHTML('<tt>'),
 
238
            self.formatter.text(node.content or ''),
 
239
            self.formatter.rawHTML('</tt>'),
 
240
        ])
 
241
 
 
242
# Not used
 
243
#    def abbr_emit(self, node):
 
244
#        return ''.join([
 
245
#            self.formatter.rawHTML('<abbr title="%s">' % node.title),
 
246
#            self.formatter.text(node.content or ''),
 
247
#            self.formatter.rawHTML('</abbr>'),
 
248
#        ])
 
249
 
 
250
    def link_emit(self, node):
 
251
        target = node.content
 
252
        m = self.addr_re.match(target)
 
253
        if m:
 
254
            if m.group('page_name'):
 
255
                # link to a page
 
256
                word = m.group('page_name')
 
257
                if word.startswith(wikiutil.PARENT_PREFIX):
 
258
                    word = word[wikiutil.PARENT_PREFIX_LEN:]
 
259
                elif word.startswith(wikiutil.CHILD_PREFIX):
 
260
                    word = "%s/%s" % (self.formatter.page.page_name,
 
261
                        word[wikiutil.CHILD_PREFIX_LEN:])
 
262
                # handle anchors
 
263
                parts = rsplit(word, "#", 1)
 
264
                anchor = ""
 
265
                if len(parts) == 2:
 
266
                    word, anchor = parts
 
267
                return ''.join([
 
268
                    self.formatter.pagelink(1, word, anchor=anchor),
 
269
                    self.emit_children(node) or self.formatter.text(target),
 
270
                    self.formatter.pagelink(0, word),
 
271
                ])
 
272
            elif m.group('extern_addr'):
 
273
                # external link
 
274
                address = m.group('extern_addr')
 
275
                proto = m.group('extern_proto')
 
276
                return ''.join([
 
277
                    self.formatter.url(1, address, css=proto),
 
278
                    self.emit_children(node) or self.formatter.text(target),
 
279
                    self.formatter.url(0),
 
280
                ])
 
281
            elif m.group('inter_wiki'):
 
282
                # interwiki link
 
283
                wiki = m.group('inter_wiki')
 
284
                page = m.group('inter_page')
 
285
                return ''.join([
 
286
                    self.formatter.interwikilink(1, wiki, page),
 
287
                    self.emit_children(node) or self.formatter.text(page),
 
288
                    self.formatter.interwikilink(0),
 
289
                ])
 
290
            elif m.group('attach_scheme'):
 
291
                # link to an attachment
 
292
                scheme = m.group('attach_scheme')
 
293
                attachment = m.group('attach_addr')
 
294
                url = wikiutil.url_unquote(attachment, want_unicode=True)
 
295
                text = self.get_text(node)
 
296
                return ''.join([
 
297
                        self.formatter.attachment_link(1, url),
 
298
                        self.formatter.text(text),
 
299
                        self.formatter.attachment_link(0)
 
300
                    ])
 
301
        return "".join(["[[", self.formatter.text(target), "]]"])
 
302
 
 
303
# Not used
 
304
#    def anchor_link_emit(self, node):
 
305
#        return ''.join([
 
306
#            self.formatter.url(1, node.content, css='anchor'),
 
307
#            self.emit_children(node),
 
308
#            self.formatter.url(0),
 
309
#        ])
 
310
 
 
311
    def image_emit(self, node):
 
312
        target = node.content
 
313
        text = self.get_text(node)
 
314
        m = self.addr_re.match(target)
 
315
        if m:
 
316
            if m.group('page_name'):
 
317
                # inserted anchors
 
318
                url = wikiutil.url_unquote(target, want_unicode=True)
 
319
                if target.startswith('#'):
 
320
                    return self.formatter.rawHTML(u'<a name="%s"></a>' % url[1:])
 
321
                # default to images
 
322
                return self.formatter.attachment_image(
 
323
                    url, alt=text, html_class='image')
 
324
            elif m.group('extern_addr'):
 
325
                # external link
 
326
                address = m.group('extern_addr')
 
327
                proto = m.group('extern_proto')
 
328
                url = wikiutil.url_unquote(address, want_unicode=True)
 
329
                return self.formatter.image(
 
330
                    src=url, alt=text, html_class='external_image')
 
331
            elif m.group('attach_scheme'):
 
332
                # link to an attachment
 
333
                scheme = m.group('attach_scheme')
 
334
                attachment = m.group('attach_addr')
 
335
                url = wikiutil.url_unquote(attachment, want_unicode=True)
 
336
                if scheme == 'image':
 
337
                    return self.formatter.attachment_image(
 
338
                        url, alt=text, html_class='image')
 
339
                elif scheme == 'drawing':
 
340
                    return self.formatter.attachment_drawing(url, text)
 
341
                else:
 
342
                    pass
 
343
            elif m.group('inter_wiki'):
 
344
                # interwiki link
 
345
                pass
 
346
#        return "".join(["{{", self.formatter.text(target), "}}"])
 
347
        url = wikiutil.url_unquote(node.content, want_unicode=True)
 
348
        return self.formatter.attachment_inlined(url, text)
 
349
 
 
350
# Not used
 
351
#    def drawing_emit(self, node):
 
352
#        url = wikiutil.url_unquote(node.content, want_unicode=True)
 
353
#        text = self.get_text(node)
 
354
#        return self.formatter.attachment_drawing(url, text)
 
355
 
 
356
# Not used
 
357
#    def figure_emit(self, node):
 
358
#        text = self.get_text(node)
 
359
#        url = wikiutil.url_unquote(node.content, want_unicode=True)
 
360
#        return ''.join([
 
361
#            self.formatter.rawHTML('<div class="figure">'),
 
362
#            self.get_image(url, text), self.emit_children(node),
 
363
#            self.formatter.rawHTML('</div>'),
 
364
#        ])
 
365
 
 
366
# Not used
 
367
#    def bad_link_emit(self, node):
 
368
#        return self.formatter.text(''.join([
 
369
#            '[[',
 
370
#            node.content or '',
 
371
#            ']]',
 
372
#        ]))
 
373
 
 
374
    def macro_emit(self, node):
 
375
        macro_name = node.content
 
376
        args = node.args
 
377
        return self.formatter.macro(self.macro, macro_name, args)
 
378
 
 
379
# Not used
 
380
#    def section_emit(self, node):
 
381
#        return ''.join([
 
382
#            self.formatter.rawHTML(
 
383
#                '<div class="%s" style="%s">' % (node.sect, node.style)),
 
384
#            self.emit_children(node),
 
385
#            self.formatter.rawHTML('</div>'),
 
386
#        ])
 
387
 
 
388
    def break_emit(self, node):
 
389
        return self.formatter.linebreak(preformatted=0)
 
390
 
 
391
# Not used
 
392
#    def blockquote_emit(self, node):
 
393
#        return ''.join([
 
394
#            self.formatter.rawHTML('<blockquote>'),
 
395
#            self.emit_children(node),
 
396
#            self.formatter.rawHTML('</blockquote>'),
 
397
#        ])
 
398
 
 
399
    def preformatted_emit(self, node):
 
400
        parser_name = getattr(node, 'sect', '')
 
401
        if parser_name:
 
402
            # The formatter.parser will *sometimes* just return the result
 
403
            # and *sometimes* try to write it directly. We need to take both
 
404
            # cases into account!
 
405
            lines = node.content.split(u'\n')
 
406
            buf = StringIO.StringIO()
 
407
            try:
 
408
                try:
 
409
                    self.request.redirect(buf)
 
410
                    ret = self.formatter.parser(parser_name, lines)
 
411
                finally:
 
412
                    self.request.redirect()
 
413
                buf.flush()
 
414
                writ = buf.getvalue()
 
415
                buf.close()
 
416
                return ret + writ
 
417
            except wikiutil.PluginMissingError:
 
418
                pass
 
419
        return ''.join([
 
420
            self.formatter.preformatted(1),
 
421
            self.formatter.text(node.content),
 
422
            self.formatter.preformatted(0),
 
423
        ])
 
424
 
 
425
    def default_emit(self, node):
 
426
        """Fallback function for emitting unknown nodes."""
 
427
 
 
428
        return ''.join([
 
429
            self.formatter.preformatted(1),
 
430
            self.formatter.text('<%s>\n' % node.kind),
 
431
            self.emit_children(node),
 
432
            self.formatter.preformatted(0),
 
433
        ])
 
434
 
 
435
    def emit_children(self, node):
 
436
        """Emit all the children of a node."""
 
437
 
 
438
        return ''.join([self.emit_node(child) for child in node.children])
 
439
 
 
440
    def emit_node(self, node):
 
441
        """Emit a single node."""
 
442
 
 
443
        emit = getattr(self, '%s_emit' % node.kind, self.default_emit)
 
444
        return emit(node)
 
445
 
 
446
    def emit(self):
 
447
        """Emit the document represented by self.root DOM tree."""
 
448
 
 
449
        # Try to disable 'smart' formatting if possible
 
450
        magic_save = getattr(self.formatter, 'no_magic', False)
 
451
        self.formatter.no_magic = True
 
452
        output = '\n'.join([
 
453
            self.emit_node(self.root),
 
454
        ])
 
455
        # restore 'smart' formatting if it was set
 
456
        self.formatter.no_magic = magic_save
 
457
        return output