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