# -*- coding: iso-8859-1 -*- """ MoinMoin - "text/html+css" Formatter @copyright: 2000 - 2004 by Jürgen Hermann @license: GNU GPL, see COPYING for details. """ from MoinMoin.formatter.base import FormatterBase from MoinMoin import wikiutil, i18n, config from MoinMoin.Page import Page class Formatter(FormatterBase): """ Send HTML data. """ hardspace = ' ' def __init__(self, request, **kw): apply(FormatterBase.__init__, (self, request), kw) # inline tags stack. When an inline tag is called, it goes into # the stack. When a block element starts, all inline tags in # the stack are closed. self._inlineStack = [] self._in_li = 0 self._in_code = 0 self._in_code_area = 0 self._in_code_line = 0 self._code_area_num = 0 self._code_area_js = 0 self._code_area_state = ['', 0, -1, -1, 0] self._show_section_numbers = None self._content_ids = [] self.pagelink_preclosed = False self._is_included = kw.get('is_included',False) self.request = request self.cfg = request.cfg if not hasattr(request, '_fmt_hd_counters'): request._fmt_hd_counters = [] # Primitive formatter functions ##################################### # all other methods should use these to format tags. This keeps the # code clean and handle pathological cases like unclosed p and # inline tags. def langAttr(self, lang=None): """ Return lang and dir attribute Must be used on all block elements - div, p, table, etc. @param lang: if defined, will return attributes for lang. if not defined, will return attributes only if the current lang is different from the content lang. @rtype: dict @retrun: language attributes """ if not lang: lang = self.request.current_lang # Actions that generate content in user language should change # the content lang from the default defined in cfg. if lang == self.request.content_lang: # lang is inherited from content div return {} attr = {'lang': lang, 'dir': i18n.getDirection(lang),} return attr def formatAttributes(self, attr=None): """ Return formatted attributes string @param attr: dict containing keys and values @rtype: string ? @return: formated attributes or empty string """ if attr: attr = [' %s="%s"' % (k, v) for k, v in attr.items()] return ''.join(attr) return '' # TODO: use set when we require Python 2.3 # TODO: The list is not complete, add missing from dtd _blocks = 'p div pre table tr td ol ul dl li dt dd h1 h2 h3 h4 h5 h6 hr form' _blocks = dict(zip(_blocks.split(), [1] * len(_blocks))) def open(self, tag, newline=False, attr=None): """ Open a tag with optional attributes @param tag: html tag, string @param newline: render tag on a separate line @parm attr: dict with tag attributes @rtype: string ? @return: open tag with attributes """ if tag in self._blocks: # Block elements result = [] # Add language attributes, but let caller overide the default attributes = self.langAttr() if attr: attributes.update(attr) # Format attributes = self.formatAttributes(attributes) result.append('<%s%s>' % (tag, attributes)) if newline: result.append('\n') return ''.join(result) else: # Inline elements # Add to inlineStack self._inlineStack.append(tag) # Format return '<%s%s>' % (tag, self.formatAttributes(attr)) def close(self, tag, newline=False): """ Close tag @param tag: html tag, string @rtype: string ? @return: closing tag """ if tag in self._blocks: # Block elements # Close all tags in inline stack # Work on a copy, because close(inline) manipulate the stack result = [] stack = self._inlineStack[:] stack.reverse() for inline in stack: result.append(self.close(inline)) # Format with newline if newline: result.append('\n') result.append('\n' % (tag)) return ''.join(result) else: # Inline elements # Pull from stack, ignore order, that is not our problem. # The code that calls us should keep correct calling order. if tag in self._inlineStack: self._inlineStack.remove(tag) return '' % tag # Public methods ################################################### def startContent(self, content_id='content', **kwargs): """ Start page content div """ # Setup id if content_id!='content': aid = 'top_%s' % (content_id,) else: aid = 'top' self._content_ids.append(content_id) result = [] # Use the content language attr = self.langAttr(self.request.content_lang) attr['id'] = content_id result.append(self.open('div', newline=1, attr=attr)) result.append(self.anchordef(aid)) return ''.join(result) def endContent(self): """ Close page content div """ # Setup id try: cid = self._content_ids.pop() except: cid = 'content' if cid!='content': aid = 'bottom_%s' % (cid,) else: aid = 'bottom' result = [] result.append(self.anchordef(aid)) result.append(self.close('div', newline=1)) return ''.join(result) def lang(self, on, lang_name): """ Insert text with specific lang and direction. Enclose within span tag if lang_name is different from the current lang """ tag = 'span' if lang_name != self.request.current_lang: # Enclose text in span using lang attributes if on: attr = self.langAttr(lang=lang_name) return self.open(tag, attr=attr) return self.close(tag) # Direction did not change, no need for span return '' def sysmsg(self, on, **kw): tag = 'div' if on: return self.open(tag, attr={'class': 'message'}) return self.close(tag) # Links ############################################################## def pagelink(self, on, pagename='', page=None, **kw): """ Link to a page. formatter.text_python will use an optimized call with a page!=None parameter. DO NOT USE THIS YOURSELF OR IT WILL BREAK. See wikiutil.link_tag() for possible keyword parameters. """ apply(FormatterBase.pagelink, (self, on, pagename, page), kw) if page is None: page = Page(self.request, pagename, formatter=self); if self.request.user.show_nonexist_qm and on and not page.exists(): self.pagelink_preclosed = True return (page.link_to(self.request, on=1, **kw) + self.text("?") + page.link_to(self.request, on=0, **kw)) elif not on and self.pagelink_preclosed: self.pagelink_preclosed = False return "" else: return page.link_to(self.request, on=on, **kw) def interwikilink(self, on, interwiki='', pagename='', **kw): if not on: return '' wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:%s' % (interwiki, pagename)) wikiurl = wikiutil.mapURL(self.request, wikiurl) if wikitag == 'Self': # for own wiki, do simple links href = wikiutil.join_wiki(wikiurl, wikiutil.AbsPageName(self.request, self.page.page_name, wikitail)) return (self.url(1, href, unescaped=0, pretty_url=kw.get('pretty_url', 0))) else: # return InterWiki hyperlink href = wikiutil.join_wiki(wikiurl, wikitail) if wikitag_bad: html_class = 'badinterwiki' else: html_class = 'interwiki' icon = '' if self.request.user.show_fancy_links: icon = self.request.theme.make_icon('interwiki', {'wikitag': wikitag}) return (self.url(1, href, title=wikitag, unescaped=0, pretty_url=kw.get('pretty_url', 0), css = html_class) + icon) # unescaped=1 was changed to 0 to make interwiki links with pages with umlauts (or other non-ascii) work def url(self, on, url=None, css=None, **kw): """ Keyword params: title - title attribute ... some more (!!! TODO) """ if url is not None: url = wikiutil.mapURL(self.request, url) pretty = kw.get('pretty_url', 0) title = kw.get('title', None) #if not pretty and wikiutil.isPicture(url): # # XXX # return '%s' % (url,url) # create link if not on: return '' str = '\n' % (id, ) def anchorlink(self, on, name='', id = None): extra = '' if id: extra = ' id="%s"' % id return ['' % (name, extra), ''][not on] # Text ############################################################## def _text(self, text): if self._in_code: return wikiutil.escape(text).replace(' ', self.hardspace) return wikiutil.escape(text) # Inline ########################################################### def strong(self, on): tag = 'strong' if on: return self.open(tag) return self.close(tag) def emphasis(self, on): tag = 'em' if on: return self.open(tag) return self.close(tag) def underline(self, on): tag = 'span' if on: return self.open(tag, attr={'class': 'u'}) return self.close(tag) def highlight(self, on): tag = 'strong' if on: return self.open(tag, attr={'class': 'highlight'}) return self.close(tag) def sup(self, on): tag = 'sup' if on: return self.open(tag) return self.close(tag) def sub(self, on): tag = 'sub' if on: return self.open(tag) return self.close(tag) def code(self, on): tag = 'tt' # Maybe we don't need this, because we have tt will be in inlineStack. self._in_code = on if on: return self.open(tag) return self.close(tag) def small(self, on): tag = 'small' if on: return self.open(tag) return self.close(tag) def big(self, on): tag = 'big' if on: return self.open(tag) return self.close(tag) # Block elements #################################################### def preformatted(self, on): FormatterBase.preformatted(self, on) tag = 'pre' if on: return self.open(tag, newline=1) return self.close(tag) # Use by code area _toggleLineNumbersScript = """ """ def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1): res = [] ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num)) if on: # Open a code area self._in_code_area = 1 self._in_code_line = 0 self._code_area_state = [ci, show, start, step, start] # Open the code div - using left to right always! attr = {'class': 'codearea', 'lang': 'en', 'dir': 'ltr'} res.append(self.open('div', attr=attr)) # Add the script only in the first code area on the page if self._code_area_js == 0 and self._code_area_state[1] >= 0: res.append(self._toggleLineNumbersScript) self._code_area_js = 1 # Add line number link, but only for JavaScript enabled browsers. if self._code_area_state[1] >= 0: toggleLineNumbersLink = r''' ''' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3]) res.append(toggleLineNumbersLink) # Open pre - using left to right always! attr = {'id': self._code_area_state[0], 'lang': 'en', 'dir': 'ltr'} res.append(self.open('pre', newline=True, attr=attr)) else: # Close code area res = [] if self._in_code_line: res.append(self.code_line(0)) res.append(self.close('pre')) res.append(self.close('div')) # Update state self._in_code_area = 0 self._code_area_num += 1 return ''.join(res) def code_line(self, on): res = '' if not on or (on and self._in_code_line): res += '\n' if on: res += '' if self._code_area_state[1] > 0: res += '%4d ' % (self._code_area_state[4], ) self._code_area_state[4] += self._code_area_state[3] self._in_code_line = on != 0 return res def code_token(self, on, tok_type): return ['' % tok_type, ''][not on] # Paragraphs, Lines, Rules ########################################### def linebreak(self, preformatted=1): if self._in_code_area: preformatted = 1 return ['\n', '
\n'][not preformatted] def paragraph(self, on): if self._terse: return '' FormatterBase.paragraph(self, on) if self._in_li: self._in_li = self._in_li + 1 tag = 'p' if on: return self.open(tag) return self.close(tag) def rule(self, size=None): if size: # Add hr class: hr1 - hr6 return self.open('hr', newline=1, attr={'class': 'hr%d' % size}) return self.open('hr', newline=1) def icon(self, type): return self.request.theme.make_icon(type) def smiley(self, text): w, h, b, img = config.smileys[text.strip()] href = img if not href.startswith('/'): href = self.request.theme.img_url(img) return self.image(src=href, alt=text, width=str(w), height=str(h)) # Lists ############################################################## def number_list(self, on, type=None, start=None): tag = 'ol' if on: attr = {} if type is not None: attr['type'] = type if start is not None: attr['start'] = start return self.open(tag, newline=1, attr=attr) return self.close(tag) def bullet_list(self, on): tag = 'ul' if on: return self.open(tag, newline=1) return self.close(tag) def listitem(self, on, **kw): """ List item inherit its lang from the list. """ tag = 'li' self._in_li = on != 0 if on: attr = {} css_class = kw.get('css_class', None) if css_class: attr['class'] = css_class style = kw.get('style', None) if style: attr['style'] = style return self.open(tag, attr=attr) return self.close(tag) def definition_list(self, on): tag = 'dl' if on: return self.open(tag, newline=1) return self.close(tag) def definition_term(self, on): tag = 'dt' if on: return self.open(tag) return self.close(tag) def definition_desc(self, on): tag = 'dd' if on: return self.open(tag) return self.close(tag) def heading(self, on, depth, id = None, **kw): # remember depth of first heading, and adapt counting depth accordingly if not self._base_depth: self._base_depth = depth count_depth = max(depth - (self._base_depth - 1), 1) # check numbering, possibly changing the default if self._show_section_numbers is None: self._show_section_numbers = self.cfg.show_section_numbers numbering = self.request.getPragma('section-numbers', '').lower() if numbering in ['0', 'off']: self._show_section_numbers = 0 elif numbering in ['1', 'on']: self._show_section_numbers = 1 elif numbering in ['2', '3', '4', '5', '6']: # explicit base level for section number display self._show_section_numbers = int(numbering) heading_depth = depth + 1 # closing tag, with empty line after, to make source more readable if not on: return self.close('h%d' % heading_depth) + '\n' # create section number number = '' if self._show_section_numbers: # count headings on all levels self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth] while len(self.request._fmt_hd_counters) < count_depth: self.request._fmt_hd_counters.append(0) self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1 number = '.'.join(map(str, self.request._fmt_hd_counters[self._show_section_numbers-1:])) if number: number += ". " attr = {} if id: attr['id'] = id # Add space before heading, easier to check source code result = '\n' + self.open('h%d' % heading_depth, attr=attr) # TODO: convert this to readable code if self.request.user.show_topbottom: # TODO change top/bottom refs to content-specific top/bottom refs? result = ("%s%s%s%s%s%s%s%s" % (result, kw.get('icons',''), self.url(1, "#bottom", unescaped=1), self.icon('bottom'), self.url(0), self.url(1, "#top", unescaped=1), self.icon('top'), self.url(0))) return "%s%s%s" % (result, kw.get('icons',''), number) # Tables ############################################################# _allowed_table_attrs = { 'table': ['class', 'id', 'style'], 'row': ['class', 'id', 'style'], '': ['colspan', 'rowspan', 'class', 'id', 'style'], } def _checkTableAttr(self, attrs, prefix): """ Check table attributes Convert from wikitable attributes to html 4 attributes. @param attrs: attribute dict @param prefix: used in wiki table attributes @rtyp: dict @return: valid table attributes """ if not attrs: return {} result = {} s = "" # we collect synthesized style in s for key, val in attrs.items(): # Ignore keys that don't start with prefix if prefix and key[:len(prefix)] != prefix: continue key = key[len(prefix):] val = val.strip('"') # remove invalid attrs from dict and synthesize style if key == 'width': s += "width: %s;" % val elif key == 'bgcolor': s += "background-color: %s;" % val elif key == 'align': s += "text-align: %s;" % val elif key == 'valign': s += "vertical-align: %s;" % val # Ignore unknown keys if key not in self._allowed_table_attrs[prefix]: continue result[key] = val if s: if result.has_key('style'): result['style'] += s else: result['style'] = s return result def table(self, on, attrs=None): """ Create table @param on: start table @param attrs: table attributes @rtype: string @return start or end tag of a table """ result = [] if on: # Open div to get correct alignment with table width smaller # then 100% result.append(self.open('div', newline=1)) # Open table if not attrs: attrs = {} else: attrs = self._checkTableAttr(attrs, 'table') result.append(self.open('table', newline=1, attr=attrs)) else: # Close table then div result.append(self.close('table')) result.append(self.close('div')) return ''.join(result) def table_row(self, on, attrs=None): tag = 'tr' if on: if not attrs: attrs = {} else: attrs = self._checkTableAttr(attrs, 'row') return self.open(tag, newline=1, attr=attrs) return self.close(tag) def table_cell(self, on, attrs=None): tag = 'td' if on: if not attrs: attrs = {} else: attrs = self._checkTableAttr(attrs, '') return self.open(tag, newline=1, attr=attrs) return self.close(tag) def escapedText(self, text): return wikiutil.escape(text)