diff --git a/wiki/formatter/text_questionnaire.py b/wiki/formatter/text_questionnaire.py new file mode 100644 index 00000000..1ce88672 --- /dev/null +++ b/wiki/formatter/text_questionnaire.py @@ -0,0 +1,731 @@ +# -*- 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)