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