Attachment 'text_html.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - "text/html+css" Formatter
4
5 @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
6 @license: GNU GPL, see COPYING for details.
7 """
8
9 from MoinMoin.formatter.base import FormatterBase
10 from MoinMoin import wikiutil, i18n, config
11 from MoinMoin.Page import Page
12
13 class Formatter(FormatterBase):
14 """
15 Send HTML data.
16 """
17
18 hardspace = ' '
19
20 def __init__(self, request, **kw):
21 apply(FormatterBase.__init__, (self, request), kw)
22
23 # inline tags stack. When an inline tag is called, it goes into
24 # the stack. When a block elemetns starts, all inline tags in
25 # the stack are closed.
26 self._inlineStack = []
27
28 self._in_li = 0
29 self._in_code = 0
30 self._in_code_area = 0
31 self._in_code_line = 0
32 self._code_area_num = 0
33 self._code_area_state = ['', 0, -1, -1, 0]
34 self._show_section_numbers = None
35 self._content_ids = []
36 self.pagelink_preclosed = False
37 self._is_included = kw.get('is_included',False)
38 self.request = request
39 self.cfg = request.cfg
40
41 if not hasattr(request, '_fmt_hd_counters'):
42 request._fmt_hd_counters = []
43
44 def langAttr(self, lang=None):
45 """ Return lang and dir attribute
46
47 Must be used on all block elements - div, p, table, etc.
48 @param lang: if defined, will return attributes for lang. if not
49 defined, will return attributes only if the current lang is
50 differnt from the content lang.
51 @rtype: dict
52 @retrun: language attributes
53 """
54 if not lang:
55 lang = self.request.current_lang
56 # Actions that generate content in user language should change
57 # the content lang from the default defined in cfg.
58 if lang == self.request.content_lang:
59 # lang is inherited from content div
60 return {}
61
62 attr = {'lang': lang, 'dir': i18n.getDirection(lang),}
63 return attr
64
65
66 def _langAttr(self, lang=None):
67 """ Return lang and dir attribute
68
69 DONT USE, use langAttr instead
70
71 TODO: update all clients that use this to use langAttr and
72 delete this!
73
74 Must be used on all block elements - div, p, table, etc.
75 @param lang: if defined, will return attributes for lang. if not
76 defined, will return attributes only if the current lang is
77 differnt from the content lang.
78 @rtype: string
79 @retrun: language attributes
80 """
81 if not lang:
82 lang = self.request.current_lang
83 # Actions that generate content in user language should change
84 # the content lang from the default defined in cfg.
85 if lang == self.request.content_lang:
86 # lang is inherited from content div
87 return ''
88 result = ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
89 return result
90
91 # Primitive formater functions #####################################
92
93 # all other methods should use these to format tags. This keeps the
94 # code clean and handle pathological cases like unclosed p and
95 # inline tages.
96
97 def formatAttributes(self, attr=None):
98 """ Return formated atributes string
99
100 @param attr: dict containing keys and values
101 @rtype: string ?
102 @return: formated attribtues or empty string
103 """
104 if attr:
105 attr = [' %s="%s"' % (k, v) for k, v in attr.items()]
106 return ''.join(attr)
107 return ''
108
109 def openInlineTag(self, tag, attr=None):
110 """ Open a tag with optional attribues
111
112 @param tag: html tag, string
113 @parm attr: dict with tag attribues
114 @rtype: string ?
115 @return: open tag with attributes
116 """
117 # Add to inlineStack
118 self._inlineStack.append(tag)
119 # Format
120 return '<%s%s>' % (tag, self.formatAttributes(attr))
121
122 def closeInlineTag(self, tag):
123 """ Close inline tag, remove from stack
124
125 @param tag: html tag, string
126 @rtype: string ?
127 @return: closing tag
128 """
129 # Pull from stack, ignore order, that not our problem. The code
130 # that calls us should keep correct calling order.
131 tagindex = self._inlineStack.index(tag)
132 if tagindex != -1:
133 del self._inlineStack[tagindex]
134 return '</%s>' % tag
135
136 def openBlockTag(self, tag, newline=False, attr=None):
137 """ Open a tag with optional attribues
138
139 @param tag: html tag, string
140 @param newline: render tag on a separate line
141 @parm attr: dict with tag attribues
142 @rtype: string ?
143 @return: open tag with attributes
144 """
145 result = []
146 # Close p tags, our parser like to wrap everyting in p
147 if self.in_p:
148 self.in_p = 0
149 result.append(self.paragraph(False))
150
151 # Format with attribtues and newliine
152 if newline:
153 result.append('\n')
154 attr = self.formatAttributes(attr)
155 result.append('<%s%s>' % (tag, attr))
156 return ''.join(result)
157
158 def closeBlockTag(self, tag, newline=False):
159 """ Close inline tag, remove from stack
160
161 @param tag: html tag, string
162 @param newline: render tag on a new line
163 @rtype: string ?
164 @return: closing tag
165 """
166 # Close all tags in inline stack
167 # Work on a copy, because closeInlineTag manipulate the stack
168 result = []
169 stack = self.inlineStack[:]
170 stack.reverse()
171 for tag in stack:
172 result.append(self.closeInlineTag(tag))
173 # Format with newline
174 if newline:
175 result.append('\n')
176 result.append('</%s>\n' % (tag))
177 return ''.join(result)
178
179 # Public methods ###################################################
180
181 def startContent(self, content_id='content', **kwargs):
182 """ Start content div """
183 if content_id!='content':
184 aid = 'top_%s' % (content_id,)
185 else:
186 aid = 'top'
187 self._content_ids.append(content_id)
188 return '<div id="%(id)s" %(lang_attr)s>\n%(anchor)s\n' % {
189 'id': content_id,
190 'lang_attr': self._langAttr(self.request.content_lang),
191 'anchor': self.anchordef(aid)
192 }
193
194 def endContent(self):
195 try:
196 cid = self._content_ids.pop()
197 except:
198 cid = 'content'
199 if cid!='content':
200 aid = 'bottom_%s' % (cid,)
201 else:
202 aid = 'bottom'
203 return '%s\n</div>\n' % self.anchordef(aid)
204
205 def lang(self, on, lang_name):
206 """ Insert text with specific lang and direction.
207
208 Enclose within span tag if lang_name is different from
209 the current lang
210 """
211 if lang_name != self.request.current_lang:
212 dir = i18n.getDirection(lang_name)
213 return ['<span lang="%(lang_name)s" dir="%(dir)s">' % {
214 'lang_name': lang_name, 'dir': dir},
215 '</span>'] [not on]
216
217 return ''
218
219 def sysmsg(self, on, **kw):
220 return ['%s<div class="message">' % self.existParagraph(), '</div>\n'][not on]
221
222
223 # Links ##############################################################
224
225 def pagelink(self, on, pagename='', **kw):
226 """ Link to a page.
227
228 See wikiutil.link_tag() for possible keyword parameters.
229 """
230 apply(FormatterBase.pagelink, (self, on, pagename), kw)
231 page = Page(self.request, pagename, formatter=self);
232
233 if self.request.user.show_nonexist_qm and on and not page.exists():
234 self.pagelink_preclosed = True
235 return (page.link_to(self.request, on=1, **kw) +
236 self.text("?") +
237 page.link_to(self.request, on=0, **kw))
238 elif not on and self.pagelink_preclosed:
239 self.pagelink_preclosed = False
240 return ""
241 else:
242 return page.link_to(self.request, on=on, **kw)
243
244 def interwikilink(self, on, interwiki='', pagename='', **kw):
245 if not on: return '</a>'
246
247 wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:%s' % (interwiki, pagename))
248 wikiurl = wikiutil.mapURL(self.request, wikiurl)
249 href = wikiutil.join_wiki(wikiurl, wikitail)
250
251 # return InterWiki hyperlink
252 if wikitag_bad:
253 html_class = 'badinterwiki'
254 else:
255 html_class = 'interwiki'
256
257 icon = ''
258 if self.request.user.show_fancy_links:
259 icon = self.request.theme.make_icon('interwiki', {'wikitag': wikitag})
260 return (self.url(1, href, title=wikitag, unescaped=1,
261 pretty_url=kw.get('pretty_url', 0), css = html_class) +
262 icon)
263
264 def url(self, on, url=None, css=None, **kw):
265 """
266 Keyword params:
267 title - title attribute
268 ... some more (!!! TODO)
269 """
270 url = wikiutil.mapURL(self.request, url)
271 pretty = kw.get('pretty_url', 0)
272 title = kw.get('title', None)
273
274 #if not pretty and wikiutil.isPicture(url):
275 # # XXX
276 # return '<img src="%s" alt="%s">' % (url,url)
277
278 # create link
279 if not on:
280 return '</a>'
281 str = '<a'
282 if css: str = '%s class="%s"' % (str, css)
283 if title: str = '%s title="%s"' % (str, title)
284 str = '%s href="%s">' % (str, wikiutil.escape(url, 1))
285
286 type = kw.get('type', '')
287
288 if type=='www':
289 str = '%s%s ' % (str, self.icon("www"))
290 elif type=='mailto':
291 str = '%s%s ' % (str, self.icon('mailto'))
292
293 return str
294
295 def anchordef(self, id):
296 return '<a id="%s"></a>' % (id, )
297
298 def anchorlink(self, on, name='', id = None):
299 extra = ''
300 if id:
301 extra = ' id="%s"' % id
302 return ['<a href="#%s"%s>' % (name, extra), '</a>'][not on]
303
304 # Text and Text Attributes ###########################################
305
306 def _text(self, text):
307 if self._in_code:
308 return wikiutil.escape(text).replace(' ', self.hardspace)
309 return wikiutil.escape(text)
310
311 # Inline ###########################################################
312
313 def strong(self, on):
314 tag = 'strong'
315 if on:
316 return self.openInlineTag(tag)
317 return self.closeInlineTag(tag)
318
319 def emphasis(self, on):
320 tag = 'em'
321 if on:
322 return self.openInlineTag(tag)
323 return self.closeInlineTag(tag)
324
325 def underline(self, on):
326 tag = 'u'
327 if on:
328 return self.openInlineTag(tag)
329 return self.closeInlineTag(tag)
330
331 def highlight(self, on):
332 tag = 'strong'
333 if on:
334 return self.openInlineTag(tag, attr={'class': 'highlight'})
335 return self.closeInlineTag(tag)
336
337 def sup(self, on):
338 tag = 'sup'
339 if on:
340 return self.openInlineTag(tag)
341 return self.closeInlineTag(tag)
342
343 def sub(self, on):
344 tag = 'sub'
345 if on:
346 return self.openInlineTag(tag)
347 return self.closeInlineTag(tag)
348
349 def code(self, on):
350 tag = 'tt'
351
352 # Maybe we don't need this, becuse whe have tt will be in inlineStack.
353 self._in_code = on
354
355 if on:
356 return self.openInlineTag(tag)
357 return self.closeInlineTag(tag)
358
359 def small(self, on):
360 tag = 'small'
361 if on:
362 return self.openInlineTag(tag)
363 return self.closeInlineTag(tag)
364
365 def big(self, on):
366 tag = 'big'
367 if on:
368 return self.openInlineTag(tag)
369 return self.closeInlineTag(tag)
370
371
372 # Block elements ###################################################
373
374 def preformatted(self, on):
375 FormatterBase.preformatted(self, on)
376 tag = 'pre'
377 if on:
378 return self.openBlockTag(tag)
379 return self.closeBlockTag(tag)
380
381 def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1):
382 res = ''
383 ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num))
384 if on:
385 self._in_code_area = 1
386 self._in_code_line = 0
387 self._code_area_state = [ci, show, start, step, start]
388 if self._code_area_num == 0 and self._code_area_state[1] >= 0:
389 res += """<script language='JavaScript'>
390 function isnumbered(obj){
391 return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber';
392 }
393 function nformat(num,chrs,add){
394 var nlen = Math.max(0,chrs-(''+num).length), res = '';
395 while (nlen>0) { res += ' '; nlen-- }
396 return res+num+add;
397 }
398 function addnumber(did,nstart,nstep){
399 var c = document.getElementById(did), l = c.firstChild, n = 1;
400 if (!isnumbered(c))
401 if (typeof nstart == 'undefined') nstart = 1;
402 if (typeof nstep == 'undefined') nstep = 1;
403 n = nstart;
404 while (l != null){
405 if (l.tagName == 'SPAN'){
406 var s = document.createElement('SPAN');
407 s.className = 'LineNumber'
408 s.appendChild(document.createTextNode(nformat(n,4,' ')));
409 n += nstep;
410 if (l.childNodes.length)
411 l.insertBefore(s, l.firstChild)
412 else
413 l.appendChild(s)
414 }
415 l = l.nextSibling;
416 }
417 return false;
418 }
419 function remnumber(did){
420 var c = document.getElementById(did), l = c.firstChild;
421 if (isnumbered(c))
422 while (l != null){
423 if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild);
424 l = l.nextSibling;
425 }
426 return false;
427 }
428 function togglenumber(did,nstart,nstep){
429 var c = document.getElementById(did);
430 if (isnumbered(c)) {
431 remnumber(did);
432 } else {
433 addnumber(did,nstart,nstep);
434 }
435 return false;
436 }
437 </script>
438 """
439 res += self.openBlockTag('div', attr={'class': 'codearea'})
440 ## res += self.existParagraph() + '<div class="codearea">\n'
441 if self._code_area_state[1] >= 0:
442 res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
443 res += '<pre id="%s" class="codearea">' % (self._code_area_state[0], )
444 else:
445 res = ''
446 if self._in_code_line:
447 res += self.code_line(0)
448 res += '</pre>'
449 if self._code_area_state[1] >= 0:
450 res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
451
452 # This will close all user left open inline tags
453 res += self.closeBlockTag('div')
454
455 self._in_code_area = 0
456 self._code_area_num += 1
457 return res
458
459 def code_line(self, on):
460 res = ''
461 if not on or (on and self._in_code_line):
462 res += '</span>\n'
463 if on:
464 res += '<span class="line">'
465 if self._code_area_state[1] > 0:
466 res += '<span class="LineNumber">%4d </span>' % (self._code_area_state[4], )
467 self._code_area_state[4] += self._code_area_state[3]
468 self._in_code_line = on != 0
469 return res
470
471 def code_token(self, on, tok_type):
472 return ['<span class="%s">' % tok_type, '</span>'][not on]
473
474 # Paragraphs, Lines, Rules ###########################################
475
476 def linebreak(self, preformatted=1):
477 if self._in_code_area:
478 preformatted = 1
479 return ['\n', '<br>\n'][not preformatted]
480
481 def paragraph(self, on):
482 if self._terse:
483 return ''
484 FormatterBase.paragraph(self, on)
485 if self._in_li:
486 self._in_li = self._in_li + 1
487 tag = 'p'
488 if on:
489 return self.openBlockTag(tag, attr=self.langAttr())
490 return self.closeBlockTag(tag)
491
492 def rule(self, size=0):
493 if size:
494 return self.openBlockTag('hr', attr={'size': size})
495 return self.openBlockTag('hr')
496
497 def icon(self, type):
498 return self.request.theme.make_icon(type)
499
500 def smiley(self, text):
501 w, h, b, img = config.smileys[text.strip()]
502 href = img
503 if not href.startswith('/'):
504 href = self.request.theme.img_url(img)
505 return self.image(src=href, alt=text, width=str(w), height=str(h))
506
507 # Lists ##############################################################
508
509 def number_list(self, on, type=None, start=None):
510 tag = 'ol'
511 if on:
512 attr = self.langAttr()
513 if type is not None:
514 attr['type'] = type
515 if start is not None:
516 attr['start'] = start
517 return self.openBlockTag(tag, attr=attr)
518 return self.closeBlockTag(tag)
519
520 def bullet_list(self, on):
521 tag = 'ul'
522 if on:
523 attr = self.langAttr()
524 return self.openBlockTag(tag, attr=attr)
525 return self.closeBlockTag(tag)
526
527 def listitem(self, on, **kw):
528 """ List item inherit its lang from the list. """
529 tag = 'li'
530 self._in_li = on != 0
531 if on:
532 css_class = kw.get('css_class', None)
533 attrs = ''
534 if css_class: attrs += ' class="%s"' % (css_class,)
535 style = kw.get('style', None)
536 if style: attrs += ' style="%s"' % style
537 result = '<li%s>' % (attrs,)
538 else:
539 result = '</li>\n'
540 return result
541
542 def definition_list(self, on):
543 result = ['%s<dl>' % self.exitsParagraph(), '</dl>\n'][not on]
544 return result
545
546 def definition_term(self, on):
547 return ['<dt%s>' % (self._langAttr()), '</dt>\n'][not on]
548
549 def definition_desc(self, on):
550 return ['<dd%s>' % self._langAttr(), '</dd>\n'][not on]
551
552 def heading(self, on, depth, id = None, **kw):
553 # remember depth of first heading, and adapt counting depth accordingly
554 if not self._base_depth:
555 self._base_depth = depth
556
557 count_depth = max(depth - (self._base_depth - 1), 1)
558
559 # check numbering, possibly changing the default
560 if self._show_section_numbers is None:
561 self._show_section_numbers = self.cfg.show_section_numbers
562 numbering = self.request.getPragma('section-numbers', '').lower()
563 if numbering in ['0', 'off']:
564 self._show_section_numbers = 0
565 elif numbering in ['1', 'on']:
566 self._show_section_numbers = 1
567 elif numbering in ['2', '3', '4', '5', '6']:
568 # explicit base level for section number display
569 self._show_section_numbers = int(numbering)
570
571 heading_depth = depth + 1
572
573 # closing tag
574 if not on:
575 return '</h%d>\n' % heading_depth
576
577
578 # create section number
579 number = ''
580 if self._show_section_numbers:
581 # count headings on all levels
582 self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth]
583 while len(self.request._fmt_hd_counters) < count_depth:
584 self.request._fmt_hd_counters.append(0)
585 self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1
586 number = '.'.join(map(str, self.request._fmt_hd_counters[self._show_section_numbers-1:]))
587 if number: number += ". "
588
589 id_text = ''
590 if id:
591 id_text = ' id="%s"' % id
592
593 result = '<h%d%s>' % (heading_depth, id_text)
594 if self.request.user.show_topbottom:
595 # TODO change top/bottom refs to content-specific top/bottom refs?
596 result = ("%s%s%s%s%s%s%s%s" %
597 (result,
598 kw.get('icons',''),
599 self.url(1, "#bottom", unescaped=1),
600 self.icon('bottom'),
601 self.url(0),
602 self.url(1, "#top", unescaped=1),
603 self.icon('top'),
604 self.url(0)))
605 return "%s%s%s" % (result, kw.get('icons',''), number)
606
607
608 # Tables #############################################################
609
610 # TODO: find better solution for bgcolor, align, valign (deprecated in html4)
611 # do not remove current code before making working compliant code
612
613 _allowed_table_attrs = {
614 'table': ['class', 'width', 'bgcolor'],
615 'row': ['class', 'width', 'align', 'valign', 'bgcolor'],
616 '': ['colspan', 'rowspan', 'class', 'width', 'align', 'valign', 'bgcolor'],
617 }
618
619 def _checkTableAttr(self, attrs, prefix):
620 if not attrs: return ''
621
622 result = ''
623 for key, val in attrs.items():
624 if prefix and key[:len(prefix)] != prefix: continue
625 key = key[len(prefix):]
626 if key not in self._allowed_table_attrs[prefix]: continue
627 result = '%s %s=%s' % (result, key, val)
628 return result
629
630 def table(self, on, attrs={}):
631 if on:
632 # Enclose table inside a div to get correct alignment
633 # when using language macros
634 attrs = attrs and attrs.copy() or {}
635 result = '%(endpara)s<div%(lang)s>\n<table%(tableAttr)s>' % {
636 'endpara': self.exitsParagraph(),
637 'lang': self._langAttr(),
638 'tableAttr': self._checkTableAttr(attrs, 'table')
639 }
640 else:
641 result = '</table>\n</div>'
642 return '%s\n' % result
643
644 def table_row(self, on, attrs={}):
645 if on:
646 result = '<tr%s>' % self._checkTableAttr(attrs, 'row')
647 else:
648 result = '</tr>'
649 return '%s\n' % result
650
651 def table_cell(self, on, attrs={}):
652 if on:
653 result = '<td%s>' % self._checkTableAttr(attrs, '')
654 else:
655 result = '</td>'
656 return '%s\n' % result
657
658 def escapedText(self, text):
659 return wikiutil.escape(text)
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.