This is what changed between 1.2-latest and 1.3-latest:
1 --- moin--main--1.2--patch-364/MoinMoin/parser/wiki.py 2005-01-16 16:07:50.096079352 +0100
2 +++ moin--main--1.3--patch-546/MoinMoin/parser/wiki.py 2005-01-16 16:08:19.182657520 +0100
3 @@ -6,17 +6,11 @@
4 @license: GNU GPL, see COPYING for details.
5 """
6
7 -# Imports
8 import os, re
9 from MoinMoin import config, wikimacro, wikiutil
10 from MoinMoin.Page import Page
11 from MoinMoin.util import web
12
13 -
14 -#############################################################################
15 -### MoinMoin Wiki Markup Parser
16 -#############################################################################
17 -
18 class Parser:
19 """
20 Object that turns Wiki markup into HTML.
21 @@ -34,29 +28,29 @@
22 # some common strings
23 PARENT_PREFIX = wikiutil.PARENT_PREFIX
24 attachment_schemas = ["attachment", "inline", "drawing"]
25 - punct_pattern = re.escape('''"\'}]|:,.)?!''')
26 - url_pattern = ('http|https|ftp|nntp|news|mailto|telnet|wiki|file|' +
27 - '|'.join(attachment_schemas) +
28 - (config.url_schemas and '|' + '|'.join(config.url_schemas) or ''))
29 + punct_pattern = re.escape(u'''"\'}]|:,.)?!''')
30 + url_pattern = (u'http|https|ftp|nntp|news|mailto|telnet|wiki|file|' +
31 + u'|'.join(attachment_schemas) +
32 + (config.url_schemas and u'|' + u'|'.join(config.url_schemas) or ''))
33
34 # some common rules
35 - word_rule = r'(?:(?<![%(l)s])|^)%(parent)s(?:%(subpages)s(?:[%(u)s][%(l)s]+){2,})+(?![%(u)s%(l)s]+)' % {
36 - 'u': config.upperletters,
37 - 'l': config.lowerletters,
38 + word_rule = ur'(?:(?<![%(l)s])|^)%(parent)s(?:%(subpages)s(?:[%(u)s][%(l)s]+){2,})+(?![%(u)s%(l)s]+)' % {
39 + 'u': config.chars_upper,
40 + 'l': config.chars_lower,
41 'subpages': config.allow_subpages and (wikiutil.CHILD_PREFIX + '?') or '',
42 - 'parent': config.allow_subpages and (r'(?:%s)?' % re.escape(PARENT_PREFIX)) or '',
43 + 'parent': config.allow_subpages and (ur'(?:%s)?' % re.escape(PARENT_PREFIX)) or '',
44 }
45 - url_rule = r'%(url_guard)s(%(url)s)\:([^\s\<%(punct)s]|([%(punct)s][^\s\<%(punct)s]))+' % {
46 - 'url_guard': '(^|(?<!\w))',
47 + url_rule = ur'%(url_guard)s(%(url)s)\:([^\s\<%(punct)s]|([%(punct)s][^\s\<%(punct)s]))+' % {
48 + 'url_guard': u'(^|(?<!\w))',
49 'url': url_pattern,
50 'punct': punct_pattern,
51 }
52
53 - ol_rule = r"^\s+(?:[0-9]+|[aAiI])\.(?:#\d+)?\s"
54 - dl_rule = r"^\s+.*?::\s"
55 + ol_rule = ur"^\s+(?:[0-9]+|[aAiI])\.(?:#\d+)?\s"
56 + dl_rule = ur"^\s+.*?::\s"
57
58 # the big, fat, ugly one ;)
59 - formatting_rules = r"""(?:(?P<emph_ibb>'''''(?=[^']+'''))
60 + formatting_rules = ur"""(?:(?P<emph_ibb>'''''(?=[^']+'''))
61 (?P<emph_ibi>'''''(?=[^']+''))
62 (?P<emph_ib_or_bi>'{5}(?=[^']))
63 (?P<emph>'{2,3})
64 @@ -66,37 +60,44 @@
65 (?P<tt>\{\{\{.*?\}\}\})
66 (?P<processor>(\{\{\{(#!.*|\s*$)))
67 (?P<pre>(\{\{\{ ?|\}\}\}))
68 +(?P<small>(\~- ?|-\~))
69 +(?P<big>(\~\+ ?|\+\~))
70 (?P<rule>-{4,})
71 (?P<comment>^\#\#.*$)
72 -(?P<macro>\[\[(%(macronames)s)(?:\(.*?\))?\]\]))
73 -(?P<li>^\s+\*)
74 +(?P<macro>\[\[(%%(macronames)s)(?:\(.*?\))?\]\]))
75 (?P<ol>%(ol_rule)s)
76 (?P<dl>%(dl_rule)s)
77 +(?P<li>^\s+\*?)
78 (?P<tableZ>\|\| $)
79 -(?P<table>(?:\|\|)+(?:<[^>]*?>)?(?=.))
80 +(?P<table>(?:\|\|)+(?:<[^>]*?>)?(?!\|? $))
81 (?P<heading>^\s*(?P<hmarker>=+)\s.*\s(?P=hmarker) $)
82 (?P<interwiki>[A-Z][a-zA-Z]+\:[^\s'\"\:\<\|]([^\s%(punct)s]|([%(punct)s][^\s%(punct)s]))+)
83 (?P<word>%(word_rule)s)
84 (?P<url_bracket>\[((%(url)s)\:|#|\:)[^\s\]]+(\s[^\]]+)?\])
85 (?P<url>%(url_rule)s)
86 -(?P<email>[-\w._+]+\@[\w-]+\.[\w.-]+)
87 +(?P<email>[-\w._+]+\@[\w-]+(\.[\w-]+)+)
88 (?P<smiley>(?<=\s)(%(smiley)s)(?=\s))
89 (?P<smileyA>^(%(smiley)s)(?=\s))
90 (?P<ent>[<>&])""" % {
91 'url': url_pattern,
92 'punct': punct_pattern,
93 - 'macronames': '|'.join(wikimacro.names),
94 'ol_rule': ol_rule,
95 'dl_rule': dl_rule,
96 'url_rule': url_rule,
97 'word_rule': word_rule,
98 - 'smiley': '|'.join(map(re.escape, config.smileys.keys()))}
99 + 'smiley': u'|'.join(map(re.escape, config.smileys.keys()))}
100 +
101 + # Don't start p before these
102 + no_new_p_before = ("heading rule table tableZ tr td ul ol dl dt dd li "
103 + "processor macro pre")
104 + no_new_p_before = dict(zip(no_new_p_before.split(), [1] * len(no_new_p_before)))
105
106 def __init__(self, raw, request, **kw):
107 self.raw = raw
108 self.request = request
109 self.form = request.form
110 self._ = request.getText
111 + self.cfg = request.cfg
112
113 self.macro = None
114
115 @@ -108,22 +109,31 @@
116 self.in_dd = 0
117 self.in_pre = 0
118 self.in_table = 0
119 + self.is_big = False
120 + self.is_small = False
121 self.inhibit_p = 0 # if set, do not auto-create a <p>aragraph
122 - self.titles = {}
123 + self.titles = request._page_headings
124
125 # holds the nesting level (in chars) of open lists
126 self.list_indents = []
127 self.list_types = []
128 +
129 + self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(wikimacro.getNames(self.cfg))}
130
131 def _close_item(self, result):
132 #result.append("<!-- close item begin -->\n")
133 - if self.formatter.in_p:
134 - result.append(self.formatter.paragraph(0))
135 + if self.in_table:
136 + result.append(self.formatter.table(0))
137 + self.in_table = 0
138 if self.in_li:
139 self.in_li = 0
140 + if self.formatter.in_p:
141 + result.append(self.formatter.paragraph(0))
142 result.append(self.formatter.listitem(0))
143 if self.in_dd:
144 self.in_dd = 0
145 + if self.formatter.in_p:
146 + result.append(self.formatter.paragraph(0))
147 result.append(self.formatter.definition_desc(0))
148 #result.append("<!-- close item end -->\n")
149
150 @@ -147,12 +157,11 @@
151 elif config.allow_subpages and url[0] == wikiutil.CHILD_PREFIX:
152 # fancy link to subpage [wiki:/SubPage text]
153 return self._word_repl(url, text)
154 - elif Page(url).exists():
155 + elif Page(self.request, url).exists():
156 # fancy link to local page [wiki:LocalPage text]
157 return self._word_repl(url, text)
158
159 wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, url)
160 - wikiurl = wikiutil.mapURL(wikiurl)
161 href = wikiutil.join_wiki(wikiurl, wikitail)
162
163 # check for image URL, and possibly return IMG tag
164 @@ -163,19 +172,9 @@
165 if wikitag is None:
166 return self._word_repl(wikitail)
167
168 - # return InterWiki hyperlink
169 - if wikitag_bad:
170 - html_class = 'badinterwiki'
171 - else:
172 - html_class = 'interwiki'
173 - text = self.highlight_text(text) # also cgi.escapes if necessary
174 -
175 - icon = ''
176 - if self.request.user.show_fancy_links:
177 - icon = self.request.theme.make_icon('interwiki', {'wikitag': wikitag})
178 - return self.formatter.url(href, icon + text,
179 - title=wikitag, unescaped=1, pretty_url=kw.get('pretty_url', 0), css = html_class)
180 -
181 + return (self.formatter.interwikilink(1, wikitag, wikitail) +
182 + self.formatter.text(text) +
183 + self.formatter.interwikilink(0))
184
185 def attachment(self, url_and_text, **kw):
186 """ This gets called on attachment URLs.
187 @@ -209,15 +208,15 @@
188 fname = fname + ".png"
189 url = url + ".png"
190 # fallback for old gif drawings (1.1 -> 1.2)
191 - fpath = os.path.join(AttachFile.getAttachDir(pagename), fname)
192 + fpath = AttachFile.getFilename(self.request, pagename, fname)
193 if not os.path.exists(fpath):
194 gfname = fname[:-4] + ".gif"
195 gurl = url[:-4] + ".gif"
196 - gfpath = os.path.join(AttachFile.getAttachDir(pagename), gfname)
197 + gfpath = AttachFile.getFilename(self.request, pagename, gfname)
198 if os.path.exists(gfpath):
199 fname, url, fpath = gfname, gurl, gfpath
200 else:
201 - fpath = os.path.join(AttachFile.getAttachDir(pagename), fname)
202 + fpath = AttachFile.getFilename(self.request, pagename, fname)
203
204 # check whether attachment exists, possibly point to upload form
205 if not os.path.exists(fpath):
206 @@ -226,19 +225,19 @@
207 else:
208 linktext = _('Upload new attachment "%(filename)s"')
209 return wikiutil.link_tag(self.request,
210 - '%s?action=AttachFile&rename=%s%s' % (
211 - wikiutil.quoteWikiname(pagename),
212 - urllib.quote_plus(fname),
213 - drawing and ('&drawing=%s' % urllib.quote(drawing)) or ''),
214 - linktext % {'filename': fname})
215 + self.formatter.text('%s?action=AttachFile&rename=%s%s' % (
216 + wikiutil.quoteWikinameURL(pagename),
217 + urllib.quote_plus(fname.encode(config.charset)),
218 + drawing and ('&drawing=%s' % urllib.quote(drawing.encode(config.charset))) or '')),
219 + linktext % {'filename': self.formatter.text(fname)})
220
221 # check for image URL, and possibly return IMG tag
222 # (images are always inlined, just like for other URLs)
223 if not kw.get('pretty_url', 0) and wikiutil.isPicture(url):
224 if drawing:
225 # check for map file
226 - mappath = os.path.join(AttachFile.getAttachDir(pagename), drawing + '.map')
227 - edit_link = '%s?action=AttachFile&rename=%s&drawing=%s' % (wikiutil.quoteWikiname(pagename), urllib.quote_plus(fname), urllib.quote(drawing))
228 + mappath = AttachFile.getFilename(self.request, pagename, drawing + '.map')
229 + edit_link = self.formatter.text('%s?action=AttachFile&rename=%s&drawing=%s' % (wikiutil.quoteWikinameURL(pagename), urllib.quote_plus(fname.encode(config.charset)), urllib.quote(drawing.encode(config.charset))))
230 if os.path.exists(mappath):
231 # we have a image map. inline it and add a map ref
232 # to the img tag
233 @@ -255,7 +254,7 @@
234 # add alt and title tags to areas
235 map = re.sub('href\s*=\s*"((?!%TWIKIDRAW%).+?)"',r'href="\1" alt="\1" title="\1"',map)
236 # add in edit links plus alt and title attributes
237 - map = map.replace('%TWIKIDRAW%"', edit_link + '" alt="' + _('Edit drawing %(filename)s') % {'filename': fname} + '" title="' + _('Edit drawing %(filename)s') % {'filename': fname} + '"')
238 + map = map.replace('%TWIKIDRAW%"', edit_link + '" alt="' + _('Edit drawing %(filename)s') % {'filename': self.formatter.text(fname)} + '" title="' + _('Edit drawing %(filename)s') % {'filename': self.formatter.text(fname)} + '"')
239 # unxml, because 4.01 concrete will not validate />
240 map = map.replace('/>','>')
241 return map + self.formatter.image(alt=drawing,
242 @@ -265,36 +264,51 @@
243 edit_link,
244 self.formatter.image(alt=url,
245 src=AttachFile.getAttachUrl(pagename, url, self.request, addts=1), html_class="drawing"),
246 - attrs='title="%s"' % (_('Edit drawing %(filename)s') % {'filename': fname}))
247 + attrs='title="%s"' % (_('Edit drawing %(filename)s') % {'filename': self.formatter.text(fname)}))
248 else:
249 return self.formatter.image(alt=url,
250 src=AttachFile.getAttachUrl(pagename, url, self.request, addts=1))
251
252 - # try to inline the attachment (we only accept a list
253 - # of known extensions)
254 + # try to inline the attachment (parser know what they
255 + # can handle)
256 base, ext = os.path.splitext(url)
257 - if inline and ext in ['.py']:
258 - if ext == '.py':
259 - import cStringIO
260 - from MoinMoin.parser import python
261 -
262 - buff = cStringIO.StringIO()
263 - colorizer = python.Parser(open(fpath, 'r').read(), self.request, out = buff)
264 + if inline:
265 + Parser = wikiutil.getParserForExtension(self.cfg, ext)
266 + if Parser is not None:
267 + content = file(fpath, 'r').read()
268 + # Try to decode text. It might return junk, but we don't
269 + # have enough information with attachments.
270 + content = wikiutil.decodeUnknownInput(content)
271 + colorizer = Parser(content, self.request)
272 colorizer.format(self.formatter)
273 - return self.formatter.preformatted(1) + \
274 - self.formatter.rawHTML(buff.getvalue()) + \
275 - self.formatter.preformatted(0)
276 -
277 - return self.formatter.url(
278 - AttachFile.getAttachUrl(pagename, url, self.request),
279 - text, pretty_url=kw.get('pretty_url', 0))
280
281 + url = AttachFile.getAttachUrl(pagename, url, self.request)
282 +
283 + if kw.get('pretty_url', 0) and wikiutil.isPicture(url):
284 + return self.formatter.image(src=url)
285 + else:
286 + return (self.formatter.url(1, url) +
287 + self.formatter.text(text) +
288 + self.formatter.url(0))
289
290 def _u_repl(self, word):
291 """Handle underline."""
292 self.is_u = not self.is_u
293 return self.formatter.underline(self.is_u)
294
295 + def _small_repl(self, word):
296 + """Handle small."""
297 + if word.strip() == '~-' and self.is_small: return word
298 + if word.strip() == '-~' and not self.is_small: return word
299 + self.is_small = not self.is_small
300 + return self.formatter.small(self.is_small)
301 +
302 + def _big_repl(self, word):
303 + """Handle big."""
304 + if word.strip() == '~+' and self.is_big: return word
305 + if word.strip() == '+~' and not self.is_big: return word
306 + self.is_big = not self.is_big
307 + return self.formatter.big(self.is_big)
308
309 def _emph_repl(self, word):
310 """Handle emphasis, i.e. '' and '''."""
311 @@ -337,25 +351,27 @@
312 def _sup_repl(self, word):
313 """Handle superscript."""
314 return self.formatter.sup(1) + \
315 - self.highlight_text(word[1:-1]) + \
316 + self.formatter.text(word[1:-1]) + \
317 self.formatter.sup(0)
318
319
320 def _sub_repl(self, word):
321 """Handle subscript."""
322 return self.formatter.sub(1) + \
323 - self.highlight_text(word[2:-2]) + \
324 + self.formatter.text(word[2:-2]) + \
325 self.formatter.sub(0)
326
327
328 def _rule_repl(self, word):
329 """Handle sequences of dashes."""
330 - self.inhibit_p = 1
331 - result = self._undent()
332 + ##self.inhibit_p = 1
333 + result = self._undent() + self._closeP()
334 if len(word) <= 4:
335 result = result + self.formatter.rule()
336 else:
337 - result = result + self.formatter.rule(min(len(word), 10) - 2)
338 + # Create variable rule size 1 - 6. Actual size defined in css.
339 + size = min(len(word), 10) - 4
340 + result = result + self.formatter.rule(size)
341 return result
342
343
344 @@ -372,20 +388,17 @@
345 if not text:
346 # if a simple, self-referencing link, emit it as plain text
347 if word == self.formatter.page.page_name:
348 - return word
349 + return self.formatter.text(word)
350 text = word
351 if config.allow_subpages and word.startswith(wikiutil.CHILD_PREFIX):
352 word = self.formatter.page.page_name + word
353 - text = self.highlight_text(text)
354 - if word == text:
355 - return self.formatter.pagelink(word)
356 - else:
357 - return self.formatter.pagelink(word, text)
358 + return (self.formatter.pagelink(1, word) +
359 + self.formatter.text(text) +
360 + self.formatter.pagelink(0, word))
361
362 def _notword_repl(self, word):
363 """Handle !NotWikiNames."""
364 - return self.highlight_text(word[1:])
365 -
366 + return self.formatter.text(word[1:])
367
368 def _interwiki_repl(self, word):
369 """Handle InterWiki links."""
370 @@ -400,7 +413,15 @@
371 if scheme in self.attachment_schemas:
372 return self.attachment([word])
373
374 - return self.formatter.url(word, text=self.highlight_text(word))
375 + if wikiutil.isPicture(word):
376 + # Get image name http://here.com/dir/image.gif -> image
377 + name = word.split('/')[-1]
378 + name = ''.join(name.split('.')[:-1])
379 + return self.formatter.image(src=word, alt=name)
380 + else:
381 + return (self.formatter.url(1, word, type='www') +
382 + self.formatter.text(word) +
383 + self.formatter.url(0))
384
385
386 def _wikiname_bracket_repl(self, word):
387 @@ -427,7 +448,9 @@
388
389 if words[0][0] == '#':
390 # anchor link
391 - return self.formatter.url(words[0], self.highlight_text(words[1]))
392 + return (self.formatter.url(1, words[0]) +
393 + self.formatter.text(words[1]) +
394 + self.formatter.url(0))
395
396 scheme = words[0].split(":", 1)[0]
397 if scheme == "wiki": return self.interwiki(words, pretty_url=1)
398 @@ -435,16 +458,21 @@
399 return self.attachment(words, pretty_url=1)
400
401 if wikiutil.isPicture(words[1]) and re.match(self.url_rule, words[1]):
402 - text = self.formatter.image(title=words[0], alt=words[0], src=words[1])
403 + return (self.formatter.url(1, words[0], 'external', unescaped=1) +
404 + self.formatter.image(title=words[0], alt=words[0], src=words[1]) +
405 + self.formatter.url(0))
406 else:
407 - text = web.getLinkIcon(self.request, self.formatter, scheme)
408 - text += self.highlight_text(words[1])
409 - return self.formatter.url(words[0], text, 'external', pretty_url=1, unescaped=1)
410 + return (self.formatter.url(1, words[0], 'external',
411 + type='www', unescaped=1) +
412 + self.formatter.text(words[1]) +
413 + self.formatter.url(0))
414
415
416 def _email_repl(self, word):
417 """Handle email addresses (without a leading mailto:)."""
418 - return self.formatter.url("mailto:" + word, self.highlight_text(word))
419 + return (self.formatter.url(1, "mailto:" + word, type='mailto') +
420 + self.formatter.text(word) +
421 + self.formatter.url(0))
422
423
424 def _ent_repl(self, word):
425 @@ -463,15 +491,22 @@
426 def _li_repl(self, match):
427 """Handle bullet lists."""
428 result = []
429 + indented_only = (match == (" " * len(match)))
430 + if indented_only and self.in_li: return ''
431 +
432 self._close_item(result)
433 - self.inhibit_p = 1
434 + #self.inhibit_p = 1
435 self.in_li = 1
436 css_class = ''
437 if self.line_was_empty and not self.first_list_item:
438 css_class = 'gap'
439 - result.append(" "*4*self._indent_level())
440 - result.append(self.formatter.listitem(1, css_class=css_class))
441 - result.append(self.formatter.paragraph(1))
442 + if indented_only:
443 + result.append(self.formatter.listitem(1, css_class=css_class,
444 + style="list-style-type:none"))
445 + else:
446 + result.append(self.formatter.listitem(1, css_class=css_class))
447 + # Suspected p!
448 + ## result.append(self.formatter.paragraph(1))
449 return ''.join(result)
450
451
452 @@ -484,15 +519,15 @@
453 """Handle definition lists."""
454 result = []
455 self._close_item(result)
456 - self.inhibit_p = 1
457 + #self.inhibit_p = 1
458 self.in_dd = 1
459 result.extend([
460 - " "*4*self._indent_level(),
461 self.formatter.definition_term(1),
462 - self.formatter.text(match[:-3]),
463 + self.formatter.text(match[1:-3]),
464 self.formatter.definition_term(0),
465 self.formatter.definition_desc(1),
466 - self.formatter.paragraph(1)
467 + ## CHANGE: no automatic paragraph
468 + ##self.formatter.paragraph(1)
469 ])
470 return ''.join(result)
471
472 @@ -507,73 +542,72 @@
473 open = [] # don't make one out of these two statements!
474 close = []
475
476 - # Close open paragraphs and list items
477 - if self._indent_level() != new_level:
478 - self._close_item(close)
479 - else:
480 - if not self.line_was_empty:
481 - self.inhibit_p = 1
482 +
483 + if self._indent_level() != new_level and self.in_table:
484 + close.append(self.formatter.table(0))
485 + self.in_table = 0
486 + # #self._close_item(close)
487 + #else:
488 + # if not self.line_was_empty:
489 + # self.inhibit_p = 1
490
491 # Close lists while char-wise indent is greater than the current one
492 - while self._indent_level() > new_level:
493 - indentstr = " "*4*self._indent_level()
494 + while ((self._indent_level() > new_level) or
495 + ( new_level and
496 + (self._indent_level() == new_level) and
497 + (self.list_types[-1]) != list_type)):
498 + self._close_item(close)
499 if self.list_types[-1] == 'ol':
500 tag = self.formatter.number_list(0)
501 elif self.list_types[-1] == 'dl':
502 tag = self.formatter.definition_list(0)
503 else:
504 tag = self.formatter.bullet_list(0)
505 - close.append("\n%s%s\n" % (indentstr, tag))
506 + close.append(tag)
507
508 del(self.list_indents[-1])
509 del(self.list_types[-1])
510
511 - if new_level:
512 - self.inhibit_p = 1
513 - else:
514 - self.inhibit_p = 0
515 + #if new_level:
516 + # self.inhibit_p = 1
517 + #else:
518 + # self.inhibit_p = 0
519 +
520 + if self.list_types: # we are still in a list
521 + if self.list_types[-1] == 'dl':
522 + self.in_dd = 1
523 + else:
524 + self.in_li = 1
525
526 - # XXX This would give valid, but silly looking html.
527 - # the right way is that inner list has to be CONTAINED in outer li -
528 - # but in the one before, not a new one, like this code does:
529 - #if self.list_types: # we are still in a list, bracket with li /li
530 - # if self.list_types[-1] in ['ol', 'ul']:
531 - # open.append(" "*4*new_level)
532 - # open.append(self.formatter.listitem(0))
533 - # elif self.list_types[-1] == 'dl':
534 - # open.append(" "*4*new_level)
535 - # open.append(self.formatter.definition_desc(0))
536 -
537 # Open new list, if necessary
538 if self._indent_level() < new_level:
539 - # XXX see comment 10 lines above
540 - #if self.list_types: # we already are in a list, bracket with li /li
541 - # if self.list_types[-1] in ['ol', 'ul']:
542 - # open.append(" "*4*new_level)
543 - # open.append(self.formatter.listitem(1))
544 - # elif self.list_types[-1] == 'dl':
545 - # open.append(" "*4*new_level)
546 - # open.append(self.formatter.definition_desc(1))
547
548 self.list_indents.append(new_level)
549 self.list_types.append(list_type)
550 +
551 + if self.formatter.in_p:
552 + close.append(self.formatter.paragraph(0))
553
554 - indentstr = " "*4*new_level
555 if list_type == 'ol':
556 tag = self.formatter.number_list(1, numtype, numstart)
557 elif list_type == 'dl':
558 tag = self.formatter.definition_list(1)
559 else:
560 tag = self.formatter.bullet_list(1)
561 - open.append("\n%s%s\n" % (indentstr, tag))
562 + open.append(tag)
563
564 self.first_list_item = 1
565 - self.inhibit_p = 1
566 -
567 + ## Maybe this prevent p creation in lists?
568 + ##self.inhibit_p = 1
569 + self.in_li = 0
570 + self.in_dd = 0
571 # If list level changes, close an open table
572 if self.in_table and (open or close):
573 close[0:0] = [self.formatter.table(0)]
574 self.in_table = 0
575 +
576 + ## Maybe this prevent p creation in lists?
577 + ##self.inhibit_p = bool(self.list_types)
578
579 return ''.join(close) + ''.join(open)
580
581 @@ -599,7 +633,7 @@
582 def _tt_repl(self, word):
583 """Handle inline code."""
584 return self.formatter.code(1) + \
585 - self.highlight_text(word[3:-3]) + \
586 + self.formatter.text(word[3:-3]) + \
587 self.formatter.code(0)
588
589
590 @@ -607,7 +641,7 @@
591 """Handle backticked inline code."""
592 if len(word) == 2: return ""
593 return self.formatter.code(1) + \
594 - self.highlight_text(word[1:-1]) + \
595 + self.formatter.text(word[1:-1]) + \
596 self.formatter.code(0)
597
598
599 @@ -689,22 +723,32 @@
600 def _tableZ_repl(self, word):
601 """Handle table row end."""
602 if self.in_table:
603 - return self.formatter.table_cell(0) + self.formatter.table_row(0)
604 + result = ''
605 + # REMOVED: check for self.in_li, p should always close
606 + if self.formatter.in_p:
607 + result = self.formatter.paragraph(0)
608 + result += self.formatter.table_cell(0) + self.formatter.table_row(0)
609 + return result
610 else:
611 return word
612
613 def _table_repl(self, word):
614 """Handle table cell separator."""
615 if self.in_table:
616 + result = []
617 # check for attributes
618 attrs, attrerr = self._getTableAttrs(word)
619
620 # start the table row?
621 if self.table_rowstart:
622 self.table_rowstart = 0
623 - leader = self.formatter.table_row(1, attrs)
624 + result.append(self.formatter.table_row(1, attrs))
625 else:
626 - leader = self.formatter.table_cell(0)
627 + # Close table cell, first closing open p
628 + # REMOVED check for self.in_li, paragraph should close always!
629 + if self.formatter.in_p:
630 + result.append(self.formatter.paragraph(0))
631 + result.append(self.formatter.table_cell(0))
632
633 # check for adjacent cell markers
634 if word.count("|") > 2:
635 @@ -713,8 +757,9 @@
636 if not attrs.has_key('colspan'):
637 attrs['colspan'] = '"%d"' % (word.count("|")/2)
638
639 - # return the complete cell markup
640 - return leader + self.formatter.table_cell(1, attrs) + attrerr
641 + # return the complete cell markup
642 + result.append(self.formatter.table_cell(1, attrs) + attrerr)
643 + return ''.join(result)
644 else:
645 return word
646
647 @@ -723,13 +768,7 @@
648 """Handle section headings."""
649 import sha
650
651 - self.inhibit_p = 1
652 - icons = ''
653 - if self.request.user.show_topbottom:
654 - bottom = self.request.theme.make_icon('bottom')
655 - icons = icons + self.formatter.url("#bottom", bottom, unescaped=1)
656 - top = self.request.theme.make_icon('top')
657 - icons = icons + self.formatter.url("#top", top, unescaped=1)
658 + ##self.inhibit_p = 1
659
660 h = word.strip()
661 level = 1
662 @@ -737,46 +776,57 @@
663 level = level+1
664 depth = min(5,level)
665
666 + # this is needed for Included pages
667 + # TODO but it might still result in unpredictable results
668 + # when included the same page multiple times
669 title_text = h[level:-level].strip()
670 - self.titles.setdefault(title_text, 0)
671 - self.titles[title_text] += 1
672 + pntt = self.formatter.page.page_name + title_text
673 + self.titles.setdefault(pntt, 0)
674 + self.titles[pntt] += 1
675
676 unique_id = ''
677 - if self.titles[title_text] > 1:
678 - unique_id = '-%d' % self.titles[title_text]
679 -
680 - return self.formatter.heading(depth, self.highlight_text(title_text), icons=icons, id="head-"+sha.new(title_text).hexdigest()+unique_id)
681 -
682 -
683 + if self.titles[pntt] > 1:
684 + unique_id = '-%d' % self.titles[pntt]
685 + result = self._closeP()
686 + result += self.formatter.heading(1, depth, id="head-"+sha.new(pntt.encode(config.charset)).hexdigest()+unique_id)
687 +
688 + return (result + self.formatter.text(title_text) +
689 + self.formatter.heading(0, depth))
690 +
691 def _processor_repl(self, word):
692 """Handle processed code displays."""
693 if word[:3] == '{{{': word = word[3:]
694
695 self.processor = None
696 self.processor_name = None
697 + self.processor_is_parser = 0
698 s_word = word.strip()
699 if s_word == '#!':
700 # empty bang paths lead to a normal code display
701 # can be used to escape real, non-empty bang paths
702 word = ''
703 self.in_pre = 3
704 - return self.formatter.preformatted(1)
705 + return self._closeP() + self.formatter.preformatted(1)
706 elif s_word[:2] == '#!':
707 + # first try to find a processor for this (will go away in 1.4)
708 processor_name = s_word[2:].split()[0]
709 - self.processor = wikiutil.importPlugin("processor", processor_name, "process")
710 - if not self.processor and s_word.find('python') > 0:
711 - from MoinMoin.processor.Colorize import process
712 - self.processor = process
713 - self.processor_name = "Colorize"
714 + self.processor = wikiutil.importPlugin(
715 + self.request.cfg, "processor", processor_name, "process")
716 + # now look for a parser with that name
717 + if self.processor is None:
718 + self.processor = wikiutil.importPlugin(
719 + self.request.cfg, "parser", processor_name, "Parser")
720 + if self.processor:
721 + self.processor_is_parser = 1
722
723 if self.processor:
724 self.processor_name = processor_name
725 self.in_pre = 2
726 self.colorize_lines = [word]
727 return ''
728 - elif s_word:
729 + elif s_word:
730 self.in_pre = 3
731 - return self.formatter.preformatted(1) + \
732 + return self._closeP() + self.formatter.preformatted(1) + \
733 self.formatter.text(s_word + ' (-)')
734 else:
735 self.in_pre = 1
736 @@ -787,17 +837,18 @@
737 word = word.strip()
738 if word == '{{{' and not self.in_pre:
739 self.in_pre = 3
740 - return self.formatter.preformatted(self.in_pre)
741 + ##self.inhibit_p = 1
742 + return self._closeP() + self.formatter.preformatted(self.in_pre)
743 elif word == '}}}' and self.in_pre:
744 self.in_pre = 0
745 - self.inhibit_p = 1
746 + self.inhibit_p = 0
747 return self.formatter.preformatted(self.in_pre)
748 return word
749
750
751 def _smiley_repl(self, word):
752 """Handle smileys."""
753 - return wikiutil.getSmiley(word, self.formatter)
754 + return self.formatter.smiley(word)
755
756 _smileyA_repl = _smiley_repl
757
758 @@ -805,7 +856,11 @@
759 def _comment_repl(self, word):
760 return ''
761
762 -
763 + def _closeP(self):
764 + if self.formatter.in_p:
765 + return self.formatter.paragraph(0)
766 + return ''
767 +
768 def _macro_repl(self, word):
769 """Handle macros ([[macroname]])."""
770 macro_name = word[2:-2]
771 @@ -820,71 +875,63 @@
772 # create macro instance
773 if self.macro is None:
774 self.macro = wikimacro.Macro(self)
775 -
776 - # call the macro
777 return self.formatter.macro(self.macro, macro_name, args)
778
779 -
780 - def highlight_text(self, text, **kw):
781 - if not self.hilite_re: return self.formatter.text(text)
782 -
783 - # work around for dom/xml formatter
784 - # if not self.hilite_re: return text
785 - # XXX bad idea: this allowed `<b>raw html</b>` to get through!
786 -
787 - result = []
788 - lastpos = 0
789 - match = self.hilite_re.search(text)
790 - while match and lastpos < len(text):
791 - # add the match we found
792 - result.append(self.formatter.text(text[lastpos:match.start()]))
793 - result.append(self.formatter.highlight(1))
794 - result.append(self.formatter.text(match.group(0)))
795 - result.append(self.formatter.highlight(0))
796 -
797 - # search for the next one
798 - lastpos = match.end() + (match.end() == lastpos)
799 - match = self.hilite_re.search(text, lastpos)
800 -
801 - result.append(self.formatter.text(text[lastpos:]))
802 - return ''.join(result)
803 -
804 def scan(self, scan_re, line):
805 - """ scans the line for wiki syntax and replaces the
806 - found regular expressions
807 - calls highlight_text if self.hilite_re is set
808 + """ Scans one line
809 +
810 + Append text before match, invoke replace() with match, and
811 + add text after match.
812 """
813 result = []
814 lastpos = 0
815 - match = scan_re.search(line)
816 - while match and lastpos < len(line):
817 - # add the match we found
818 - if self.hilite_re:
819 - result.append(self.highlight_text(line[lastpos:match.start()]))
820 - else:
821 +
822 + ###result.append(u'<span class="info">[scan: <tt>"%s"</tt>]</span>' % line)
823 +
824 + for match in scan_re.finditer(line):
825 + # Add text before the match
826 + if lastpos < match.start():
827 +
828 + ###result.append(u'<span class="info">[add text before match: <tt>"%s"</tt>]</span>' % line[lastpos:match.start()])
829 +
830 + if not (self.inhibit_p or self.in_pre or self.formatter.in_p):
831 + result.append(self.formatter.paragraph(1))
832 result.append(self.formatter.text(line[lastpos:match.start()]))
833 +
834 + # Replace match with markup
835 result.append(self.replace(match))
836 -
837 - # search for the next one
838 - lastpos = match.end() + (match.end() == lastpos)
839 - match = scan_re.search(line, lastpos)
840 -
841 - if self.hilite_re:
842 - result.append(self.highlight_text(line[lastpos:]))
843 - else:
844 - result.append(self.formatter.text(line[lastpos:]))
845 - return ''.join(result)
846 + lastpos = match.end()
847 +
848 + ###result.append('<span class="info">[no match, add rest: <tt>"%s"<tt>]</span>' % line[lastpos:])
849 +
850 + # No match: Add paragraph with the text of the line
851 + if not (self.in_pre or self.inhibit_p or
852 + self.formatter.in_p) and lastpos < len(line):
853 + result.append(self.formatter.paragraph(1))
854 + result.append(self.formatter.text(line[lastpos:]))
855 + return u''.join(result)
856
857 def replace(self, match):
858 - #hit = filter(lambda g: g[1], match.groupdict().items())
859 + """ Replace match using type name """
860 + result = []
861 for type, hit in match.groupdict().items():
862 if hit is not None and type != "hmarker":
863 - ##print "###", cgi.escape(`type`), cgi.escape(`hit`), "###"
864 +
865 + ###result.append(u'<span class="info">[replace: %s: "%s"]</span>' % (type, hit))
866 if self.in_pre and type not in ['pre', 'ent']:
867 - return self.highlight_text(hit)
868 + return self.formatter.text(hit)
869 else:
870 - return getattr(self, '_' + type + '_repl')(hit)
871 + # Open p for certain types
872 + if not (self.inhibit_p or self.formatter.in_p
873 + or self.in_pre or (type in self.no_new_p_before)):
874 + result.append(self.formatter.paragraph(1))
875 +
876 + # Get replace method and replece hit
877 + replace = getattr(self, '_' + type + '_repl')
878 + result.append(replace(hit))
879 + return ''.join(result)
880 else:
881 + # We should never get here
882 import pprint
883 raise Exception("Can't handle match " + `match`
884 + "\n" + pprint.pformat(match.groupdict())
885 @@ -892,7 +939,6 @@
886
887 return ""
888
889 -
890 def format(self, formatter):
891 """ For each line, scan through looking for magic
892 strings, outputting verbatim any intervening text.
893 @@ -902,23 +948,25 @@
894
895 # prepare regex patterns
896 rules = self.formatting_rules.replace('\n', '|')
897 - if config.allow_extended_names:
898 - rules = rules + r'|(?P<wikiname_bracket>\[".*?"\])'
899 - if config.bang_meta:
900 - rules = r'(?P<notword>!%(word_rule)s)|%(rules)s' % {
901 + if self.cfg.allow_extended_names:
902 + rules = rules + ur'|(?P<wikiname_bracket>\[".*?"\])'
903 + if self.cfg.bang_meta:
904 + rules = ur'(?P<notword>!%(word_rule)s)|%(rules)s' % {
905 'word_rule': self.word_rule,
906 'rules': rules,
907 }
908 - if config.backtick_meta:
909 - rules = rules + r'|(?P<tt_bt>`.*?`)'
910 - if config.allow_numeric_entities:
911 - rules = r'(?P<ent_numeric>&#\d{1,5};)|' + rules
912 -
913 - scan_re = re.compile(rules)
914 - number_re = re.compile(self.ol_rule)
915 - term_re = re.compile(self.dl_rule)
916 - indent_re = re.compile("^\s*")
917 - eol_re = re.compile(r'\r?\n')
918 + if self.cfg.backtick_meta:
919 + rules = rules + ur'|(?P<tt_bt>`.*?`)'
920 + if self.cfg.allow_numeric_entities:
921 + rules = ur'(?P<ent_numeric>&#\d{1,5};)|' + rules
922 +
923 + self.request.clock.start('compile_huge_and_ugly')
924 + scan_re = re.compile(rules, re.UNICODE)
925 + number_re = re.compile(self.ol_rule, re.UNICODE)
926 + term_re = re.compile(self.dl_rule, re.UNICODE)
927 + indent_re = re.compile("^\s*", re.UNICODE)
928 + eol_re = re.compile(r'\r?\n', re.UNICODE)
929 + self.request.clock.stop('compile_huge_and_ugly')
930
931 # get text and replace TABs
932 rawtext = self.raw.expandtabs()
933 @@ -928,6 +976,7 @@
934 self.lines = eol_re.split(rawtext)
935 self.line_is_empty = 0
936
937 + # Main loop
938 for line in self.lines:
939 self.lineno = self.lineno + 1
940 self.table_rowstart = 1
941 @@ -937,25 +986,32 @@
942 self.inhibit_p = 0
943
944 if self.in_pre:
945 + # TODO: move this into function
946 # still looking for processing instructions
947 + # TODO: use strings for pre state, not numbers
948 if self.in_pre == 1:
949 self.processor = None
950 + self.processor_is_parser = 0
951 processor_name = ''
952 if (line.strip()[:2] == "#!"):
953 - from MoinMoin.processor import processors
954 processor_name = line.strip()[2:].split()[0]
955 - self.processor = wikiutil.importPlugin("processor", processor_name, "process")
956 - if not self.processor and (line.find('python') > 0):
957 - from MoinMoin.processor.Colorize import process
958 - self.processor = process
959 - processor_name = "Colorize"
960 + self.processor = wikiutil.importPlugin(
961 + self.request.cfg, "processor", processor_name, "process")
962 +
963 + # now look for a parser with that name
964 + if self.processor is None:
965 + self.processor = wikiutil.importPlugin(
966 + self.request.cfg, "parser", processor_name, "Parser")
967 + if self.processor:
968 + self.processor_is_parser = 1
969 if self.processor:
970 self.in_pre = 2
971 self.colorize_lines = [line]
972 self.processor_name = processor_name
973 continue
974 else:
975 - self.request.write(self.formatter.preformatted(1))
976 + self.request.write(self._closeP() +
977 + self.formatter.preformatted(1))
978 self.in_pre = 3
979 if self.in_pre == 2:
980 # processing mode
981 @@ -965,8 +1021,14 @@
982 continue
983 if line[:endpos]:
984 self.colorize_lines.append(line[:endpos])
985 - self.request.write(
986 - self.formatter.processor(self.processor_name, self.colorize_lines))
987 +
988 + # Close p before calling processor
989 + # TODO: do we really need this?
990 + self.request.write(self._closeP())
991 + res = self.formatter.processor(self.processor_name,
992 + self.colorize_lines,
993 + self.processor_is_parser)
994 + self.request.write(res)
995 del self.colorize_lines
996 self.in_pre = 0
997 self.processor = None
998 @@ -974,19 +1036,23 @@
999 # send rest of line through regex machinery
1000 line = line[endpos+3:]
1001 else:
1002 - # paragraph break on empty lines
1003 + # we don't have \n as whitespace any more
1004 + # This is the space between lines we join to one paragraph
1005 + line = line + ' '
1006 +
1007 + # Paragraph break on empty lines
1008 if not line.strip():
1009 - #self.request.write("<!-- empty line start -->\n")
1010 - if self.formatter.in_p:
1011 - self.request.write(self.formatter.paragraph(0))
1012 if self.in_table:
1013 self.request.write(self.formatter.table(0))
1014 self.in_table = 0
1015 + # CHANGE: removed check for not self.list_types
1016 + # p should close on every empty line
1017 + if (self.formatter.in_p):
1018 + self.request.write(self.formatter.paragraph(0))
1019 self.line_is_empty = 1
1020 - #self.request.write("<!-- empty line end -->\n")
1021 continue
1022
1023 - # check indent level
1024 + # Check indent level
1025 indent = indent_re.match(line)
1026 indlen = len(indent.group(0))
1027 indtype = "ul"
1028 @@ -1010,45 +1076,50 @@
1029 indtype = "dl"
1030
1031 # output proper indentation tags
1032 - #self.request.write("<!-- inhibit_p==%d -->\n" % self.inhibit_p)
1033 - #self.request.write("<!-- #%d calling _indent_to -->\n" % self.lineno)
1034 - self.request.write(self._indent_to(indlen, indtype, numtype, numstart))
1035 - #self.request.write("<!-- #%d after calling _indent_to -->\n" % self.lineno)
1036 - #self.request.write("<!-- inhibit_p==%d -->\n" % self.inhibit_p)
1037 + self.request.write(self._indent_to(indlen, indtype, numtype,
1038 + numstart))
1039
1040 - # start or end table mode
1041 - if not self.in_table and line[indlen:indlen+2] == "||" and line[-2:] == "||":
1042 + # Table mode
1043 + # TODO: move into function?
1044 + if (not self.in_table and line[indlen:indlen + 2] == "||"
1045 + and line[-3:] == "|| " and len(line) >= 5 + indlen):
1046 + # Start table
1047 + if self.list_types and not self.in_li:
1048 + self.request.write(self.formatter.listitem
1049 + (1, style="list-style-type:none"))
1050 + ## CHANGE: no automatic p on li
1051 + ##self.request.write(self.formatter.paragraph(1))
1052 + self.in_li = 1
1053 +
1054 + # CHANGE: removed check for self.in_li
1055 + # paragraph should end before table, always!
1056 + if self.formatter.in_p:
1057 + self.request.write(self.formatter.paragraph(0))
1058 attrs, attrerr = self._getTableAttrs(line[indlen+2:])
1059 self.request.write(self.formatter.table(1, attrs) + attrerr)
1060 - self.in_table = self.lineno
1061 - elif self.in_table and not(line[:2]=="##" or # intra-table comments should not break a table
1062 - line[indlen:indlen+2] == "||" and line[-2:] == "||"):
1063 + self.in_table = True # self.lineno
1064 + elif (self.in_table and not
1065 + # intra-table comments should not break a table
1066 + (line[:2]=="##" or
1067 + line[indlen:indlen + 2] == "||" and
1068 + line[-3:] == "|| " and
1069 + len(line) >= 5 + indlen)):
1070 +
1071 + # Close table
1072 self.request.write(self.formatter.table(0))
1073 self.in_table = 0
1074 -
1075 - # convert line from wiki markup to HTML and print it
1076 - if not self.in_pre: # we don't want to have trailing blanks in pre
1077 - line = line + " " # we don't have \n as whitespace any more
1078 -
1079 - formatted_line = self.scan(scan_re, line) # this also sets self.inhibit_p as side effect!
1080 -
1081 - #self.request.write("<!-- inhibit_p==%d -->\n" % self.inhibit_p)
1082 - if not (self.inhibit_p or self.in_pre or self.in_table or self.formatter.in_p):
1083 - self.request.write(self.formatter.paragraph(1))
1084 -
1085 - #self.request.write("<!-- %s\n start -->\n" % line)
1086 +
1087 + # Scan line, format and write
1088 + formatted_line = self.scan(scan_re, line)
1089 self.request.write(formatted_line)
1090 - #self.request.write("<!-- end -->\n")
1091
1092 if self.in_pre:
1093 self.request.write(self.formatter.linebreak())
1094 - #if self.in_li:
1095 - # self.in_li = 0
1096 - # self.request.write(self.formatter.listitem(0))
1097
1098 - # close code displays, paragraphs, tables and open lists
1099 + # Close code displays, paragraphs, tables and open lists
1100 + self.request.write(self._undent())
1101 if self.in_pre: self.request.write(self.formatter.preformatted(0))
1102 if self.formatter.in_p: self.request.write(self.formatter.paragraph(0))
1103 if self.in_table: self.request.write(self.formatter.table(0))
1104 - self.request.write(self._undent())
1105 +
1106
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