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, want_unicode=True)
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."""
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-"+sha.new(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