1 # -*- coding: iso-8859-1 -*-
3 MoinMoin - MoinMoin Wiki Markup Parser
5 @copyright: 2000, 2001, 2002 by Jürgen Hermann <jh@web.de>
6 @license: GNU GPL, see COPYING for details.
10 from MoinMoin import config, wikiutil
11 from MoinMoin import macro as wikimacro
12 from MoinMoin.Page import Page
13 from MoinMoin.util import web
19 Object that turns Wiki markup into HTML.
21 All formatting commands can be parsed one line at a time, though
22 some state is carried over between lines.
24 Methods named like _*_repl() are responsible to handle the named regex
25 patterns defined in print_html().
33 PARENT_PREFIX = wikiutil.PARENT_PREFIX
34 punct_pattern = re.escape(u'''"\'}]|:,.)?!''')
35 attachment_schemas = ["attachment", "inline", "drawing", ]
36 url_schemas = ['http', 'https', 'ftp', 'wiki', 'mailto', 'nntp', 'news',
37 'telnet', 'file', 'irc', 'ircs',
38 'webcal', 'ed2k', 'xmpp', 'rootz',
40 url_pattern = u'|'.join(url_schemas + attachment_schemas)
43 word_rule = ur'(?:(?<![%(u)s%(l)s])|^)%(parent)s(?:%(subpages)s(?:[%(u)s][%(l)s]+){2,})+(?![%(u)s%(l)s]+)' % {
44 'u': config.chars_upper,
45 'l': config.chars_lower,
46 'subpages': wikiutil.CHILD_PREFIX + '?',
47 'parent': ur'(?:%s)?' % re.escape(PARENT_PREFIX),
49 url_rule = ur'%(url_guard)s(%(url)s)\:([^\s\<%(punct)s]|([%(punct)s][^\s\<%(punct)s]))+' % {
50 'url_guard': u'(^|(?<!\w))',
52 'punct': punct_pattern,
55 ol_rule = ur"^\s+(?:[0-9]+|[aAiI])\.(?:#\d+)?\s"
56 dl_rule = ur"^\s+.*?::\s"
58 config_smileys = dict([(key, None) for key in config.smileys])
60 # the big, fat, ugly one ;)
61 formatting_rules = ur"""(?P<ent_numeric>&#(\d{1,5}|x[0-9a-fA-F]+);)
62 (?:(?P<emph_ibb>'''''(?=[^']+'''))
63 (?P<emph_ibi>'''''(?=[^']+''))
64 (?P<emph_ib_or_bi>'{5}(?=[^']))
68 (?P<sub>,,[^,]{1,40},,)
69 (?P<tt>\{\{\{.*?\}\}\})
70 (?P<processor>(\{\{\{(#!.*|\s*$)))
71 (?P<pre>(\{\{\{ ?|\}\}\}))
72 (?P<small>(\~- ?|-\~))
73 (?P<big>(\~\+ ?|\+\~))
74 (?P<strike>(--\(|\)--))
77 (?P<macro>\[\[(%%(macronames)s)(?:\(.*?\))?\]\]))
81 (?P<li_none>^\s+\.\s*)
84 (?P<table>(?:\|\|)+(?:<[^>]*?>)?(?!\|? $))
85 (?P<heading>^\s*(?P<hmarker>=+)\s.*\s(?P=hmarker) $)
86 (?P<interwiki>[A-Z][a-zA-Z]+\:[^\s'\"\:\<\|]([^\s%(punct)s]|([%(punct)s][^\s%(punct)s]))+)
87 (?P<word>%(word_rule)s)
88 (?P<url_bracket>\[((%(url)s)\:|#|\:)[^\s\]]+(\s[^\]]+)?\])
90 (?P<email>[-\w._+]+\@[\w-]+(\.[\w-]+)+)
91 (?P<smiley>(?<=\s)(%(smiley)s)(?=\s))
92 (?P<smileyA>^(%(smiley)s)(?=\s))
93 (?P<ent_symbolic>&\w+;)
95 (?P<wikiname_bracket>\[".*?"\])
96 (?P<tt_bt>`.*?`)""" % {
99 'punct': punct_pattern,
102 'url_rule': url_rule,
103 'word_rule': word_rule,
104 'smiley': u'|'.join(map(re.escape, config_smileys.keys()))}
106 # Don't start p before these
107 no_new_p_before = ("heading rule table tableZ tr td "
108 "ul ol dl dt dd li li_none indent "
109 "macro processor pre")
110 no_new_p_before = no_new_p_before.split()
111 no_new_p_before = dict(zip(no_new_p_before, [1] * len(no_new_p_before)))
113 def __init__(self, raw, request, **kw):
115 self.request = request
116 self.form = request.form
117 self._ = request.getText
118 self.cfg = request.cfg
119 self.line_anchors = kw.get('line_anchors', True)
121 self.start_line = kw.get('start_line', 0)
128 self.in_list = 0 # between <ul/ol/dl> and </ul/ol/dl>
129 self.in_li = 0 # between <li> and </li>
130 self.in_dd = 0 # between <dd> and </dd>
134 self.is_small = False
135 self.inhibit_p = 0 # if set, do not auto-create a <p>aragraph
136 self.titles = request._page_headings
138 # holds the nesting level (in chars) of open lists
139 self.list_indents = []
142 self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(wikimacro.getNames(self.cfg))}
144 def _close_item(self, result):
145 #result.append("<!-- close item begin -->\n")
147 result.append(self.formatter.table(0))
151 if self.formatter.in_p:
152 result.append(self.formatter.paragraph(0))
153 result.append(self.formatter.listitem(0))
156 if self.formatter.in_p:
157 result.append(self.formatter.paragraph(0))
158 result.append(self.formatter.definition_desc(0))
159 #result.append("<!-- close item end -->\n")
162 def interwiki(self, url_and_text, **kw):
163 # TODO: maybe support [wiki:Page http://wherever/image.png] ?
164 if len(url_and_text) == 1:
165 url = url_and_text[0]
168 url, text = url_and_text
170 # keep track of whether this is a self-reference, so links
171 # are always shown even the page doesn't exist.
172 is_self_reference = 0
174 if url2.startswith('wiki:self:'):
175 url = url[10:] # remove "wiki:self:"
176 is_self_reference = 1
177 elif url2.startswith('wiki:'):
178 url = url[5:] # remove "wiki:"
180 tag, tail = wikiutil.split_wiki(url)
187 elif (url.startswith(wikiutil.CHILD_PREFIX) or # fancy link to subpage [wiki:/SubPage text]
188 is_self_reference or # [wiki:Self:LocalPage text] or [:LocalPage:text]
189 Page(self.request, url).exists()): # fancy link to local page [wiki:LocalPage text]
190 return self._word_repl(url, text)
192 wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, url)
193 href = wikiutil.join_wiki(wikiurl, wikitail)
195 # check for image URL, and possibly return IMG tag
196 if not kw.get('pretty_url', 0) and wikiutil.isPicture(wikitail):
197 return self.formatter.image(src=href)
201 return self._word_repl(wikitail)
203 return (self.formatter.interwikilink(1, tag, tail) +
204 self.formatter.text(text) +
205 self.formatter.interwikilink(0, tag, tail))
207 def attachment(self, url_and_text, **kw):
208 """ This gets called on attachment URLs.
211 if len(url_and_text) == 1:
212 url = url_and_text[0]
215 url, text = url_and_text
217 inline = url[0] == 'i'
218 drawing = url[0] == 'd'
219 url = url.split(":", 1)[1]
220 url = wikiutil.url_unquote(url)
223 from MoinMoin.action import AttachFile
225 return self.formatter.attachment_drawing(url, text)
227 # check for image URL, and possibly return IMG tag
228 # (images are always inlined, just like for other URLs)
229 if not kw.get('pretty_url', 0) and wikiutil.isPicture(url):
230 return self.formatter.attachment_image(url)
232 # inline the attachment
234 return self.formatter.attachment_inlined(url, text)
236 return self.formatter.attachment_link(url, text)
238 def _u_repl(self, word):
239 """Handle underline."""
240 self.is_u = not self.is_u
241 return self.formatter.underline(self.is_u)
243 def _strike_repl(self, word):
244 """Handle strikethrough."""
245 # XXX we don't really enforce the correct sequence --( ... )-- here
246 self.is_strike = not self.is_strike
247 return self.formatter.strike(self.is_strike)
249 def _small_repl(self, word):
251 if word.strip() == '~-' and self.is_small:
252 return self.formatter.text(word)
253 if word.strip() == '-~' and not self.is_small:
254 return self.formatter.text(word)
255 self.is_small = not self.is_small
256 return self.formatter.small(self.is_small)
258 def _big_repl(self, word):
260 if word.strip() == '~+' and self.is_big:
261 return self.formatter.text(word)
262 if word.strip() == '+~' and not self.is_big:
263 return self.formatter.text(word)
264 self.is_big = not self.is_big
265 return self.formatter.big(self.is_big)
267 def _emph_repl(self, word):
268 """Handle emphasis, i.e. '' and '''."""
269 ##print "#", self.is_b, self.is_em, "#"
271 self.is_b = not self.is_b
272 if self.is_em and self.is_b:
274 return self.formatter.strong(self.is_b)
276 self.is_em = not self.is_em
277 if self.is_em and self.is_b:
279 return self.formatter.emphasis(self.is_em)
281 def _emph_ibb_repl(self, word):
282 """Handle mixed emphasis, i.e. ''''' followed by '''."""
283 self.is_b = not self.is_b
284 self.is_em = not self.is_em
285 if self.is_em and self.is_b:
287 return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b)
289 def _emph_ibi_repl(self, word):
290 """Handle mixed emphasis, i.e. ''''' followed by ''."""
291 self.is_b = not self.is_b
292 self.is_em = not self.is_em
293 if self.is_em and self.is_b:
295 return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em)
297 def _emph_ib_or_bi_repl(self, word):
298 """Handle mixed emphasis, exactly five '''''."""
299 ##print "*", self.is_b, self.is_em, "*"
300 b_before_em = self.is_b > self.is_em > 0
301 self.is_b = not self.is_b
302 self.is_em = not self.is_em
304 return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em)
306 return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b)
309 def _sup_repl(self, word):
310 """Handle superscript."""
311 return self.formatter.sup(1) + \
312 self.formatter.text(word[1:-1]) + \
313 self.formatter.sup(0)
315 def _sub_repl(self, word):
316 """Handle subscript."""
317 return self.formatter.sub(1) + \
318 self.formatter.text(word[2:-2]) + \
319 self.formatter.sub(0)
322 def _rule_repl(self, word):
323 """Handle sequences of dashes."""
324 result = self._undent() + self._closeP()
326 result = result + self.formatter.rule()
328 # Create variable rule size 1 - 6. Actual size defined in css.
329 size = min(len(word), 10) - 4
330 result = result + self.formatter.rule(size)
334 def _word_repl(self, word, text=None):
335 """Handle WikiNames."""
337 # check for parent links
338 # !!! should use wikiutil.AbsPageName here, but setting `text`
339 # correctly prevents us from doing this for now
340 if word.startswith(wikiutil.PARENT_PREFIX):
343 word = '/'.join(filter(None, self.formatter.page.page_name.split('/')[:-1] + [word[wikiutil.PARENT_PREFIX_LEN:]]))
346 # if a simple, self-referencing link, emit it as plain text
347 if word == self.formatter.page.page_name:
348 return self.formatter.text(word)
350 if word.startswith(wikiutil.CHILD_PREFIX):
351 word = self.formatter.page.page_name + '/' + word[wikiutil.CHILD_PREFIX_LEN:]
354 parts = word.split("#", 1)
359 return (self.formatter.pagelink(1, word, anchor=anchor) +
360 self.formatter.text(text) +
361 self.formatter.pagelink(0, word))
363 def _notword_repl(self, word):
364 """Handle !NotWikiNames."""
365 return self.formatter.nowikiword(word[1:])
367 def _interwiki_repl(self, word):
368 """Handle InterWiki links."""
369 wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, word)
371 return self.formatter.text(word)
373 return self.interwiki(["wiki:" + word])
376 def _url_repl(self, word):
377 """Handle literal URLs including inline images."""
378 scheme = word.split(":", 1)[0]
381 return self.interwiki([word])
382 if scheme in self.attachment_schemas:
383 return self.attachment([word])
385 if wikiutil.isPicture(word):
386 word = wikiutil.mapURL(self.request, word)
387 # Get image name http://here.com/dir/image.gif -> image
388 name = word.split('/')[-1]
389 name = ''.join(name.split('.')[:-1])
390 return self.formatter.image(src=word, alt=name)
392 return (self.formatter.url(1, word, css=scheme) +
393 self.formatter.text(word) +
394 self.formatter.url(0))
397 def _wikiname_bracket_repl(self, word):
398 """Handle special-char wikinames."""
399 wikiname = word[2:-2]
401 return self._word_repl(wikiname)
403 return self.formatter.text(word)
406 def _url_bracket_repl(self, word):
407 """Handle bracketed URLs."""
409 # Local extended link?
411 words = word[2:-1].split(':', 1)
414 words[0] = 'wiki:Self:%s' % words[0]
415 return self.interwiki(words, pretty_url=1)
416 #return self._word_repl(words[0], words[1])
418 # Traditional split on space
419 words = word[1:-1].split(None, 1)
423 if words[0][0] == '#':
425 return (self.formatter.url(1, words[0]) +
426 self.formatter.text(words[1]) +
427 self.formatter.url(0))
429 scheme = words[0].split(":", 1)[0]
431 return self.interwiki(words, pretty_url=1)
432 if scheme in self.attachment_schemas:
433 return self.attachment(words, pretty_url=1)
435 if wikiutil.isPicture(words[1]) and re.match(self.url_rule, words[1]):
436 return (self.formatter.url(1, words[0], css='external') +
437 self.formatter.image(title=words[0], alt=words[0], src=words[1]) +
438 self.formatter.url(0))
440 return (self.formatter.url(1, words[0], css=scheme) +
441 self.formatter.text(words[1]) +
442 self.formatter.url(0))
445 def _email_repl(self, word):
446 """Handle email addresses (without a leading mailto:)."""
447 return (self.formatter.url(1, "mailto:" + word, css='mailto') +
448 self.formatter.text(word) +
449 self.formatter.url(0))
452 def _ent_repl(self, word):
453 """Handle SGML entities."""
454 return self.formatter.text(word)
455 #return {'&': '&',
459 def _ent_numeric_repl(self, word):
460 """Handle numeric (decimal and hexadecimal) SGML entities."""
461 return self.formatter.rawHTML(word)
463 def _ent_symbolic_repl(self, word):
464 """Handle symbolic SGML entities."""
465 return self.formatter.rawHTML(word)
467 def _indent_repl(self, match):
468 """Handle pure indentation (no - * 1. markup)."""
470 if not (self.in_li or self.in_dd):
471 self._close_item(result)
474 if self.line_was_empty and not self.first_list_item:
476 result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none"))
477 return ''.join(result)
479 def _li_none_repl(self, match):
480 """Handle type=none (" .") lists."""
482 self._close_item(result)
485 if self.line_was_empty and not self.first_list_item:
487 result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none"))
488 return ''.join(result)
490 def _li_repl(self, match):
491 """Handle bullet (" *") lists."""
493 self._close_item(result)
496 if self.line_was_empty and not self.first_list_item:
498 result.append(self.formatter.listitem(1, css_class=css_class))
499 return ''.join(result)
501 def _ol_repl(self, match):
502 """Handle numbered lists."""
503 return self._li_repl(match)
505 def _dl_repl(self, match):
506 """Handle definition lists."""
508 self._close_item(result)
511 self.formatter.definition_term(1),
512 self.formatter.text(match[1:-3].lstrip(' ')),
513 self.formatter.definition_term(0),
514 self.formatter.definition_desc(1),
516 return ''.join(result)
519 def _indent_level(self):
520 """Return current char-wise indent level."""
521 return len(self.list_indents) and self.list_indents[-1]
524 def _indent_to(self, new_level, list_type, numtype, numstart):
525 """Close and open lists."""
526 open = [] # don't make one out of these two statements!
529 if self._indent_level() != new_level and self.in_table:
530 close.append(self.formatter.table(0))
533 while self._indent_level() > new_level:
534 self._close_item(close)
535 if self.list_types[-1] == 'ol':
536 tag = self.formatter.number_list(0)
537 elif self.list_types[-1] == 'dl':
538 tag = self.formatter.definition_list(0)
540 tag = self.formatter.bullet_list(0)
543 del self.list_indents[-1]
544 del self.list_types[-1]
546 if self.list_types: # we are still in a list
547 if self.list_types[-1] == 'dl':
552 # Open new list, if necessary
553 if self._indent_level() < new_level:
554 self.list_indents.append(new_level)
555 self.list_types.append(list_type)
557 if self.formatter.in_p:
558 close.append(self.formatter.paragraph(0))
560 if list_type == 'ol':
561 tag = self.formatter.number_list(1, numtype, numstart)
562 elif list_type == 'dl':
563 tag = self.formatter.definition_list(1)
565 tag = self.formatter.bullet_list(1)
568 self.first_list_item = 1
572 # If list level changes, close an open table
573 if self.in_table and (open or close):
574 close[0:0] = [self.formatter.table(0)]
577 self.in_list = self.list_types != []
578 return ''.join(close) + ''.join(open)
582 """Close all open lists."""
584 #result.append("<!-- _undent start -->\n")
585 self._close_item(result)
586 for type in self.list_types[::-1]:
588 result.append(self.formatter.number_list(0))
590 result.append(self.formatter.definition_list(0))
592 result.append(self.formatter.bullet_list(0))
593 #result.append("<!-- _undent end -->\n")
594 self.list_indents = []
596 return ''.join(result)
599 def _tt_repl(self, word):
600 """Handle inline code."""
601 return self.formatter.code(1) + \
602 self.formatter.text(word[3:-3]) + \
603 self.formatter.code(0)
606 def _tt_bt_repl(self, word):
607 """Handle backticked inline code."""
608 # if len(word) == 2: return "" // removed for FCK editor
609 return self.formatter.code(1, css="backtick") + \
610 self.formatter.text(word[1:-1]) + \
611 self.formatter.code(0)
614 def _getTableAttrs(self, attrdef):
615 # skip "|" and initial "<"
616 while attrdef and attrdef[0] == "|":
617 attrdef = attrdef[1:]
618 if not attrdef or attrdef[0] != "<":
620 attrdef = attrdef[1:]
622 # extension for special table markup
623 def table_extension(key, parser, attrs, wiki_parser=self):
624 """ returns: tuple (found_flag, msg)
625 found_flag: whether we found something and were able to process it here
626 true for special stuff like 100% or - or #AABBCC
627 false for style xxx="yyy" attributes
628 msg: "" or an error msg
633 if key[0] in "0123456789":
634 token = parser.get_token()
637 msg = _('Expected "%(wanted)s" after "%(key)s", got "%(token)s"') % {
638 'wanted': wanted, 'key': key, 'token': token}
643 msg = _('Expected an integer "%(key)s" before "%(token)s"') % {
644 'key': key, 'token': token}
647 attrs['width'] = '"%s%%"' % key
649 arg = parser.get_token()
653 msg = _('Expected an integer "%(arg)s" after "%(key)s"') % {
654 'arg': arg, 'key': key}
657 attrs['colspan'] = '"%s"' % arg
659 arg = parser.get_token()
663 msg = _('Expected an integer "%(arg)s" after "%(key)s"') % {
664 'arg': arg, 'key': key}
667 attrs['rowspan'] = '"%s"' % arg
670 attrs['align'] = '"left"'
673 attrs['align'] = '"center"'
676 attrs['align'] = '"right"'
679 attrs['valign'] = '"top"'
682 attrs['valign'] = '"bottom"'
684 arg = parser.get_token()
686 if len(arg) != 6: raise ValueError
689 msg = _('Expected a color value "%(arg)s" after "%(key)s"') % {
690 'arg': arg, 'key': key}
693 attrs['bgcolor'] = '"#%s"' % arg
694 return found, self.formatter.rawHTML(msg)
697 attr, msg = wikiutil.parseAttributes(self.request, attrdef, '>', table_extension)
699 msg = '<strong class="highlight">%s</strong>' % msg
702 def _tableZ_repl(self, word):
703 """Handle table row end."""
706 # REMOVED: check for self.in_li, p should always close
707 if self.formatter.in_p:
708 result = self.formatter.paragraph(0)
709 result += self.formatter.table_cell(0) + self.formatter.table_row(0)
712 return self.formatter.text(word)
714 def _table_repl(self, word):
715 """Handle table cell separator."""
718 # check for attributes
719 attrs, attrerr = self._getTableAttrs(word)
721 # start the table row?
722 if self.table_rowstart:
723 self.table_rowstart = 0
724 result.append(self.formatter.table_row(1, attrs))
726 # Close table cell, first closing open p
727 # REMOVED check for self.in_li, paragraph should close always!
728 if self.formatter.in_p:
729 result.append(self.formatter.paragraph(0))
730 result.append(self.formatter.table_cell(0))
732 # check for adjacent cell markers
733 if word.count("|") > 2:
734 if not attrs.has_key('align') and \
735 not (attrs.has_key('style') and 'text-align' in attrs['style'].lower()):
736 # add center alignment if we don't have some alignment already
737 attrs['align'] = '"center"'
738 if not attrs.has_key('colspan'):
739 attrs['colspan'] = '"%d"' % (word.count("|")/2)
741 # return the complete cell markup
742 result.append(self.formatter.table_cell(1, attrs) + attrerr)
743 result.append(self._line_anchordef())
744 return ''.join(result)
746 return self.formatter.text(word)
749 def _heading_repl(self, word):
750 """Handle section headings."""
751 from MoinMoin.support.python_compatibility import hash_new
755 while h[level:level+1] == '=':
757 depth = min(5, level)
759 # this is needed for Included pages
760 # TODO but it might still result in unpredictable results
761 # when included the same page multiple times
762 title_text = h[level:-level].strip()
763 pntt = self.formatter.page.page_name + title_text
764 self.titles.setdefault(pntt, 0)
765 self.titles[pntt] += 1
768 if self.titles[pntt] > 1:
769 unique_id = '-%d' % self.titles[pntt]
770 result = self._closeP()
771 result += self.formatter.heading(1, depth, id="head-"+hash_new('sha1', pntt.encode(config.charset)).hexdigest()+unique_id)
773 return (result + self.formatter.text(title_text) +
774 self.formatter.heading(0, depth))
776 def _processor_repl(self, word):
777 """Handle processed code displays."""
778 if word[:3] == '{{{':
781 self.processor = None
782 self.processor_name = None
783 self.processor_is_parser = 0
784 s_word = word.strip()
786 # empty bang paths lead to a normal code display
787 # can be used to escape real, non-empty bang paths
790 return self._closeP() + self.formatter.preformatted(1)
791 elif s_word[:2] == '#!':
792 # First try to find a processor for this (will go away in 2.0)
793 processor_name = s_word[2:].split()[0]
794 self.setProcessor(processor_name)
797 self.processor_name = processor_name
799 self.colorize_lines = [word]
803 return self._closeP() + self.formatter.preformatted(1) + \
804 self.formatter.text(s_word + ' (-)')
809 def _pre_repl(self, word):
810 """Handle code displays."""
812 if word == '{{{' and not self.in_pre:
814 return self._closeP() + self.formatter.preformatted(self.in_pre)
815 elif word == '}}}' and self.in_pre:
818 return self.formatter.preformatted(self.in_pre)
819 return self.formatter.text(word)
822 def _smiley_repl(self, word):
823 """Handle smileys."""
824 return self.formatter.smiley(word)
826 _smileyA_repl = _smiley_repl
829 def _comment_repl(self, word):
830 # if we are in a paragraph, we must close it so that normal text following
831 # in the line below the comment will reopen a new paragraph.
832 if self.formatter.in_p:
833 self.formatter.paragraph(0)
834 self.line_is_empty = 1 # markup following comment lines treats them as if they were empty
835 return self.formatter.comment(word)
838 if self.formatter.in_p:
839 return self.formatter.paragraph(0)
842 def _macro_repl(self, word):
843 """Handle macros ([[macroname]])."""
844 macro_name = word[2:-2]
845 self.inhibit_p = 0 # 1 fixes UserPreferences, 0 fixes paragraph formatting for macros
847 # check for arguments
849 if macro_name.count("("):
850 macro_name, args = macro_name.split('(', 1)
853 # create macro instance
854 if self.macro is None:
855 self.macro = wikimacro.Macro(self)
856 return self.formatter.macro(self.macro, macro_name, args)
858 def scan(self, scan_re, line):
861 Append text before match, invoke replace() with match, and add text after match.
866 ###result.append(u'<span class="info">[scan: <tt>"%s"</tt>]</span>' % line)
868 for match in scan_re.finditer(line):
869 # Add text before the match
870 if lastpos < match.start():
872 ###result.append(u'<span class="info">[add text before match: <tt>"%s"</tt>]</span>' % line[lastpos:match.start()])
874 if not (self.inhibit_p or self.in_pre or self.formatter.in_p):
875 result.append(self.formatter.paragraph(1, css_class="line862"))
876 result.append(self.formatter.text(line[lastpos:match.start()]))
878 # Replace match with markup
879 if not (self.inhibit_p or self.in_pre or self.formatter.in_p or
880 self.in_table or self.in_list):
881 result.append(self.formatter.paragraph(1, css_class="line867"))
882 result.append(self.replace(match))
883 lastpos = match.end()
885 ###result.append('<span class="info">[no match, add rest: <tt>"%s"<tt>]</span>' % line[lastpos:])
887 # Add paragraph with the remainder of the line
888 if not (self.in_pre or self.in_li or self.in_dd or self.inhibit_p or
889 self.formatter.in_p) and lastpos < len(line):
890 result.append(self.formatter.paragraph(1, css_class="line874"))
891 result.append(self.formatter.text(line[lastpos:]))
892 return u''.join(result)
894 def replace(self, match):
895 """ Replace match using type name """
897 for type, hit in match.groupdict().items():
898 if hit is not None and type != "hmarker":
900 ###result.append(u'<span class="info">[replace: %s: "%s"]</span>' % (type, hit))
901 if self.in_pre and type not in ['pre', 'ent']:
902 return self.formatter.text(hit)
904 # Open p for certain types
905 if not (self.inhibit_p or self.formatter.in_p
906 or self.in_pre or (type in self.no_new_p_before)):
907 result.append(self.formatter.paragraph(1, css_class="line891"))
909 # Get replace method and replece hit
910 replace = getattr(self, '_' + type + '_repl')
911 result.append(replace(hit))
912 return ''.join(result)
914 # We should never get here
916 raise Exception("Can't handle match " + `match`
917 + "\n" + pprint.pformat(match.groupdict())
918 + "\n" + pprint.pformat(match.groups()) )
922 def _line_anchordef(self):
923 if self.line_anchors and not self.line_anchor_printed:
924 self.line_anchor_printed = 1
925 return self.formatter.line_anchordef(self.lineno)
929 def format(self, formatter):
930 """ For each line, scan through looking for magic
931 strings, outputting verbatim any intervening text.
933 self.formatter = formatter
934 self.hilite_re = self.formatter.page.hilite_re
936 # prepare regex patterns
937 rules = self.formatting_rules.replace('\n', '|')
938 if self.cfg.bang_meta:
939 rules = ur'(?P<notword>!%(word_rule)s)|%(rules)s' % {
940 'word_rule': self.word_rule,
943 self.request.clock.start('compile_huge_and_ugly')
944 scan_re = re.compile(rules, re.UNICODE)
945 number_re = re.compile(self.ol_rule, re.UNICODE)
946 term_re = re.compile(self.dl_rule, re.UNICODE)
947 indent_re = re.compile("^\s*", re.UNICODE)
948 eol_re = re.compile(r'\r?\n', re.UNICODE)
949 self.request.clock.stop('compile_huge_and_ugly')
951 # get text and replace TABs
952 rawtext = self.raw.expandtabs()
954 # go through the lines
955 self.lineno = self.start_line
956 self.lines = eol_re.split(rawtext)
957 self.line_is_empty = 0
959 self.in_processing_instructions = 1
962 for line in self.lines:
964 self.line_anchor_printed = 0
965 if not self.in_table:
966 self.request.write(self._line_anchordef())
967 self.table_rowstart = 1
968 self.line_was_empty = self.line_is_empty
969 self.line_is_empty = 0
970 self.first_list_item = 0
973 # ignore processing instructions
974 if self.in_processing_instructions:
976 for pi in ("##", "#format", "#refresh", "#redirect", "#deprecated",
977 "#pragma", "#form", "#acl", "#language"):
978 if line.lower().startswith(pi):
979 self.request.write(self.formatter.comment(line))
983 self.in_processing_instructions = 0
985 continue # do not parse this line
987 # TODO: move this into function
988 # still looking for processing instructions
989 # TODO: use strings for pre state, not numbers
991 self.processor = None
992 self.processor_is_parser = 0
994 if (line.strip()[:2] == "#!"):
995 processor_name = line.strip()[2:].split()[0]
996 self.setProcessor(processor_name)
1000 self.colorize_lines = [line]
1001 self.processor_name = processor_name
1004 self.request.write(self._closeP() +
1005 self.formatter.preformatted(1))
1007 if self.in_pre == 2:
1009 endpos = line.find("}}}")
1011 self.colorize_lines.append(line)
1014 self.colorize_lines.append(line[:endpos])
1016 # Close p before calling processor
1017 # TODO: do we really need this?
1018 self.request.write(self._closeP())
1019 res = self.formatter.processor(self.processor_name,
1020 self.colorize_lines,
1021 self.processor_is_parser)
1022 self.request.write(res)
1023 del self.colorize_lines
1025 self.processor = None
1027 # send rest of line through regex machinery
1028 line = line[endpos+3:]
1029 if not line.strip(): # just in the case "}}} " when we only have blanks left...
1032 # we don't have \n as whitespace any more
1033 # This is the space between lines we join to one paragraph
1036 # Paragraph break on empty lines
1037 if not line.strip():
1039 self.request.write(self.formatter.table(0))
1040 self.request.write(self._line_anchordef())
1042 # CHANGE: removed check for not self.list_types
1043 # p should close on every empty line
1044 if self.formatter.in_p:
1045 self.request.write(self.formatter.paragraph(0))
1046 self.line_is_empty = 1
1049 # Check indent level
1050 indent = indent_re.match(line)
1051 indlen = len(indent.group(0))
1056 match = number_re.match(line)
1058 numtype, numstart = match.group(0).strip().split('.')
1059 numtype = numtype[0]
1061 if numstart and numstart[0] == "#":
1062 numstart = int(numstart[1:])
1068 match = term_re.match(line)
1072 # output proper indentation tags
1073 self.request.write(self._indent_to(indlen, indtype, numtype, numstart))
1076 # TODO: move into function?
1077 if (not self.in_table and line[indlen:indlen + 2] == "||"
1078 and line[-3:] == "|| " and len(line) >= 5 + indlen):
1080 if self.list_types and not self.in_li:
1081 self.request.write(self.formatter.listitem(1, style="list-style-type:none"))
1082 ## CHANGE: no automatic p on li
1083 ##self.request.write(self.formatter.paragraph(1))
1086 # CHANGE: removed check for self.in_li
1087 # paragraph should end before table, always!
1088 if self.formatter.in_p:
1089 self.request.write(self.formatter.paragraph(0))
1090 attrs, attrerr = self._getTableAttrs(line[indlen+2:])
1091 self.request.write(self.formatter.table(1, attrs) + attrerr)
1092 self.in_table = True # self.lineno
1093 elif (self.in_table and not
1094 # intra-table comments should not break a table
1095 (line[:2] == "##" or
1096 line[indlen:indlen + 2] == "||" and
1097 line[-3:] == "|| " and
1098 len(line) >= 5 + indlen)):
1101 self.request.write(self.formatter.table(0))
1102 self.request.write(self._line_anchordef())
1105 # Scan line, format and write
1106 formatted_line = self.scan(scan_re, line)
1107 self.request.write(formatted_line)
1109 if self.in_pre == 3:
1110 self.request.write(self.formatter.linebreak())
1112 # Close code displays, paragraphs, tables and open lists
1113 self.request.write(self._undent())
1114 if self.in_pre: self.request.write(self.formatter.preformatted(0))
1115 if self.formatter.in_p: self.request.write(self.formatter.paragraph(0))
1116 if self.in_table: self.request.write(self.formatter.table(0))
1118 # --------------------------------------------------------------------
1121 def setProcessor(self, name):
1122 """ Set processer to either processor or parser named 'name' """
1123 cfg = self.request.cfg
1125 self.processor = wikiutil.importPlugin(cfg, "processor", name,
1127 self.processor_is_parser = 0
1128 except wikiutil.PluginMissingError:
1130 self.processor = wikiutil.importPlugin(cfg, "parser", name,
1132 self.processor_is_parser = 1
1133 except wikiutil.PluginMissingError:
1134 self.processor = None