Back to index

moin  1.9.0~rc2
__init__.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - Formatter Package and FormatterBase
00004 
00005     See "base.py" for the formatter interface.
00006 
00007     @copyright: 2000-2004 by Juergen Hermann <jh@web.de>
00008     @license: GNU GPL, see COPYING for details.
00009 """
00010 import re
00011 
00012 from MoinMoin import log
00013 logging = log.getLogger(__name__)
00014 
00015 from MoinMoin.util import pysupport
00016 from MoinMoin import wikiutil
00017 
00018 modules = pysupport.getPackageModules(__file__)
00019 
00020 
00021 class FormatterBase:
00022     """ This defines the output interface used all over the rest of the code.
00023 
00024         Note that no other means should be used to generate _content_ output,
00025         while navigational elements (HTML page header/footer) and the like
00026         can be printed directly without violating output abstraction.
00027     """
00028 
00029     hardspace = ' '
00030 
00031     def __init__(self, request, **kw):
00032         self.request = request
00033         self._ = request.getText
00034 
00035         self._store_pagelinks = kw.get('store_pagelinks', 0)
00036         self._terse = kw.get('terse', 0)
00037         self.pagelinks = []
00038         self.in_p = 0
00039         self.in_pre = 0
00040         self._highlight_re = None
00041         self._base_depth = 0
00042 
00043     def set_highlight_re(self, hi_re=None):
00044         """ set the highlighting regular expression (e.g. for search terms)
00045 
00046         @param hi_re: either a valid re as str/unicode (you may want to use
00047                       re.escape before passing generic strings!) or a compiled
00048                       re object. raises re.error for invalid re.
00049         """
00050         if isinstance(hi_re, (str, unicode)):
00051             hi_re = re.compile(hi_re, re.U + re.IGNORECASE)
00052         self._highlight_re = hi_re
00053 
00054     def lang(self, on, lang_name):
00055         return ""
00056 
00057     def setPage(self, page):
00058         self.page = page
00059 
00060     def sysmsg(self, on, **kw):
00061         """ Emit a system message (embed it into the page).
00062 
00063             Normally used to indicate disabled options, or invalid markup.
00064         """
00065         return ""
00066 
00067     # Document Level #####################################################
00068 
00069     def startDocument(self, pagename):
00070         return ""
00071 
00072     def endDocument(self):
00073         return ""
00074 
00075     def startContent(self, content_id="content", **kw):
00076         if self.page:
00077             self.request.uid_generator.begin(self.page.page_name)
00078         return ""
00079 
00080     def endContent(self):
00081         if self.page:
00082             self.request.uid_generator.end()
00083         return ""
00084 
00085     # Links ##############################################################
00086 
00087     def pagelink(self, on, pagename='', page=None, **kw):
00088         """ make a link to page <pagename>. Instead of supplying a pagename,
00089             it is also possible to give a live Page object, then page.page_name
00090             will be used.
00091         """
00092         if not self._store_pagelinks or not on or kw.get('generated'):
00093             return ''
00094         if not pagename and page:
00095             pagename = page.page_name
00096         pagename = wikiutil.normalize_pagename(pagename, self.request.cfg)
00097         if pagename and pagename not in self.pagelinks:
00098             self.pagelinks.append(pagename)
00099 
00100     def interwikilink(self, on, interwiki='', pagename='', **kw):
00101         """ calls pagelink() for internal interwikilinks
00102             to make sure they get counted for self.pagelinks.
00103             IMPORTANT: on and off must be called with same parameters, see
00104                        also the text_html formatter.
00105         """
00106         wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_interwiki(self.request, interwiki, pagename)
00107         if wikitag == 'Self' or wikitag == self.request.cfg.interwikiname:
00108             return self.pagelink(on, wikitail, **kw)
00109         return ''
00110 
00111     def url(self, on, url=None, css=None, **kw):
00112         raise NotImplementedError
00113 
00114     # Attachments ######################################################
00115 
00116     def attachment_link(self, on, url=None, **kw):
00117         raise NotImplementedError
00118     def attachment_image(self, url, **kw):
00119         raise NotImplementedError
00120     def attachment_drawing(self, url, text, **kw):
00121         raise NotImplementedError
00122 
00123     def attachment_inlined(self, url, text, **kw):
00124         from MoinMoin.action import AttachFile
00125         import os
00126         _ = self.request.getText
00127         pagename, filename = AttachFile.absoluteName(url, self.page.page_name)
00128         fname = wikiutil.taintfilename(filename)
00129         fpath = AttachFile.getFilename(self.request, pagename, fname)
00130         ext = os.path.splitext(filename)[1]
00131         Parser = wikiutil.getParserForExtension(self.request.cfg, ext)
00132         if Parser is not None:
00133             try:
00134                 content = file(fpath, 'r').read()
00135                 # Try to decode text. It might return junk, but we don't
00136                 # have enough information with attachments.
00137                 content = wikiutil.decodeUnknownInput(content)
00138                 colorizer = Parser(content, self.request, filename=filename)
00139                 colorizer.format(self)
00140             except IOError:
00141                 pass
00142 
00143         return (self.attachment_link(1, url) +
00144                 self.text(text) +
00145                 self.attachment_link(0))
00146 
00147     def anchordef(self, name):
00148         return ""
00149 
00150     def line_anchordef(self, lineno):
00151         return ""
00152 
00153     def anchorlink(self, on, name='', **kw):
00154         return ""
00155 
00156     def line_anchorlink(self, on, lineno=0):
00157         return ""
00158 
00159     def image(self, src=None, **kw):
00160         """An inline image.
00161 
00162         Extra keyword arguments are according to the HTML <img> tag attributes.
00163         In particular an 'alt' or 'title' argument should give a description
00164         of the image.
00165         """
00166         title = src
00167         for titleattr in ('title', 'html__title', 'alt', 'html__alt'):
00168             if titleattr in kw:
00169                 title = kw[titleattr]
00170                 break
00171         if title:
00172             return '[Image:%s]' % title
00173         return '[Image]'
00174 
00175     # generic transclude/include:
00176     def transclusion(self, on, **kw):
00177         raise NotImplementedError
00178     def transclusion_param(self, **kw):
00179         raise NotImplementedError
00180 
00181     def smiley(self, text):
00182         return text
00183 
00184     def nowikiword(self, text):
00185         return self.text(text)
00186 
00187     # Text and Text Attributes ###########################################
00188 
00189     def text(self, text, **kw):
00190         if not self._highlight_re:
00191             return self._text(text)
00192 
00193         result = []
00194         lastpos = 0
00195         match = self._highlight_re.search(text)
00196         while match and lastpos < len(text):
00197             # add the match we found
00198             result.append(self._text(text[lastpos:match.start()]))
00199             result.append(self.highlight(1))
00200             result.append(self._text(match.group(0)))
00201             result.append(self.highlight(0))
00202 
00203             # search for the next one
00204             lastpos = match.end() + (match.end() == lastpos)
00205             match = self._highlight_re.search(text, lastpos)
00206 
00207         result.append(self._text(text[lastpos:]))
00208         return ''.join(result)
00209 
00210     def _text(self, text):
00211         raise NotImplementedError
00212 
00213     def strong(self, on, **kw):
00214         raise NotImplementedError
00215 
00216     def emphasis(self, on, **kw):
00217         raise NotImplementedError
00218 
00219     def underline(self, on, **kw):
00220         raise NotImplementedError
00221 
00222     def highlight(self, on, **kw):
00223         raise NotImplementedError
00224 
00225     def sup(self, on, **kw):
00226         raise NotImplementedError
00227 
00228     def sub(self, on, **kw):
00229         raise NotImplementedError
00230 
00231     def strike(self, on, **kw):
00232         raise NotImplementedError
00233 
00234     def code(self, on, **kw):
00235         raise NotImplementedError
00236 
00237     def preformatted(self, on, **kw):
00238         self.in_pre = on != 0
00239 
00240     def small(self, on, **kw):
00241         raise NotImplementedError
00242 
00243     def big(self, on, **kw):
00244         raise NotImplementedError
00245 
00246     # special markup for syntax highlighting #############################
00247 
00248     def code_area(self, on, code_id, **kw):
00249         raise NotImplementedError
00250 
00251     def code_line(self, on):
00252         raise NotImplementedError
00253 
00254     def code_token(self, tok_text, tok_type):
00255         raise NotImplementedError
00256 
00257     # Paragraphs, Lines, Rules ###########################################
00258 
00259     def linebreak(self, preformatted=1):
00260         raise NotImplementedError
00261 
00262     def paragraph(self, on, **kw):
00263         self.in_p = on != 0
00264 
00265     def rule(self, size=0, **kw):
00266         raise NotImplementedError
00267 
00268     def icon(self, type):
00269         return type
00270 
00271     # Lists ##############################################################
00272 
00273     def number_list(self, on, type=None, start=None, **kw):
00274         raise NotImplementedError
00275 
00276     def bullet_list(self, on, **kw):
00277         raise NotImplementedError
00278 
00279     def listitem(self, on, **kw):
00280         raise NotImplementedError
00281 
00282     def definition_list(self, on, **kw):
00283         raise NotImplementedError
00284 
00285     def definition_term(self, on, compact=0, **kw):
00286         raise NotImplementedError
00287 
00288     def definition_desc(self, on, **kw):
00289         raise NotImplementedError
00290 
00291     def heading(self, on, depth, **kw):
00292         raise NotImplementedError
00293 
00294     # Tables #############################################################
00295 
00296     def table(self, on, attrs={}, **kw):
00297         raise NotImplementedError
00298 
00299     def table_row(self, on, attrs={}, **kw):
00300         raise NotImplementedError
00301 
00302     def table_cell(self, on, attrs={}, **kw):
00303         raise NotImplementedError
00304 
00305     # Dynamic stuff / Plugins ############################################
00306 
00307     def macro(self, macro_obj, name, args, markup=None):
00308         # call the macro
00309         try:
00310             return macro_obj.execute(name, args)
00311         except ImportError, err:
00312             errmsg = unicode(err)
00313             if not name in errmsg:
00314                 raise
00315             if markup:
00316                 return (self.span(1, title=errmsg) +
00317                         self.text(markup) +
00318                         self.span(0))
00319             else:
00320                 return self.text(errmsg)
00321     def _get_bang_args(self, line):
00322         if line.startswith('#!'):
00323             try:
00324                 name, args = line[2:].split(None, 1)
00325             except ValueError:
00326                 return ''
00327             else:
00328                 return args
00329         return None
00330 
00331     def parser(self, parser_name, lines):
00332         """ parser_name MUST be valid!
00333             writes out the result instead of returning it!
00334         """
00335         # attention: this is copied into text_python!
00336         parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", parser_name)
00337         args = None
00338         if lines:
00339             args = self._get_bang_args(lines[0])
00340             logging.debug("formatter.parser: parser args %r" % args)
00341             if args is not None:
00342                 lines = lines[1:]
00343         if lines and not lines[0]:
00344             lines = lines[1:]
00345         if lines and not lines[-1].strip():
00346             lines = lines[:-1]
00347         p = parser('\n'.join(lines), self.request, format_args=args)
00348         p.format(self)
00349         del p
00350         return ''
00351 
00352     # Other ##############################################################
00353 
00354     def div(self, on, **kw):
00355         """ open/close a blocklevel division """
00356         return ""
00357 
00358     def span(self, on, **kw):
00359         """ open/close a inline span """
00360         return ""
00361 
00362     def rawHTML(self, markup):
00363         """ This allows emitting pre-formatted HTML markup, and should be
00364             used wisely (i.e. very seldom).
00365 
00366             Using this event while generating content results in unwanted
00367             effects, like loss of markup or insertion of CDATA sections
00368             when output goes to XML formats.
00369         """
00370 
00371         import formatter, htmllib
00372         from MoinMoin.util import simpleIO
00373 
00374         # Regenerate plain text
00375         f = simpleIO()
00376         h = htmllib.HTMLParser(formatter.AbstractFormatter(formatter.DumbWriter(f)))
00377         h.feed(markup)
00378         h.close()
00379 
00380         return self.text(f.getvalue())
00381 
00382     def escapedText(self, on, **kw):
00383         """ This allows emitting text as-is, anything special will
00384             be escaped (at least in HTML, some text output format
00385             would possibly do nothing here)
00386         """
00387         return ""
00388 
00389     def comment(self, text, **kw):
00390         return ""
00391 
00392     # ID handling #################################################
00393 
00394     def sanitize_to_id(self, text):
00395         '''
00396         Take 'text' and return something that is a valid ID
00397         for this formatter.
00398         The default returns the first non-space character of the string.
00399 
00400         Because of the way this is used, it must be idempotent,
00401         i.e. calling it on an already sanitized id must yield the
00402         original id.
00403         '''
00404         return text.strip()[:1]
00405 
00406     def make_id_unique(self, id):
00407         '''
00408         Take an ID and make it unique in the current namespace.
00409         '''
00410         ns = self.request.uid_generator.include_id
00411         if not ns is None:
00412             ns = self.sanitize_to_id(ns)
00413         id = self.sanitize_to_id(id)
00414         id = self.request.uid_generator(id, ns)
00415         return id
00416 
00417     def qualify_id(self, id):
00418         '''
00419         Take an ID and return a string that is qualified by
00420         the current namespace; this default implementation
00421         is suitable if the dot ('.') is valid in IDs for your
00422         formatter.
00423         '''
00424         ns = self.request.uid_generator.include_id
00425         if not ns is None:
00426             ns = self.sanitize_to_id(ns)
00427             return '%s.%s' % (ns, id)
00428         return id