comparison MoinMoin/formatter/text_html.py @ 0:77665d8e2254

tag of nonpublic@localhost--archive/moin--enterprise--1.5--base-0 (automatically generated log message) imported from: moin--main--1.5--base-0
author Thomas Waldmann <tw-public@gmx.de>
date Thu, 22 Sep 2005 15:09:50 +0000
parents
children 3ffdb52c6969
comparison
equal deleted inserted replaced
-1:000000000000 0:77665d8e2254
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - "text/html+css" Formatter
4
5 @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
6 @license: GNU GPL, see COPYING for details.
7 """
8 import os.path, urllib
9 from MoinMoin.formatter.base import FormatterBase
10 from MoinMoin import wikiutil, i18n, config
11 from MoinMoin.Page import Page
12 from MoinMoin.action import AttachFile
13
14 class Formatter(FormatterBase):
15 """
16 Send HTML data.
17 """
18
19 hardspace = '&nbsp;'
20
21 def __init__(self, request, **kw):
22 apply(FormatterBase.__init__, (self, request), kw)
23
24 # inline tags stack. When an inline tag is called, it goes into
25 # the stack. When a block element starts, all inline tags in
26 # the stack are closed.
27 self._inlineStack = []
28
29 self._in_li = 0
30 self._in_code = 0
31 self._in_code_area = 0
32 self._in_code_line = 0
33 self._code_area_num = 0
34 self._code_area_js = 0
35 self._code_area_state = ['', 0, -1, -1, 0]
36 self._show_section_numbers = None
37 self._content_ids = []
38 self.pagelink_preclosed = False
39 self._is_included = kw.get('is_included',False)
40 self.request = request
41 self.cfg = request.cfg
42
43 if not hasattr(request, '_fmt_hd_counters'):
44 request._fmt_hd_counters = []
45
46 # Primitive formatter functions #####################################
47
48 # all other methods should use these to format tags. This keeps the
49 # code clean and handle pathological cases like unclosed p and
50 # inline tags.
51
52 def langAttr(self, lang=None):
53 """ Return lang and dir attribute
54
55 Must be used on all block elements - div, p, table, etc.
56 @param lang: if defined, will return attributes for lang. if not
57 defined, will return attributes only if the current lang is
58 different from the content lang.
59 @rtype: dict
60 @retrun: language attributes
61 """
62 if not lang:
63 lang = self.request.current_lang
64 # Actions that generate content in user language should change
65 # the content lang from the default defined in cfg.
66 if lang == self.request.content_lang:
67 # lang is inherited from content div
68 return {}
69
70 attr = {'lang': lang, 'dir': i18n.getDirection(lang),}
71 return attr
72
73 def formatAttributes(self, attr=None):
74 """ Return formatted attributes string
75
76 @param attr: dict containing keys and values
77 @rtype: string ?
78 @return: formated attributes or empty string
79 """
80 if attr:
81 attr = [' %s="%s"' % (k, v) for k, v in attr.items()]
82 return ''.join(attr)
83 return ''
84
85 # TODO: use set when we require Python 2.3
86 # TODO: The list is not complete, add missing from dtd
87 _blocks = 'p div pre table tr td ol ul dl li dt dd h1 h2 h3 h4 h5 h6 hr form'
88 _blocks = dict(zip(_blocks.split(), [1] * len(_blocks)))
89
90 def open(self, tag, newline=False, attr=None):
91 """ Open a tag with optional attributes
92
93 @param tag: html tag, string
94 @param newline: render tag on a separate line
95 @parm attr: dict with tag attributes
96 @rtype: string ?
97 @return: open tag with attributes
98 """
99 if tag in self._blocks:
100 # Block elements
101 result = []
102
103 # Add language attributes, but let caller overide the default
104 attributes = self.langAttr()
105 if attr:
106 attributes.update(attr)
107
108 # Format
109 attributes = self.formatAttributes(attributes)
110 result.append('<%s%s>' % (tag, attributes))
111 if newline:
112 result.append('\n')
113 return ''.join(result)
114 else:
115 # Inline elements
116 # Add to inlineStack
117 self._inlineStack.append(tag)
118 # Format
119 return '<%s%s>' % (tag, self.formatAttributes(attr))
120
121 def close(self, tag, newline=False):
122 """ Close tag
123
124 @param tag: html tag, string
125 @rtype: string ?
126 @return: closing tag
127 """
128 if tag in self._blocks:
129 # Block elements
130 # Close all tags in inline stack
131 # Work on a copy, because close(inline) manipulate the stack
132 result = []
133 stack = self._inlineStack[:]
134 stack.reverse()
135 for inline in stack:
136 result.append(self.close(inline))
137 # Format with newline
138 if newline:
139 result.append('\n')
140 result.append('</%s>\n' % (tag))
141 return ''.join(result)
142 else:
143 # Inline elements
144 # Pull from stack, ignore order, that is not our problem.
145 # The code that calls us should keep correct calling order.
146 if tag in self._inlineStack:
147 self._inlineStack.remove(tag)
148 return '</%s>' % tag
149
150
151 # Public methods ###################################################
152
153 def startContent(self, content_id='content', **kwargs):
154 """ Start page content div """
155
156 # Setup id
157 if content_id!='content':
158 aid = 'top_%s' % (content_id,)
159 else:
160 aid = 'top'
161 self._content_ids.append(content_id)
162 result = []
163 # Use the content language
164 attr = self.langAttr(self.request.content_lang)
165 attr['id'] = content_id
166 result.append(self.open('div', newline=1, attr=attr))
167 result.append(self.anchordef(aid))
168 return ''.join(result)
169
170 def endContent(self):
171 """ Close page content div """
172
173 # Setup id
174 try:
175 cid = self._content_ids.pop()
176 except:
177 cid = 'content'
178 if cid!='content':
179 aid = 'bottom_%s' % (cid,)
180 else:
181 aid = 'bottom'
182
183 result = []
184 result.append(self.anchordef(aid))
185 result.append(self.close('div', newline=1))
186 return ''.join(result)
187
188 def lang(self, on, lang_name):
189 """ Insert text with specific lang and direction.
190
191 Enclose within span tag if lang_name is different from
192 the current lang
193 """
194 tag = 'span'
195 if lang_name != self.request.current_lang:
196 # Enclose text in span using lang attributes
197 if on:
198 attr = self.langAttr(lang=lang_name)
199 return self.open(tag, attr=attr)
200 return self.close(tag)
201
202 # Direction did not change, no need for span
203 return ''
204
205 def sysmsg(self, on, **kw):
206 tag = 'div'
207 if on:
208 return self.open(tag, attr={'class': 'message'})
209 return self.close(tag)
210
211 # Links ##############################################################
212
213 def pagelink(self, on, pagename='', page=None, **kw):
214 """ Link to a page.
215
216 formatter.text_python will use an optimized call with a page!=None
217 parameter. DO NOT USE THIS YOURSELF OR IT WILL BREAK.
218
219 See wikiutil.link_tag() for possible keyword parameters.
220 """
221 apply(FormatterBase.pagelink, (self, on, pagename, page), kw)
222 if page is None:
223 page = Page(self.request, pagename, formatter=self);
224
225 if self.request.user.show_nonexist_qm and on and not page.exists():
226 self.pagelink_preclosed = True
227 return (page.link_to(self.request, on=1, **kw) +
228 self.text("?") +
229 page.link_to(self.request, on=0, **kw))
230 elif not on and self.pagelink_preclosed:
231 self.pagelink_preclosed = False
232 return ""
233 else:
234 return page.link_to(self.request, on=on, **kw)
235
236 def interwikilink(self, on, interwiki='', pagename='', **kw):
237 """
238 @keyword title: override using the interwiki wikiname as title
239 """
240 if not on:
241 return '</a>'
242 wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:%s' % (interwiki, pagename))
243 wikiurl = wikiutil.mapURL(self.request, wikiurl)
244 if wikitag == 'Self': # for own wiki, do simple links
245 import urllib
246 if wikitail.find('#') > -1:
247 wikitail, kw['anchor'] = wikitail.split('#', 1)
248 wikitail = urllib.unquote(wikitail)
249 try: # XXX this is the only place where we access self.page - do we need it? Crashes silently on actions!
250 return apply(self.pagelink, (on, wikiutil.AbsPageName(self.request, self.page.page_name, wikitail)), kw)
251 except:
252 return apply(self.pagelink, (on, wikitail), kw)
253 else: # return InterWiki hyperlink
254 href = wikiutil.join_wiki(wikiurl, wikitail)
255 if wikitag_bad:
256 html_class = 'badinterwiki'
257 else:
258 html_class = 'interwiki'
259 title = kw.get('title', wikitag)
260 return self.url(1, href, title=title, unescaped=0, pretty_url=kw.get('pretty_url', 0), css=html_class)
261 # unescaped=1 was changed to 0 to make interwiki links with pages with umlauts (or other non-ascii) work
262
263 def url(self, on, url=None, css=None, **kw):
264 """
265 Keyword params:
266 title - <a> title attribute
267 attrs - just include those <a> attrs "as is"
268 """
269 if url is not None:
270 url = wikiutil.mapURL(self.request, url)
271 title = kw.get('title', None)
272 attrs = kw.get('attrs', None)
273
274 #pretty = kw.get('pretty_url', 0)
275 #if not pretty and wikiutil.isPicture(url):
276 # # XXX
277 # return '<img src="%s" alt="%s">' % (url,url)
278
279 # create link
280 if not on:
281 return '</a>'
282 str = '<a'
283
284 if css:
285 str = '%s class="%s"' % (str, css)
286 if title:
287 str = '%s title="%s"' % (str, title)
288 if attrs:
289 str = '%s %s' % (str, attrs)
290 str = '%s href="%s">' % (str, wikiutil.escape(url, 1))
291
292 return str
293
294 def anchordef(self, id):
295 return '<a id="%s"></a>\n' % (id, )
296
297 def anchorlink(self, on, name='', id = None):
298 extra = ''
299 if id:
300 extra = ' id="%s"' % id
301 return ['<a href="#%s"%s>' % (name, extra), '</a>'][not on]
302
303 # Attachments ######################################################
304
305 def attachment_link(self, url, text, **kw):
306 _ = self.request.getText
307 pagename = self.page.page_name
308 fname = wikiutil.taintfilename(url)
309 fpath = AttachFile.getFilename(self.request, pagename, fname)
310 if not os.path.exists(fpath):
311 linktext = _('Upload new attachment "%(filename)s"')
312 return wikiutil.link_tag(
313 self.request,
314 self.text('%s?action=AttachFile&rename=%s' %
315 (wikiutil.quoteWikinameURL(pagename),
316 urllib.quote_plus(fname.encode(config.charset)))),
317 linktext % {'filename': self.text(fname)})
318 target = AttachFile.getAttachUrl(pagename, url, self.request)
319 return (self.url(1, target, title="attachment:%s" % url) +
320 self.text(text) +
321 self.url(0))
322
323 def attachment_image(self, url, **kw):
324 _ = self.request.getText
325 pagename = self.page.page_name
326 fname = wikiutil.taintfilename(url)
327 fpath = AttachFile.getFilename(self.request, pagename, fname)
328 if not os.path.exists(fpath):
329 linktext = _('Upload new attachment "%(filename)s"')
330 return wikiutil.link_tag(
331 self.request,
332 self.text('%s?action=AttachFile&rename=%s' %
333 (wikiutil.quoteWikinameURL(pagename),
334 urllib.quote_plus(fname.encode(config.charset)))),
335 linktext % {'filename': self.text(fname)})
336 return self.image(
337 title="attachment:%s" % url,
338 src=AttachFile.getAttachUrl(pagename, url, self.request, addts=1))
339
340 def attachment_drawing(self, url, text, **kw):
341 _ = self.request.getText
342 pagename = self.page.page_name
343 fname = wikiutil.taintfilename(url)
344 drawing = fname
345 fname = fname + ".png"
346 url = url + ".png"
347 # fallback for old gif drawings (1.1 -> 1.2)
348 fpath = AttachFile.getFilename(self.request, pagename, fname)
349 if not os.path.exists(fpath):
350 gfname = fname[:-4] + ".gif"
351 gurl = url[:-4] + ".gif"
352 gfpath = AttachFile.getFilename(self.request, pagename, gfname)
353 if os.path.exists(gfpath):
354 fname, url, fpath = gfname, gurl, gfpath
355
356 # check whether attachment exists, possibly point to upload form
357 if not os.path.exists(fpath):
358 linktext = _('Create new drawing "%(filename)s"')
359 return wikiutil.link_tag(self.request,
360 self.text('%s?action=AttachFile&rename=%s%s' % (
361 wikiutil.quoteWikinameURL(pagename),
362 urllib.quote_plus(fname.encode(config.charset)),
363 drawing and ('&drawing=%s' % urllib.quote(drawing.encode(config.charset))) or '')),
364 linktext % {'filename': self.text(fname)})
365
366 mappath = AttachFile.getFilename(self.request, pagename, drawing + '.map')
367 edit_link = self.text('%s?action=AttachFile&rename=%s&drawing=%s' % (wikiutil.quoteWikinameURL(pagename), urllib.quote_plus(fname.encode(config.charset)), urllib.quote(drawing.encode(config.charset))))
368
369 # check for map file
370 if os.path.exists(mappath):
371 # we have a image map. inline it and add a map ref
372 # to the img tag
373 try:
374 map = open(mappath,'r').read()
375 except IOError:
376 pass
377 except OSError:
378 pass
379 else:
380 mapid = 'ImageMapOf'+drawing
381 # replace MAPNAME
382 map = map.replace('%MAPNAME%', mapid)
383 # add alt and title tags to areas
384 map = re.sub('href\s*=\s*"((?!%TWIKIDRAW%).+?)"',r'href="\1" alt="\1" title="\1"',map)
385 # add in edit links plus alt and title attributes
386 map = map.replace('%TWIKIDRAW%"', edit_link + '" alt="' + _('Edit drawing %(filename)s') % {'filename': self.text(fname)} + '" title="' + _('Edit drawing %(filename)s') % {'filename': self.text(fname)} + '"')
387 # unxml, because 4.01 concrete will not validate />
388 map = map.replace('/>','>')
389 return (map + self.image(
390 alt=drawing,
391 src=AttachFile.getAttachUrl(
392 pagename, url, self.request,
393 addts=1),
394 usemap='#'+mapid, html_class="drawing"))
395 else:
396 return wikiutil.link_tag(self.request,
397 edit_link,
398 self.image(alt=url,
399 src=AttachFile.getAttachUrl(pagename, url, self.request, addts=1), html_class="drawing"),
400 attrs='title="%s"' % (_('Edit drawing %(filename)s') % {'filename': self.text(fname)}))
401
402
403 def attachment_inlined(self, url, text, **kw):
404 _ = self.request.getText
405 pagename = self.page.page_name
406 fname = wikiutil.taintfilename(url)
407 fpath = AttachFile.getFilename(self.request, pagename, fname)
408 base, ext = os.path.splitext(url)
409 Parser = wikiutil.getParserForExtension(self.request.cfg, ext)
410 if Parser is not None:
411 try:
412 content = file(fpath, 'r').read()
413 # Try to decode text. It might return junk, but we don't
414 # have enough information with attachments.
415 content = wikiutil.decodeUnknownInput(content)
416 colorizer = Parser(content, self.request)
417 colorizer.format(self)
418 except IOError:
419 pass
420
421 return self.attachment_link(url, text)
422
423
424 # Text ##############################################################
425
426 def _text(self, text):
427 if self._in_code:
428 return wikiutil.escape(text).replace(' ', self.hardspace)
429 return wikiutil.escape(text)
430
431 # Inline ###########################################################
432
433 def strong(self, on):
434 tag = 'strong'
435 if on:
436 return self.open(tag)
437 return self.close(tag)
438
439 def emphasis(self, on):
440 tag = 'em'
441 if on:
442 return self.open(tag)
443 return self.close(tag)
444
445 def underline(self, on):
446 tag = 'span'
447 if on:
448 return self.open(tag, attr={'class': 'u'})
449 return self.close(tag)
450
451 def highlight(self, on):
452 tag = 'strong'
453 if on:
454 return self.open(tag, attr={'class': 'highlight'})
455 return self.close(tag)
456
457 def sup(self, on):
458 tag = 'sup'
459 if on:
460 return self.open(tag)
461 return self.close(tag)
462
463 def sub(self, on):
464 tag = 'sub'
465 if on:
466 return self.open(tag)
467 return self.close(tag)
468
469 def strike(self, on):
470 tag = 'strike'
471 if on:
472 return self.open(tag)
473 return self.close(tag)
474
475 def code(self, on, **kw):
476 tag = 'tt'
477 # Maybe we don't need this, because we have tt will be in inlineStack.
478 self._in_code = on
479 if on:
480 return self.open(tag)
481 return self.close(tag)
482
483 def small(self, on):
484 tag = 'small'
485 if on:
486 return self.open(tag)
487 return self.close(tag)
488
489 def big(self, on):
490 tag = 'big'
491 if on:
492 return self.open(tag)
493 return self.close(tag)
494
495
496 # Block elements ####################################################
497
498 def preformatted(self, on, attr=None):
499 FormatterBase.preformatted(self, on)
500 tag = 'pre'
501 if on:
502 return self.open(tag, newline=1, attr=attr)
503 return self.close(tag)
504
505 # Use by code area
506 _toggleLineNumbersScript = """
507 <script type="text/JavaScript">
508 function isnumbered(obj) {
509 return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber';
510 }
511 function nformat(num,chrs,add) {
512 var nlen = Math.max(0,chrs-(''+num).length), res = '';
513 while (nlen>0) { res += ' '; nlen-- }
514 return res+num+add;
515 }
516 function addnumber(did, nstart, nstep) {
517 var c = document.getElementById(did), l = c.firstChild, n = 1;
518 if (!isnumbered(c))
519 if (typeof nstart == 'undefined') nstart = 1;
520 if (typeof nstep == 'undefined') nstep = 1;
521 n = nstart;
522 while (l != null) {
523 if (l.tagName == 'SPAN') {
524 var s = document.createElement('SPAN');
525 s.className = 'LineNumber'
526 s.appendChild(document.createTextNode(nformat(n,4,' ')));
527 n += nstep;
528 if (l.childNodes.length)
529 l.insertBefore(s, l.firstChild)
530 else
531 l.appendChild(s)
532 }
533 l = l.nextSibling;
534 }
535 return false;
536 }
537 function remnumber(did) {
538 var c = document.getElementById(did), l = c.firstChild;
539 if (isnumbered(c))
540 while (l != null) {
541 if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild);
542 l = l.nextSibling;
543 }
544 return false;
545 }
546 function togglenumber(did, nstart, nstep) {
547 var c = document.getElementById(did);
548 if (isnumbered(c)) {
549 remnumber(did);
550 } else {
551 addnumber(did,nstart,nstep);
552 }
553 return false;
554 }
555 </script>
556 """
557
558 def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1):
559 res = []
560 ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num))
561 if on:
562 # Open a code area
563 self._in_code_area = 1
564 self._in_code_line = 0
565 self._code_area_state = [ci, show, start, step, start]
566
567 # Open the code div - using left to right always!
568 attr = {'class': 'codearea', 'lang': 'en', 'dir': 'ltr'}
569 res.append(self.open('div', attr=attr))
570
571 # Add the script only in the first code area on the page
572 if self._code_area_js == 0 and self._code_area_state[1] >= 0:
573 res.append(self._toggleLineNumbersScript)
574 self._code_area_js = 1
575
576 # Add line number link, but only for JavaScript enabled browsers.
577 if self._code_area_state[1] >= 0:
578 toggleLineNumbersLink = r'''
579 <script type="text/javascript">
580 document.write('<a href="#" onClick="return togglenumber(\'%s\', %d, %d);" \
581 class="codenumbers">Toggle line numbers<\/a>');
582 </script>
583 ''' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
584 res.append(toggleLineNumbersLink)
585
586 # Open pre - using left to right always!
587 attr = {'id': self._code_area_state[0], 'lang': 'en', 'dir': 'ltr'}
588 res.append(self.open('pre', newline=True, attr=attr))
589 else:
590 # Close code area
591 res = []
592 if self._in_code_line:
593 res.append(self.code_line(0))
594 res.append(self.close('pre'))
595 res.append(self.close('div'))
596
597 # Update state
598 self._in_code_area = 0
599 self._code_area_num += 1
600
601 return ''.join(res)
602
603 def code_line(self, on):
604 res = ''
605 if not on or (on and self._in_code_line):
606 res += '</span>\n'
607 if on:
608 res += '<span class="line">'
609 if self._code_area_state[1] > 0:
610 res += '<span class="LineNumber">%4d </span>' % (self._code_area_state[4], )
611 self._code_area_state[4] += self._code_area_state[3]
612 self._in_code_line = on != 0
613 return res
614
615 def code_token(self, on, tok_type):
616 return ['<span class="%s">' % tok_type, '</span>'][not on]
617
618 # Paragraphs, Lines, Rules ###########################################
619
620 def linebreak(self, preformatted=1):
621 if self._in_code_area:
622 preformatted = 1
623 return ['\n', '<br>\n'][not preformatted]
624
625 def paragraph(self, on):
626 if self._terse:
627 return ''
628 FormatterBase.paragraph(self, on)
629 if self._in_li:
630 self._in_li = self._in_li + 1
631 tag = 'p'
632 if on:
633 return self.open(tag)
634 return self.close(tag)
635
636 def rule(self, size=None):
637 if size:
638 # Add hr class: hr1 - hr6
639 return self.open('hr', newline=1, attr={'class': 'hr%d' % size})
640 return self.open('hr', newline=1)
641
642 def icon(self, type):
643 return self.request.theme.make_icon(type)
644
645 def smiley(self, text):
646 w, h, b, img = config.smileys[text.strip()]
647 href = img
648 if not href.startswith('/'):
649 href = self.request.theme.img_url(img)
650 return self.image(src=href, alt=text, width=str(w), height=str(h))
651
652 # Lists ##############################################################
653
654 def number_list(self, on, type=None, start=None):
655 tag = 'ol'
656 if on:
657 attr = {}
658 if type is not None:
659 attr['type'] = type
660 if start is not None:
661 attr['start'] = start
662 return self.open(tag, newline=1, attr=attr)
663 return self.close(tag)
664
665 def bullet_list(self, on):
666 tag = 'ul'
667 if on:
668 return self.open(tag, newline=1)
669 return self.close(tag)
670
671 def listitem(self, on, **kw):
672 """ List item inherit its lang from the list. """
673 tag = 'li'
674 self._in_li = on != 0
675 if on:
676 attr = {}
677 css_class = kw.get('css_class', None)
678 if css_class:
679 attr['class'] = css_class
680 style = kw.get('style', None)
681 if style:
682 attr['style'] = style
683 return self.open(tag, attr=attr)
684 return self.close(tag)
685
686 def definition_list(self, on):
687 tag = 'dl'
688 if on:
689 return self.open(tag, newline=1)
690 return self.close(tag)
691
692 def definition_term(self, on):
693 tag = 'dt'
694 if on:
695 return self.open(tag)
696 return self.close(tag)
697
698 def definition_desc(self, on):
699 tag = 'dd'
700 if on:
701 return self.open(tag)
702 return self.close(tag)
703
704 def heading(self, on, depth, id = None, **kw):
705 # remember depth of first heading, and adapt counting depth accordingly
706 if not self._base_depth:
707 self._base_depth = depth
708
709 count_depth = max(depth - (self._base_depth - 1), 1)
710
711 # check numbering, possibly changing the default
712 if self._show_section_numbers is None:
713 self._show_section_numbers = self.cfg.show_section_numbers
714 numbering = self.request.getPragma('section-numbers', '').lower()
715 if numbering in ['0', 'off']:
716 self._show_section_numbers = 0
717 elif numbering in ['1', 'on']:
718 self._show_section_numbers = 1
719 elif numbering in ['2', '3', '4', '5', '6']:
720 # explicit base level for section number display
721 self._show_section_numbers = int(numbering)
722
723 heading_depth = depth + 1
724
725 # closing tag, with empty line after, to make source more readable
726 if not on:
727 return self.close('h%d' % heading_depth) + '\n'
728
729 # create section number
730 number = ''
731 if self._show_section_numbers:
732 # count headings on all levels
733 self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth]
734 while len(self.request._fmt_hd_counters) < count_depth:
735 self.request._fmt_hd_counters.append(0)
736 self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1
737 number = '.'.join(map(str, self.request._fmt_hd_counters[self._show_section_numbers-1:]))
738 if number: number += ". "
739
740 attr = {}
741 if id:
742 attr['id'] = id
743 # Add space before heading, easier to check source code
744 result = '\n' + self.open('h%d' % heading_depth, attr=attr)
745
746 # TODO: convert this to readable code
747 if self.request.user.show_topbottom:
748 # TODO change top/bottom refs to content-specific top/bottom refs?
749 result = ("%s%s%s%s%s%s%s%s" %
750 (result,
751 kw.get('icons',''),
752 self.url(1, "#bottom", unescaped=1),
753 self.icon('bottom'),
754 self.url(0),
755 self.url(1, "#top", unescaped=1),
756 self.icon('top'),
757 self.url(0)))
758 return "%s%s%s" % (result, kw.get('icons',''), number)
759
760
761 # Tables #############################################################
762
763 _allowed_table_attrs = {
764 'table': ['class', 'id', 'style'],
765 'row': ['class', 'id', 'style'],
766 '': ['colspan', 'rowspan', 'class', 'id', 'style'],
767 }
768
769 def _checkTableAttr(self, attrs, prefix):
770 """ Check table attributes
771
772 Convert from wikitable attributes to html 4 attributes.
773
774 @param attrs: attribute dict
775 @param prefix: used in wiki table attributes
776 @rtyp: dict
777 @return: valid table attributes
778 """
779 if not attrs:
780 return {}
781
782 result = {}
783 s = "" # we collect synthesized style in s
784 for key, val in attrs.items():
785 # Ignore keys that don't start with prefix
786 if prefix and key[:len(prefix)] != prefix:
787 continue
788 key = key[len(prefix):]
789 val = val.strip('"')
790 # remove invalid attrs from dict and synthesize style
791 if key == 'width':
792 s += "width: %s;" % val
793 elif key == 'height':
794 s += "height: %s;" % val
795 elif key == 'bgcolor':
796 s += "background-color: %s;" % val
797 elif key == 'align':
798 s += "text-align: %s;" % val
799 elif key == 'valign':
800 s += "vertical-align: %s;" % val
801 # Ignore unknown keys
802 if key not in self._allowed_table_attrs[prefix]:
803 continue
804 result[key] = val
805 if s:
806 if result.has_key('style'):
807 result['style'] += s
808 else:
809 result['style'] = s
810 return result
811
812
813 def table(self, on, attrs=None):
814 """ Create table
815
816 @param on: start table
817 @param attrs: table attributes
818 @rtype: string
819 @return start or end tag of a table
820 """
821 result = []
822 if on:
823 # Open div to get correct alignment with table width smaller
824 # than 100%
825 result.append(self.open('div', newline=1))
826
827 # Open table
828 if not attrs:
829 attrs = {}
830 else:
831 attrs = self._checkTableAttr(attrs, 'table')
832 result.append(self.open('table', newline=1, attr=attrs))
833 else:
834 # Close table then div
835 result.append(self.close('table'))
836 result.append(self.close('div'))
837
838 return ''.join(result)
839
840 def table_row(self, on, attrs=None):
841 tag = 'tr'
842 if on:
843 if not attrs:
844 attrs = {}
845 else:
846 attrs = self._checkTableAttr(attrs, 'row')
847 return self.open(tag, newline=1, attr=attrs)
848 return self.close(tag)
849
850 def table_cell(self, on, attrs=None):
851 tag = 'td'
852 if on:
853 if not attrs:
854 attrs = {}
855 else:
856 attrs = self._checkTableAttr(attrs, '')
857 return self.open(tag, newline=1, attr=attrs)
858 return self.close(tag)
859
860 def escapedText(self, text):
861 return wikiutil.escape(text)
862
863 def rawHTML(self, markup):
864 return markup
865