Back to index

moin  1.9.0~rc2
text_html.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - "text/html+css" Formatter
00004 
00005     @copyright: 2000-2004 by Juergen Hermann <jh@web.de>
00006     @license: GNU GPL, see COPYING for details.
00007 """
00008 import os.path, re
00009 
00010 from MoinMoin import log
00011 logging = log.getLogger(__name__)
00012 
00013 from MoinMoin.formatter import FormatterBase
00014 from MoinMoin import wikiutil, i18n
00015 from MoinMoin.Page import Page
00016 from MoinMoin.action import AttachFile
00017 from MoinMoin.support.python_compatibility import set
00018 
00019 # insert IDs into output wherever they occur
00020 # warning: breaks toggle line numbers javascript
00021 _id_debug = False
00022 
00023 line_anchors = True
00024 prettyprint = False
00025 
00026 # These are the HTML elements that we treat as block elements.
00027 _blocks = set(['dd', 'div', 'dl', 'dt', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
00028                'hr', 'li', 'ol', 'p', 'pre', 'table', 'tbody', 'td', 'tfoot', 'th',
00029                'thead', 'tr', 'ul', 'blockquote', ])
00030 
00031 # These are the HTML elements which are typically only used with
00032 # an opening tag without a separate closing tag.  We do not
00033 # include 'script' or 'style' because sometimes they do have
00034 # content, and also IE has a parsing bug with those two elements (only)
00035 # when they don't have a closing tag even if valid XHTML.
00036 
00037 _self_closing_tags = set(['area', 'base', 'br', 'col', 'frame', 'hr', 'img',
00038                           'input', 'isindex', 'link', 'meta', 'param'])
00039 
00040 # We only open those tags and let the browser auto-close them:
00041 _auto_closing_tags = set(['p'])
00042 
00043 # These are the elements which generally should cause an increase in the
00044 # indention level in the html souce code.
00045 _indenting_tags = set(['ol', 'ul', 'dl', 'li', 'dt', 'dd', 'tr', 'td'])
00046 
00047 # These are the elements that discard any whitespace they contain as
00048 # immediate child nodes.
00049 _space_eating_tags = set(['colgroup', 'dl', 'frameset', 'head', 'map' 'menu',
00050                           'ol', 'optgroup', 'select', 'table', 'tbody', 'tfoot',
00051                           'thead', 'tr', 'ul'])
00052 
00053 # These are standard HTML attributes which are typically used without any
00054 # value; e.g., as boolean flags indicated by their presence.
00055 _html_attribute_boolflags = set(['compact', 'disabled', 'ismap', 'nohref',
00056                                  'noresize', 'noshade', 'nowrap', 'readonly',
00057                                  'selected', 'wrap'])
00058 
00059 # These are all the standard HTML attributes that are allowed on any element.
00060 _common_attributes = set(['accesskey', 'class', 'dir', 'disabled', 'id', 'lang',
00061                           'style', 'tabindex', 'title'])
00062 
00063 
00064 def rewrite_attribute_name(name, default_namespace='html'):
00065     """
00066     Takes an attribute name and tries to make it HTML correct.
00067 
00068     This function takes an attribute name as a string, as it may be
00069     passed in to a formatting method using a keyword-argument syntax,
00070     and tries to convert it into a real attribute name.  This is
00071     necessary because some attributes may conflict with Python
00072     reserved words or variable syntax (such as 'for', 'class', or
00073     'z-index'); and also to help with backwards compatibility with
00074     older versions of MoinMoin where different names may have been
00075     used (such as 'content_id' or 'css').
00076 
00077     Returns a tuple of strings: (namespace, attribute).
00078 
00079     Namespaces: The default namespace is always assumed to be 'html',
00080     unless the input string contains a colon or a double-underscore;
00081     in which case the first such occurance is assumed to separate the
00082     namespace prefix from name.  So, for example, to get the HTML
00083     attribute 'for' (as on a <label> element), you can pass in the
00084     string 'html__for' or even '__for'.
00085 
00086     Hyphens:  To better support hyphens (which are not allowed in Python
00087     variable names), all occurances of two underscores will be replaced
00088     with a hyphen.  If you use this, then you must also provide a
00089     namespace since the first occurance of '__' separates a namespace
00090     from the name.
00091 
00092     Special cases: Within the 'html' namespace, mainly to preserve
00093     backwards compatibility, these exceptions ars recognized:
00094     'content_type', 'content_id', 'css_class', and 'css'.
00095     Additionally all html attributes starting with 'on' are forced to
00096     lower-case.  Also the string 'xmlns' is recognized as having
00097     no namespace.
00098 
00099     Examples:
00100         'id' -> ('html', 'id')
00101         'css_class' -> ('html', 'class')
00102         'content_id' -> ('html', 'id')
00103         'content_type' -> ('html', 'type')
00104         'html__for' -> ('html', 'for)
00105         'xml__space' -> ('xml', 'space')
00106         '__z__index' -> ('html', 'z-index')
00107         '__http__equiv' -> ('html', 'http-equiv')
00108         'onChange' -> ('html', 'onchange')
00109         'xmlns' -> ('', 'xmlns')
00110         'xmlns__abc' -> ('xmlns', 'abc')
00111 
00112     (In actuality we only deal with namespace prefixes, not any real
00113     namespace URI...we only care about the syntax not the meanings.)
00114     """
00115 
00116     # Handle any namespaces (just in case someday we support XHTML)
00117     if ':' in name:
00118         ns, name = name.split(':', 1)
00119     elif '__' in name:
00120         ns, name = name.split('__', 1)
00121     elif name == 'xmlns':
00122         ns = ''
00123     else:
00124         ns = default_namespace
00125 
00126     name.replace('__', '-')
00127     if ns == 'html':
00128         # We have an HTML attribute, fix according to DTD
00129         if name == 'content_type': # MIME type such as in <a> and <link> elements
00130             name = 'type'
00131         elif name == 'content_id': # moin historical convention
00132             name = 'id'
00133         elif name in ('css_class', 'css'): # to avoid python word 'class'
00134             name = 'class'
00135         elif name.startswith('on'): # event handler hook
00136             name = name.lower()
00137     return ns, name
00138 
00139 
00140 def extend_attribute_dictionary(attributedict, ns, name, value):
00141     """Add a new attribute to an attribute dictionary, merging values where possible.
00142 
00143     The attributedict must be a dictionary with tuple-keys of the form:
00144     (namespace, attrname).
00145 
00146     The given ns, name, and value will be added to the dictionary.  It
00147     will replace the old value if it existed, except for a few special
00148     cases where the values are logically merged instead (CSS class
00149     names and style rules).
00150 
00151     As a special case, if value is None (not just ''), then the
00152     attribute is actually deleted from the dictionary.
00153     """
00154 
00155     key = ns, name
00156     if value is None:
00157         if key in attributedict:
00158             del attributedict[key]
00159     else:
00160         if ns == 'html' and key in attributedict:
00161             if name == 'class':
00162                 # CSS classes are appended by space-separated list
00163                 value = attributedict[key] + ' ' + value
00164             elif name == 'style':
00165                 # CSS styles are appended by semicolon-separated rules list
00166                 value = attributedict[key] + '; ' + value
00167             elif name in _html_attribute_boolflags:
00168                 # All attributes must have a value. According to XHTML those
00169                 # traditionally used as flags should have their value set to
00170                 # the same as the attribute name.
00171                 value = name
00172         attributedict[key] = value
00173 
00174 
00175 class Formatter(FormatterBase):
00176     """
00177         Send HTML data.
00178     """
00179 
00180     hardspace = '&nbsp;'
00181     indentspace = ' '
00182 
00183     def __init__(self, request, **kw):
00184         FormatterBase.__init__(self, request, **kw)
00185         self._indent_level = 0
00186 
00187         self._in_code = 0 # used by text_gedit
00188         self._in_code_area = 0
00189         self._in_code_line = 0
00190         self._code_area_js = 0
00191         self._code_area_state = ['', 0, -1, -1, 0]
00192         self._show_section_numbers = None
00193         self.pagelink_preclosed = False
00194         self._is_included = kw.get('is_included', False)
00195         self.request = request
00196         self.cfg = request.cfg
00197         self.no_magic = kw.get('no_magic', False) # disabled tag auto closing
00198 
00199         if not hasattr(request, '_fmt_hd_counters'):
00200             request._fmt_hd_counters = []
00201 
00202     # Primitive formatter functions #####################################
00203 
00204     # all other methods should use these to format tags. This keeps the
00205     # code clean and handle pathological cases like unclosed p and
00206     # inline tags.
00207 
00208     def _langAttr(self, lang=None):
00209         """ Return lang and dir attribute
00210         (INTERNAL USE BY HTML FORMATTER ONLY!)
00211 
00212         Must be used on all block elements - div, p, table, etc.
00213         @param lang: if defined, will return attributes for lang. if not
00214             defined, will return attributes only if the current lang is
00215             different from the content lang.
00216         @rtype: dict
00217         @return: language attributes
00218         """
00219         if not lang:
00220             lang = self.request.current_lang
00221             # Actions that generate content in user language should change
00222             # the content lang from the default defined in cfg.
00223             if lang == self.request.content_lang:
00224                 # lang is inherited from content div
00225                 return {}
00226 
00227         #attr = {'xml:lang': lang, 'lang': lang, 'dir': i18n.getDirection(lang),}
00228         attr = {'lang': lang, 'dir': i18n.getDirection(lang), }
00229         return attr
00230 
00231     def _formatAttributes(self, attr=None, allowed_attrs=None, **kw):
00232         """ Return HTML attributes formatted as a single string. (INTERNAL USE BY HTML FORMATTER ONLY!)
00233 
00234         @param attr: dict containing keys and values
00235         @param allowed_attrs: A list of allowable attribute names
00236         @param kw: other arbitrary attributes expressed as keyword arguments.
00237         @rtype: string
00238         @return: formated attributes or empty string
00239 
00240         The attributes and their values can either be given in the
00241         'attr' dictionary, or as extra keyword arguments.  They are
00242         both merged together.  See the function
00243         rewrite_attribute_name() for special notes on how to name
00244         attributes.
00245 
00246         Setting a value to None rather than a string (or string
00247         coercible) will remove that attribute from the list.
00248 
00249         If the list of allowed_attrs is provided, then an error is
00250         raised if an HTML attribute is encountered that is not in that
00251         list (or is not a common attribute which is always allowed or
00252         is not in another XML namespace using the double-underscore
00253         syntax).
00254         """
00255 
00256         # Merge the attr dict and kw dict into a single attributes
00257         # dictionary (rewriting any attribute names, extracting
00258         # namespaces, and merging some values like css classes).
00259         attributes = {} # dict of key=(namespace,name): value=attribute_value
00260         if attr:
00261             for a, v in attr.items():
00262                 a_ns, a_name = rewrite_attribute_name(a)
00263                 extend_attribute_dictionary(attributes, a_ns, a_name, v)
00264         if kw:
00265             for a, v in kw.items():
00266                 a_ns, a_name = rewrite_attribute_name(a)
00267                 extend_attribute_dictionary(attributes, a_ns, a_name, v)
00268 
00269         # Add title attribute if missing, but it has an alt.
00270         if ('html', 'alt') in attributes and ('html', 'title') not in attributes:
00271             attributes[('html', 'title')] = attributes[('html', 'alt')]
00272 
00273         # Force both lang and xml:lang to be present and identical if
00274         # either exists.  The lang takes precedence over xml:lang if
00275         # both exist.
00276         #if ('html', 'lang') in attributes:
00277         #    attributes[('xml', 'lang')] = attributes[('html', 'lang')]
00278         #elif ('xml', 'lang') in attributes:
00279         #    attributes[('html', 'lang')] = attributes[('xml', 'lang')]
00280 
00281         # Check all the HTML attributes to see if they are known and
00282         # allowed.  Ignore attributes if in non-HTML namespaces.
00283         if allowed_attrs:
00284             for name in [key[1] for key in attributes if key[0] == 'html']:
00285                 if name in _common_attributes or name in allowed_attrs:
00286                     pass
00287                 elif name.startswith('on'):
00288                     pass  # Too many event handlers to enumerate, just let them all pass.
00289                 else:
00290                     # Unknown or unallowed attribute.
00291                     err = 'Illegal HTML attribute "%s" passed to formatter' % name
00292                     raise ValueError(err)
00293 
00294         # Finally, format them all as a single string.
00295         if attributes:
00296             # Construct a formatted string containing all attributes
00297             # with their values escaped.  Any html:* namespace
00298             # attributes drop the namespace prefix.  We build this by
00299             # separating the attributes into three categories:
00300             #
00301             #  * Those without any namespace (should only be xmlns attributes)
00302             #  * Those in the HTML namespace (we drop the html: prefix for these)
00303             #  * Those in any other non-HTML namespace, including xml:
00304 
00305             xmlnslist = ['%s="%s"' % (k[1], wikiutil.escape(v, 1))
00306                          for k, v in attributes.items() if not k[0]]
00307             htmllist = ['%s="%s"' % (k[1], wikiutil.escape(v, 1))
00308                         for k, v in attributes.items() if k[0] == 'html']
00309             otherlist = ['%s:%s="%s"' % (k[0], k[1], wikiutil.escape(v, 1))
00310                          for k, v in attributes.items() if k[0] and k[0] != 'html']
00311 
00312             # Join all these lists together in a space-separated string.  Also
00313             # prefix the whole thing with a space too.
00314             htmllist.sort()
00315             otherlist.sort()
00316             all = [''] + xmlnslist + htmllist + otherlist
00317             return ' '.join(all)
00318         return ''
00319 
00320     def _open(self, tag, newline=False, attr=None, allowed_attrs=None,
00321               is_unique=False, **kw):
00322         """ Open a tag with optional attributes (INTERNAL USE BY HTML FORMATTER ONLY!)
00323 
00324         @param tag: html tag, string
00325         @param newline: render tag so following data is on a separate line
00326         @param attr: dict with tag attributes
00327         @param allowed_attrs: list of allowed attributes for this element
00328         @param is_unique: ID is already unique
00329         @param kw: arbitrary attributes and values
00330         @rtype: string ?
00331         @return: open tag with attributes as a string
00332         """
00333         # If it is self-closing, then don't expect a closing tag later on.
00334         is_self_closing = (tag in _self_closing_tags) and ' /' or ''
00335 
00336         # make ID unique
00337         id = None
00338         if not is_unique:
00339             if attr and 'id' in attr:
00340                 id = self.make_id_unique(attr['id'])
00341                 id = self.qualify_id(id)
00342                 attr['id'] = id
00343             if 'id' in kw:
00344                 id = self.make_id_unique(kw['id'])
00345                 id = self.qualify_id(id)
00346                 kw['id'] = id
00347         else:
00348             if attr and 'id' in attr:
00349                 id = attr['id']
00350             if 'id' in kw:
00351                 id = kw['id']
00352 
00353         if tag in _blocks:
00354             # Block elements
00355             result = []
00356 
00357             # Add language attributes, but let caller overide the default
00358             attributes = self._langAttr()
00359             if attr:
00360                 attributes.update(attr)
00361 
00362             # Format
00363             attributes = self._formatAttributes(attributes, allowed_attrs=allowed_attrs, **kw)
00364             result.append('<%s%s%s>' % (tag, attributes, is_self_closing))
00365             if newline:
00366                 result.append(self._newline())
00367             if _id_debug and id:
00368                 result.append('(%s) ' % id)
00369             tagstr = ''.join(result)
00370         else:
00371             # Inline elements
00372             tagstr = '<%s%s%s>' % (tag,
00373                                       self._formatAttributes(attr, allowed_attrs, **kw),
00374                                       is_self_closing)
00375         return tagstr
00376 
00377     def _close(self, tag, newline=False):
00378         """ Close tag (INTERNAL USE BY HTML FORMATTER ONLY!)
00379 
00380         @param tag: html tag, string
00381         @param newline: render tag so following data is on a separate line
00382         @rtype: string
00383         @return: closing tag as a string
00384         """
00385         if tag in _self_closing_tags or (tag in _auto_closing_tags and not self.no_magic):
00386             # This tag was already closed
00387             tagstr = ''
00388         elif tag in _blocks:
00389             # Block elements
00390             result = []
00391             if newline:
00392                 result.append(self._newline())
00393             result.append('</%s>' % (tag))
00394             tagstr = ''.join(result)
00395         else:
00396             # Inline elements
00397             tagstr = '</%s>' % tag
00398 
00399         if newline:
00400             tagstr += self._newline()
00401         return tagstr
00402 
00403     # Public methods ###################################################
00404 
00405     def startContent(self, content_id='content', newline=True, **kw):
00406         """ Start page content div.
00407 
00408         A link anchor is provided at the beginning of the div, with
00409         an id of 'top' (modified by the request ID cache).
00410         """
00411 
00412         if hasattr(self, 'page'):
00413             self.request.uid_generator.begin(self.page.page_name)
00414 
00415         result = []
00416         # Use the content language
00417         attr = self._langAttr(self.request.content_lang)
00418         attr['id'] = content_id
00419         result.append(self._open('div', newline=False, attr=attr,
00420                                  allowed_attrs=['align'], **kw))
00421         result.append(self.anchordef('top'))
00422         if newline:
00423             result.append('\n')
00424         return ''.join(result)
00425 
00426     def endContent(self, newline=True):
00427         """ Close page content div.
00428 
00429         A link anchor is provided at the end of the div, with
00430         an id of 'bottom' (modified by the request ID cache).
00431         """
00432 
00433         result = []
00434         result.append(self.anchordef('bottom'))
00435         result.append(self._close('div', newline=newline))
00436         if hasattr(self, 'page'):
00437             self.request.uid_generator.end()
00438         return ''.join(result)
00439 
00440     def lang(self, on, lang_name):
00441         """ Insert text with specific lang and direction.
00442 
00443             Enclose within span tag if lang_name is different from
00444             the current lang
00445         """
00446         tag = 'span'
00447         if lang_name != self.request.current_lang:
00448             # Enclose text in span using lang attributes
00449             if on:
00450                 attr = self._langAttr(lang=lang_name)
00451                 return self._open(tag, attr=attr)
00452             return self._close(tag)
00453 
00454         # Direction did not change, no need for span
00455         return ''
00456 
00457     # Links ##############################################################
00458 
00459     def pagelink(self, on, pagename='', page=None, **kw):
00460         """ Link to a page.
00461 
00462             formatter.text_python will use an optimized call with a page!=None
00463             parameter. DO NOT USE THIS YOURSELF OR IT WILL BREAK.
00464 
00465             See wikiutil.link_tag() for possible keyword parameters.
00466         """
00467         FormatterBase.pagelink(self, on, pagename, page, **kw)
00468         if 'generated' in kw:
00469             del kw['generated']
00470         if page is None:
00471             page = Page(self.request, pagename, formatter=self)
00472         if self.request.user.show_nonexist_qm and on and not page.exists():
00473             self.pagelink_preclosed = True
00474             return (page.link_to(self.request, on=1, **kw) +
00475                     self.text("?") +
00476                     page.link_to(self.request, on=0, **kw))
00477         elif not on and self.pagelink_preclosed:
00478             self.pagelink_preclosed = False
00479             return ""
00480         else:
00481             return page.link_to(self.request, on=on, **kw)
00482 
00483     def interwikilink(self, on, interwiki='', pagename='', **kw):
00484         """
00485         @keyword title: override using the interwiki wikiname as title
00486         """
00487         querystr = kw.get('querystr', {})
00488         wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_interwiki(self.request, interwiki, pagename)
00489         wikiurl = wikiutil.mapURL(self.request, wikiurl)
00490         if wikitag == 'Self': # for own wiki, do simple links
00491             wikitail = wikiutil.url_unquote(wikitail)
00492             try: # XXX this is the only place where we access self.page - do we need it? Crashes silently on actions!
00493                 pagename = wikiutil.AbsPageName(self.page.page_name, wikitail)
00494             except:
00495                 pagename = wikitail
00496             return self.pagelink(on, pagename, **kw)
00497         else: # return InterWiki hyperlink
00498             if on:
00499                 href = wikiutil.join_wiki(wikiurl, wikitail)
00500                 if querystr:
00501                     separator = ('?', '&')['?' in href]
00502                     href = '%s%s%s' % (href, separator, wikiutil.makeQueryString(querystr))
00503                 anchor = kw.get('anchor')
00504                 if anchor:
00505                     href = '%s#%s' % (href, self.sanitize_to_id(anchor))
00506                 if wikitag_bad:
00507                     html_class = 'badinterwiki'
00508                 else:
00509                     html_class = 'interwiki'
00510                 title = kw.get('title', wikitag)
00511                 return self.url(1, href, title=title, css=html_class) # interwiki links with umlauts
00512             else:
00513                 return self.url(0)
00514 
00515     def url(self, on, url=None, css=None, do_escape=None, **kw):
00516         """
00517         Inserts an <a> element (you can give any A tag attributes as kw args).
00518 
00519         @param on: 1 to start the link, 0 to end the link (no other arguments are needed when on==0).
00520         @param url: the URL to link to; will go through Wiki URL mapping.
00521         @param css: a space-separated list of CSS classes
00522         @param do_escape: DEPRECATED and not used any more, please remove it from your code!
00523                           We will remove this parameter in moin 1.8 (it used to filter url
00524                           param through wikiutil.escape, but text_html formatter's _open
00525                           will do it again, so this just leads to double escaping now).
00526         """
00527         if do_escape is not None:
00528             if do_escape:
00529                 logging.warning("Deprecation warning: MoinMoin.formatter.text_html.url being called with do_escape=1/True parameter, please review caller.")
00530             else:
00531                 logging.warning("Deprecation warning: MoinMoin.formatter.text_html.url being called with do_escape=0/False parameter, please remove it from the caller.")
00532         if on:
00533             attrs = self._langAttr()
00534 
00535             # Handle the URL mapping
00536             if url is None and 'href' in kw:
00537                 url = kw['href']
00538                 del kw['href']
00539             if url is not None:
00540                 url = wikiutil.mapURL(self.request, url)
00541                 attrs['href'] = url
00542 
00543             if css:
00544                 attrs['class'] = css
00545 
00546             markup = self._open('a', attr=attrs, **kw)
00547         else:
00548             markup = self._close('a')
00549         return markup
00550 
00551     def anchordef(self, id):
00552         """Inserts an invisible element used as a link target.
00553 
00554         Inserts an empty <span> element with an id attribute, used as an anchor
00555         for link references.  We use <span></span> rather than <span/>
00556         for browser portability.
00557         """
00558         # Don't add newlines, \n, as it will break pre and
00559         # line-numbered code sections (from line_achordef() method).
00560         #return '<a id="%s"></a>' % (id, ) # do not use - this breaks PRE sections for IE
00561         id = self.make_id_unique(id)
00562         id = self.qualify_id(id)
00563         return '<span class="anchor" id="%s"></span>' % id
00564 
00565     def line_anchordef(self, lineno):
00566         if line_anchors:
00567             return self.anchordef("line-%d" % lineno)
00568         else:
00569             return ''
00570 
00571     def anchorlink(self, on, name='', **kw):
00572         """Insert an <a> link pointing to an anchor on the same page.
00573 
00574         Call once with on=1 to start the link, and a second time with
00575         on=0 to end it.  No other arguments are needed on the second
00576         call.
00577 
00578         The name argument should be the same as the id provided to the
00579         anchordef() method, or some other elment.  It should NOT start
00580         with '#' as that will be added automatically.
00581 
00582         The id argument, if provided, is instead the id of this link
00583         itself and not of the target element the link references.
00584         """
00585         attrs = self._langAttr()
00586         if name:
00587             name = self.sanitize_to_id(name)
00588             attrs['href'] = '#' + self.qualify_id(name)
00589         if 'href' in kw:
00590             del kw['href']
00591         if on:
00592             str = self._open('a', attr=attrs, **kw)
00593         else:
00594             str = self._close('a')
00595         return str
00596 
00597     def line_anchorlink(self, on, lineno=0):
00598         if line_anchors:
00599             return self.anchorlink(on, name="line-%d" % lineno)
00600         else:
00601             return ''
00602 
00603     # Attachments ######################################################
00604 
00605     def attachment_link(self, on, url=None, querystr=None, **kw):
00606         """ Link to an attachment.
00607 
00608             @param on: 1/True=start link, 0/False=end link
00609             @param url: filename.ext or PageName/filename.ext
00610         """
00611         assert on in (0, 1, False, True) # make sure we get called the new way, not like the 1.5 api was
00612         _ = self.request.getText
00613         if querystr is None:
00614             querystr = {}
00615         assert isinstance(querystr, dict) # new in 1.6, only support dicts
00616         if 'do' not in querystr:
00617             querystr['do'] = 'view'
00618         if on:
00619             pagename, filename = AttachFile.absoluteName(url, self.page.page_name)
00620             #logging.debug("attachment_link: url %s pagename %s filename %s" % (url, pagename, filename))
00621             fname = wikiutil.taintfilename(filename)
00622             if AttachFile.exists(self.request, pagename, fname):
00623                 target = AttachFile.getAttachUrl(pagename, fname, self.request, do=querystr['do'])
00624                 if not 'title' in kw:
00625                     kw['title'] = "attachment:%s" % url
00626                 kw['css'] = 'attachment'
00627             else:
00628                 target = AttachFile.getAttachUrl(pagename, fname, self.request, do='upload_form')
00629                 kw['title'] = _('Upload new attachment "%(filename)s"') % {'filename': fname}
00630                 kw['css'] = 'attachment nonexistent'
00631             return self.url(on, target, **kw)
00632         else:
00633             return self.url(on)
00634 
00635     def attachment_image(self, url, **kw):
00636         _ = self.request.getText
00637         pagename, filename = AttachFile.absoluteName(url, self.page.page_name)
00638         fname = wikiutil.taintfilename(filename)
00639         exists = AttachFile.exists(self.request, pagename, fname)
00640         if exists:
00641             kw['css'] = 'attachment'
00642             kw['src'] = AttachFile.getAttachUrl(pagename, fname, self.request, addts=1)
00643             title = _('Inlined image: %(url)s') % {'url': self.text(url)}
00644             if not 'title' in kw:
00645                 kw['title'] = title
00646             # alt is required for images:
00647             if not 'alt' in kw:
00648                 kw['alt'] = kw['title']
00649             return self.image(**kw)
00650         else:
00651             title = _('Upload new attachment "%(filename)s"') % {'filename': fname}
00652             img = self.icon('attachimg')
00653             css = 'nonexistent'
00654             target = AttachFile.getAttachUrl(pagename, fname, self.request, do='upload_form')
00655             return self.url(1, target, css=css, title=title) + img + self.url(0)
00656 
00657     def attachment_drawing(self, url, text, **kw):
00658         # ToDo try to move this to a better place e.g. __init__
00659         try:
00660             drawing_action = AttachFile.get_action(self.request, url, do='modify')
00661             assert drawing_action is not None
00662             attachment_drawing = wikiutil.importPlugin(self.request.cfg, 'action',
00663                                               drawing_action, 'attachment_drawing')
00664             return attachment_drawing(self, url, text, **kw)
00665         except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError, AssertionError):
00666             return url
00667 
00668     # Text ##############################################################
00669 
00670     def _text(self, text):
00671         text = wikiutil.escape(text)
00672         if self._in_code:
00673             text = text.replace(' ', self.hardspace)
00674         return text
00675 
00676     # Inline ###########################################################
00677 
00678     def strong(self, on, **kw):
00679         """Creates an HTML <strong> element.
00680 
00681         Call once with on=1 to start the region, and a second time
00682         with on=0 to end it.
00683         """
00684         tag = 'strong'
00685         if on:
00686             return self._open(tag, allowed_attrs=[], **kw)
00687         return self._close(tag)
00688 
00689     def emphasis(self, on, **kw):
00690         """Creates an HTML <em> element.
00691 
00692         Call once with on=1 to start the region, and a second time
00693         with on=0 to end it.
00694         """
00695         tag = 'em'
00696         if on:
00697             return self._open(tag, allowed_attrs=[], **kw)
00698         return self._close(tag)
00699 
00700     def underline(self, on, **kw):
00701         """Creates a text span for underlining (css class "u").
00702 
00703         Call once with on=1 to start the region, and a second time
00704         with on=0 to end it.
00705         """
00706         tag = 'span'
00707         if on:
00708             return self._open(tag, attr={'class': 'u'}, allowed_attrs=[], **kw)
00709         return self._close(tag)
00710 
00711     def highlight(self, on, **kw):
00712         """Creates a text span for highlighting (css class "highlight").
00713 
00714         Call once with on=1 to start the region, and a second time
00715         with on=0 to end it.
00716         """
00717         tag = 'strong'
00718         if on:
00719             return self._open(tag, attr={'class': 'highlight'}, allowed_attrs=[], **kw)
00720         return self._close(tag)
00721 
00722     def sup(self, on, **kw):
00723         """Creates a <sup> element for superscript text.
00724 
00725         Call once with on=1 to start the region, and a second time
00726         with on=0 to end it.
00727         """
00728         tag = 'sup'
00729         if on:
00730             return self._open(tag, allowed_attrs=[], **kw)
00731         return self._close(tag)
00732 
00733     def sub(self, on, **kw):
00734         """Creates a <sub> element for subscript text.
00735 
00736         Call once with on=1 to start the region, and a second time
00737         with on=0 to end it.
00738         """
00739         tag = 'sub'
00740         if on:
00741             return self._open(tag, allowed_attrs=[], **kw)
00742         return self._close(tag)
00743 
00744     def strike(self, on, **kw):
00745         """Creates a text span for line-through (strikeout) text (css class 'strike').
00746 
00747         Call once with on=1 to start the region, and a second time
00748         with on=0 to end it.
00749         """
00750         # This does not use <strike> because has been deprecated in standard HTML.
00751         tag = 'span'
00752         if on:
00753             return self._open(tag, attr={'class': 'strike'},
00754                               allowed_attrs=[], **kw)
00755         return self._close(tag)
00756 
00757     def code(self, on, **kw):
00758         """Creates a <tt> element for inline code or monospaced text.
00759 
00760         Call once with on=1 to start the region, and a second time
00761         with on=0 to end it.
00762 
00763         Any text within this section will have spaces converted to
00764         non-break spaces.
00765         """
00766         tag = 'tt'
00767         self._in_code = on
00768         if on:
00769             return self._open(tag, allowed_attrs=[], **kw)
00770         return self._close(tag)
00771 
00772     def small(self, on, **kw):
00773         """Creates a <small> element for smaller font.
00774 
00775         Call once with on=1 to start the region, and a second time
00776         with on=0 to end it.
00777         """
00778         tag = 'small'
00779         if on:
00780             return self._open(tag, allowed_attrs=[], **kw)
00781         return self._close(tag)
00782 
00783     def big(self, on, **kw):
00784         """Creates a <big> element for larger font.
00785 
00786         Call once with on=1 to start the region, and a second time
00787         with on=0 to end it.
00788         """
00789         tag = 'big'
00790         if on:
00791             return self._open(tag, allowed_attrs=[], **kw)
00792         return self._close(tag)
00793 
00794 
00795     # Block elements ####################################################
00796 
00797     def preformatted(self, on, **kw):
00798         """Creates a preformatted text region, with a <pre> element.
00799 
00800         Call once with on=1 to start the region, and a second time
00801         with on=0 to end it.
00802         """
00803         FormatterBase.preformatted(self, on)
00804         tag = 'pre'
00805         if on:
00806             return self._open(tag, newline=1, **kw)
00807         return self._close(tag)
00808 
00809     # Use by code area
00810     _toggleLineNumbersScript = """
00811 <script type="text/javascript">
00812 function isnumbered(obj) {
00813   return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber';
00814 }
00815 function nformat(num,chrs,add) {
00816   var nlen = Math.max(0,chrs-(''+num).length), res = '';
00817   while (nlen>0) { res += ' '; nlen-- }
00818   return res+num+add;
00819 }
00820 function addnumber(did, nstart, nstep) {
00821   var c = document.getElementById(did), l = c.firstChild, n = 1;
00822   if (!isnumbered(c))
00823     if (typeof nstart == 'undefined') nstart = 1;
00824     if (typeof nstep  == 'undefined') nstep = 1;
00825     n = nstart;
00826     while (l != null) {
00827       if (l.tagName == 'SPAN') {
00828         var s = document.createElement('SPAN');
00829         s.className = 'LineNumber'
00830         s.appendChild(document.createTextNode(nformat(n,4,' ')));
00831         n += nstep;
00832         if (l.childNodes.length)
00833           l.insertBefore(s, l.firstChild)
00834         else
00835           l.appendChild(s)
00836       }
00837       l = l.nextSibling;
00838     }
00839   return false;
00840 }
00841 function remnumber(did) {
00842   var c = document.getElementById(did), l = c.firstChild;
00843   if (isnumbered(c))
00844     while (l != null) {
00845       if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild);
00846       l = l.nextSibling;
00847     }
00848   return false;
00849 }
00850 function togglenumber(did, nstart, nstep) {
00851   var c = document.getElementById(did);
00852   if (isnumbered(c)) {
00853     remnumber(did);
00854   } else {
00855     addnumber(did,nstart,nstep);
00856   }
00857   return false;
00858 }
00859 </script>
00860 """
00861 
00862     def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1, msg=None):
00863         """Creates a formatted code region, with line numbering.
00864 
00865         This region is formatted as a <div> with a <pre> inside it.  The
00866         code_id argument is assigned to the 'id' of the div element, and
00867         must be unique within the document.  The show, start, and step are
00868         used for line numbering.
00869 
00870         Note this is not like most formatter methods, it can not take any
00871         extra keyword arguments.
00872 
00873         Call once with on=1 to start the region, and a second time
00874         with on=0 to end it.
00875 
00876         the msg string is not escaped
00877         """
00878         _ = self.request.getText
00879         res = []
00880         if on:
00881             code_id = self.sanitize_to_id('CA-%s' % code_id)
00882             ci = self.qualify_id(self.make_id_unique(code_id))
00883 
00884             # Open a code area
00885             self._in_code_area = 1
00886             self._in_code_line = 0
00887             # id in here no longer used
00888             self._code_area_state = [None, show, start, step, start]
00889 
00890             if msg:
00891                 attr = {'class': 'codemsg'}
00892                 res.append(self._open('div', attr={'class': 'codemsg'}))
00893                 res.append(msg)
00894                 res.append(self._close('div'))
00895 
00896             # Open the code div - using left to right always!
00897             attr = {'class': 'codearea', 'lang': 'en', 'dir': 'ltr'}
00898             res.append(self._open('div', attr=attr))
00899 
00900             # Add the script only in the first code area on the page
00901             if self._code_area_js == 0 and self._code_area_state[1] >= 0:
00902                 res.append(self._toggleLineNumbersScript)
00903                 self._code_area_js = 1
00904 
00905             # Add line number link, but only for JavaScript enabled browsers.
00906             if self._code_area_state[1] >= 0:
00907                 toggleLineNumbersLink = r'''
00908 <script type="text/javascript">
00909 document.write('<a href="#" onclick="return togglenumber(\'%s\', %d, %d);" \
00910                 class="codenumbers">%s<\/a>');
00911 </script>
00912 ''' % (ci, self._code_area_state[2], self._code_area_state[3],
00913        _("Toggle line numbers"))
00914                 res.append(toggleLineNumbersLink)
00915 
00916             # Open pre - using left to right always!
00917             attr = {'id': ci, 'lang': 'en', 'dir': 'ltr'}
00918             res.append(self._open('pre', newline=True, attr=attr, is_unique=True))
00919         else:
00920             # Close code area
00921             res = []
00922             if self._in_code_line:
00923                 res.append(self.code_line(0))
00924             res.append(self._close('pre'))
00925             res.append(self._close('div'))
00926 
00927             # Update state
00928             self._in_code_area = 0
00929 
00930         return ''.join(res)
00931 
00932     def code_line(self, on):
00933         res = ''
00934         if not on or (on and self._in_code_line):
00935             res += '</span>\n'
00936         if on:
00937             res += '<span class="line">'
00938             if self._code_area_state[1] > 0:
00939                 res += '<span class="LineNumber">%4d </span>' % (self._code_area_state[4], )
00940                 self._code_area_state[4] += self._code_area_state[3]
00941         self._in_code_line = on != 0
00942         return res
00943 
00944     def code_token(self, on, tok_type):
00945         return ['<span class="%s">' % tok_type, '</span>'][not on]
00946 
00947     # Paragraphs, Lines, Rules ###########################################
00948 
00949     def _indent_spaces(self):
00950         """Returns space(s) for indenting the html source so list nesting is easy to read.
00951 
00952         Note that this mostly works, but because of caching may not always be accurate."""
00953         if prettyprint:
00954             return self.indentspace * self._indent_level
00955         else:
00956             return ''
00957 
00958     def _newline(self):
00959         """Returns the whitespace for starting a new html source line, properly indented."""
00960         if prettyprint:
00961             return '\n' + self._indent_spaces()
00962         else:
00963             return ''
00964 
00965     def linebreak(self, preformatted=1):
00966         """Creates a line break in the HTML output.
00967 
00968         If preformatted is true a <br> element is inserted, otherwise
00969         the linebreak will only be visible in the HTML source.
00970         """
00971         if self._in_code_area:
00972             preformatted = 1
00973         return ['\n', '<br>\n'][not preformatted] + self._indent_spaces()
00974 
00975     def paragraph(self, on, **kw):
00976         """Creates a paragraph with a <p> element.
00977 
00978         Call once with on=1 to start the region, and a second time
00979         with on=0 to end it.
00980         """
00981         if self._terse:
00982             return ''
00983         FormatterBase.paragraph(self, on)
00984         tag = 'p'
00985         if on:
00986             tagstr = self._open(tag, **kw)
00987         else:
00988             tagstr = self._close(tag)
00989         return tagstr
00990 
00991     def rule(self, size=None, **kw):
00992         """Creates a horizontal rule with an <hr> element.
00993 
00994         If size is a number in the range [1..6], the CSS class of the rule
00995         is set to 'hr1' through 'hr6'.  The intent is that the larger the
00996         size number the thicker or bolder the rule will be.
00997         """
00998         if size and 1 <= size <= 6:
00999             # Add hr class: hr1 - hr6
01000             return self._open('hr', newline=1, attr={'class': 'hr%d' % size}, **kw)
01001         return self._open('hr', newline=1, **kw)
01002 
01003     # Images / Transclusion ##############################################
01004 
01005     def icon(self, type):
01006         return self.request.theme.make_icon(type)
01007 
01008     smiley = icon
01009 
01010     def image(self, src=None, **kw):
01011         """Creates an inline image with an <img> element.
01012 
01013         The src argument must be the URL to the image file.
01014         """
01015         if src:
01016             kw['src'] = src
01017         return self._open('img', **kw)
01018 
01019     def transclusion(self, on, **kw):
01020         """Transcludes (includes/embeds) another object."""
01021         if on:
01022             return self._open('object',
01023                               allowed_attrs=['archive', 'classid', 'codebase',
01024                                              'codetype', 'data', 'declare',
01025                                              'height', 'name', 'standby',
01026                                              'type', 'width', ],
01027                               **kw)
01028         else:
01029             return self._close('object')
01030 
01031     def transclusion_param(self, **kw):
01032         """Give a parameter to a transcluded object."""
01033         return self._open('param',
01034                           allowed_attrs=['name', 'type', 'value', 'valuetype', ],
01035                           **kw)
01036 
01037     # Lists ##############################################################
01038 
01039     def number_list(self, on, type=None, start=None, **kw):
01040         """Creates an HTML ordered list, <ol> element.
01041 
01042         The 'type' if specified can be any legal numbered
01043         list-style-type, such as 'decimal','lower-roman', etc.
01044 
01045         The 'start' argument if specified gives the numeric value of
01046         the first list item (default is 1).
01047 
01048         Call once with on=1 to start the list, and a second time
01049         with on=0 to end it.
01050         """
01051         tag = 'ol'
01052         if on:
01053             attr = {}
01054             if type is not None:
01055                 attr['type'] = type
01056             if start is not None:
01057                 attr['start'] = start
01058             tagstr = self._open(tag, newline=1, attr=attr, **kw)
01059         else:
01060             tagstr = self._close(tag, newline=1)
01061         return tagstr
01062 
01063     def bullet_list(self, on, **kw):
01064         """Creates an HTML ordered list, <ul> element.
01065 
01066         The 'type' if specified can be any legal unnumbered
01067         list-style-type, such as 'disc','square', etc.
01068 
01069         Call once with on=1 to start the list, and a second time
01070         with on=0 to end it.
01071         """
01072         tag = 'ul'
01073         if on:
01074             tagstr = self._open(tag, newline=1, **kw)
01075         else:
01076             tagstr = self._close(tag, newline=1)
01077         return tagstr
01078 
01079     def listitem(self, on, **kw):
01080         """Adds a list item, <li> element, to a previously opened
01081         bullet or number list.
01082 
01083         Call once with on=1 to start the region, and a second time
01084         with on=0 to end it.
01085         """
01086         tag = 'li'
01087         if on:
01088             tagstr = self._open(tag, newline=1, **kw)
01089         else:
01090             tagstr = self._close(tag, newline=1)
01091         return tagstr
01092 
01093     def definition_list(self, on, **kw):
01094         """Creates an HTML definition list, <dl> element.
01095 
01096         Call once with on=1 to start the list, and a second time
01097         with on=0 to end it.
01098         """
01099         tag = 'dl'
01100         if on:
01101             tagstr = self._open(tag, newline=1, **kw)
01102         else:
01103             tagstr = self._close(tag, newline=1)
01104         return tagstr
01105 
01106     def definition_term(self, on, **kw):
01107         """Adds a new term to a definition list, HTML element <dt>.
01108 
01109         Call once with on=1 to start the term, and a second time
01110         with on=0 to end it.
01111         """
01112         tag = 'dt'
01113         if on:
01114             tagstr = self._open(tag, newline=1, **kw)
01115         else:
01116             tagstr = self._close(tag, newline=0)
01117         return tagstr
01118 
01119     def definition_desc(self, on, **kw):
01120         """Gives the definition to a definition item, HTML element <dd>.
01121 
01122         Call once with on=1 to start the definition, and a second time
01123         with on=0 to end it.
01124         """
01125         tag = 'dd'
01126         if on:
01127             tagstr = self._open(tag, newline=1, **kw)
01128         else:
01129             tagstr = self._close(tag, newline=0)
01130         return tagstr
01131 
01132     def heading(self, on, depth, **kw):
01133         # remember depth of first heading, and adapt counting depth accordingly
01134         if not self._base_depth:
01135             self._base_depth = depth
01136 
01137         count_depth = max(depth - (self._base_depth - 1), 1)
01138 
01139         # check numbering, possibly changing the default
01140         if self._show_section_numbers is None:
01141             self._show_section_numbers = self.cfg.show_section_numbers
01142             numbering = self.request.getPragma('section-numbers', '').lower()
01143             if numbering in ['0', 'off']:
01144                 self._show_section_numbers = 0
01145             elif numbering in ['1', 'on']:
01146                 self._show_section_numbers = 1
01147             elif numbering in ['2', '3', '4', '5', '6']:
01148                 # explicit base level for section number display
01149                 self._show_section_numbers = int(numbering)
01150 
01151         heading_depth = depth
01152 
01153         # closing tag, with empty line after, to make source more readable
01154         if not on:
01155             return self._close('h%d' % heading_depth) + '\n'
01156 
01157         # create section number
01158         number = ''
01159         if self._show_section_numbers:
01160             # count headings on all levels
01161             self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth]
01162             while len(self.request._fmt_hd_counters) < count_depth:
01163                 self.request._fmt_hd_counters.append(0)
01164             self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1
01165             number = '.'.join([str(x) for x in self.request._fmt_hd_counters[self._show_section_numbers-1:]])
01166             if number: number += ". "
01167 
01168         # Add space before heading, easier to check source code
01169         result = '\n' + self._open('h%d' % heading_depth, **kw)
01170 
01171         if self.request.user.show_topbottom:
01172             result += "%s%s%s%s%s%s" % (
01173                        self.anchorlink(1, "bottom"), self.icon('bottom'), self.anchorlink(0),
01174                        self.anchorlink(1, "top"), self.icon('top'), self.anchorlink(0))
01175 
01176         return "%s%s" % (result, number)
01177 
01178 
01179     # Tables #############################################################
01180 
01181     _allowed_table_attrs = {
01182         'table': ['class', 'id', 'style'],
01183         'row': ['class', 'id', 'style'],
01184         '': ['colspan', 'rowspan', 'class', 'id', 'style', 'abbr'],
01185     }
01186 
01187     def _checkTableAttr(self, attrs, prefix):
01188         """ Check table attributes
01189 
01190         Convert from wikitable attributes to html 4 attributes.
01191 
01192         @param attrs: attribute dict
01193         @param prefix: used in wiki table attributes
01194         @rtype: dict
01195         @return: valid table attributes
01196         """
01197         if not attrs:
01198             return {}
01199 
01200         result = {}
01201         s = [] # we collect synthesized style in s
01202         for key, val in attrs.items():
01203             # Ignore keys that don't start with prefix
01204             if prefix and key[:len(prefix)] != prefix:
01205                 continue
01206             key = key[len(prefix):]
01207             val = val.strip('"')
01208             # remove invalid attrs from dict and synthesize style
01209             if key == 'width':
01210                 s.append("width: %s" % val)
01211             elif key == 'height':
01212                 s.append("height: %s" % val)
01213             elif key == 'bgcolor':
01214                 s.append("background-color: %s" % val)
01215             elif key == 'align':
01216                 s.append("text-align: %s" % val)
01217             elif key == 'valign':
01218                 s.append("vertical-align: %s" % val)
01219             # Ignore unknown keys
01220             if key not in self._allowed_table_attrs[prefix]:
01221                 continue
01222             result[key] = val
01223         st = result.get('style', '').split(';')
01224         st = '; '.join(st + s)
01225         st = st.strip(';')
01226         st = st.strip()
01227         if not st:
01228             try:
01229                 del result['style'] # avoid empty style attr
01230             except:
01231                 pass
01232         else:
01233             result['style'] = st
01234         #logging.debug("_checkTableAttr returns %r" % result)
01235         return result
01236 
01237 
01238     def table(self, on, attrs=None, **kw):
01239         """ Create table
01240 
01241         @param on: start table
01242         @param attrs: table attributes
01243         @rtype: string
01244         @return start or end tag of a table
01245         """
01246         result = []
01247         if on:
01248             # Open div to get correct alignment with table width smaller
01249             # than 100%
01250             result.append(self._open('div', newline=1))
01251 
01252             # Open table
01253             if not attrs:
01254                 attrs = {}
01255             else:
01256                 attrs = self._checkTableAttr(attrs, 'table')
01257             result.append(self._open('table', newline=1, attr=attrs,
01258                                      allowed_attrs=self._allowed_table_attrs['table'],
01259                                      **kw))
01260             result.append(self._open('tbody', newline=1))
01261         else:
01262             # Close tbody, table, and then div
01263             result.append(self._close('tbody'))
01264             result.append(self._close('table'))
01265             result.append(self._close('div'))
01266 
01267         return ''.join(result)
01268 
01269     def table_row(self, on, attrs=None, **kw):
01270         tag = 'tr'
01271         if on:
01272             if not attrs:
01273                 attrs = {}
01274             else:
01275                 attrs = self._checkTableAttr(attrs, 'row')
01276             return self._open(tag, newline=1, attr=attrs,
01277                              allowed_attrs=self._allowed_table_attrs['row'],
01278                              **kw)
01279         return self._close(tag) + '\n'
01280 
01281     def table_cell(self, on, attrs=None, **kw):
01282         tag = 'td'
01283         if on:
01284             if not attrs:
01285                 attrs = {}
01286             else:
01287                 attrs = self._checkTableAttr(attrs, '')
01288             return '  ' + self._open(tag, attr=attrs,
01289                              allowed_attrs=self._allowed_table_attrs[''],
01290                              **kw)
01291         return self._close(tag) + '\n'
01292 
01293     def text(self, text, **kw):
01294         txt = FormatterBase.text(self, text, **kw)
01295         if kw:
01296             return self._open('span', **kw) + txt + self._close('span')
01297         return txt
01298 
01299     def escapedText(self, text, **kw):
01300         txt = wikiutil.escape(text)
01301         if kw:
01302             return self._open('span', **kw) + txt + self._close('span')
01303         return txt
01304 
01305     def rawHTML(self, markup):
01306         return markup
01307 
01308     def sysmsg(self, on, **kw):
01309         tag = 'div'
01310         if on:
01311             return self._open(tag, attr={'class': 'message'}, **kw)
01312         return self._close(tag)
01313 
01314     def div(self, on, **kw):
01315         css_class = kw.get('css_class')
01316         # the display of comment class divs depends on a user setting:
01317         if css_class and 'comment' in css_class.split():
01318             style = kw.get('style')
01319             display = self.request.user.show_comments and "display:''" or "display:none"
01320             if not style:
01321                 style = display
01322             else:
01323                 style += "; %s" % display
01324             kw['style'] = style
01325         tag = 'div'
01326         if on:
01327             return self._open(tag, **kw)
01328         return self._close(tag)
01329 
01330     def span(self, on, **kw):
01331         css_class = kw.get('css_class')
01332         # the display of comment class spans depends on a user setting:
01333         if css_class and 'comment' in css_class.split():
01334             style = kw.get('style')
01335             display = self.request.user.show_comments and "display:''" or "display:none"
01336             if not style:
01337                 style = display
01338             else:
01339                 style += "; %s" % display
01340             kw['style'] = style
01341         tag = 'span'
01342         if on:
01343             return self._open(tag, **kw)
01344         return self._close(tag)
01345 
01346     def sanitize_to_id(self, text):
01347         return wikiutil.anchor_name_from_text(text)
01348