Attachment 'FeatureRequests-PragmaCamelCase-text_moin_wiki.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - MoinMoin Wiki Markup Parser
4
5 @copyright: 2000-2002 Juergen Hermann <jh@web.de>,
6 2006-2008 MoinMoin:ThomasWaldmann,
7 2007 by MoinMoin:ReimarBauer
8 @license: GNU GPL, see COPYING for details.
9 """
10
11 import re
12
13 from MoinMoin import log
14 logging = log.getLogger(__name__)
15
16 from MoinMoin import config, wikiutil, macro
17 from MoinMoin.Page import Page
18 from MoinMoin.support.python_compatibility import rsplit, set
19
20 Dependencies = ['user'] # {{{#!wiki comment ... }}} has different output depending on the user's profile settings
21
22
23 _ = lambda x: x
24
25 class Parser:
26 """
27 Parse wiki format markup (and call the formatter to generate output).
28
29 All formatting commands can be parsed one line at a time, though
30 some state is carried over between lines.
31
32 Methods named like _*_repl() are responsible to handle the named regex patterns.
33 """
34
35 # allow caching
36 caching = 1
37 Dependencies = Dependencies
38 quickhelp = _(u"""\
39 Emphasis:: <<Verbatim('')>>''italics''<<Verbatim('')>>; <<Verbatim(''')>>'''bold'''<<Verbatim(''')>>; <<Verbatim(''''')>>'''''bold italics'''''<<Verbatim(''''')>>; <<Verbatim('')>>''mixed ''<<Verbatim(''')>>'''''bold'''<<Verbatim(''')>> and italics''<<Verbatim('')>>; <<Verbatim(----)>> horizontal rule.
40 Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====.
41 Lists:: space and one of: * bullets; 1., a., A., i., I. numbered items; 1.#n start numbering at n; space alone indents.
42 Links:: <<Verbatim(JoinCapitalizedWords)>>; <<Verbatim([[target|linktext]])>>.
43 Tables:: || cell text |||| cell text spanning 2 columns ||; no trailing white space allowed after tables or titles.
44
45 (!) For more help, see HelpOnEditing or SyntaxReference.
46 """)
47
48 # flag - automatic hyperlinking to CamelCase word
49 # Default is on
50 autocamellink = 1
51
52
53 # some common strings
54 CHILD_PREFIX = wikiutil.CHILD_PREFIX
55 CHILD_PREFIX_LEN = wikiutil.CHILD_PREFIX_LEN
56 PARENT_PREFIX = wikiutil.PARENT_PREFIX
57 PARENT_PREFIX_LEN = wikiutil.PARENT_PREFIX_LEN
58
59 punct_pattern = re.escape(u'''"\'}]|:,.)?!''')
60 url_scheme = u'|'.join(config.url_schemas)
61
62 # some common rules
63 url_rule = ur'''
64 (?:^|(?<=\W)) # require either beginning of line or some non-alphanum char (whitespace, punctuation) to the left
65 (?P<url_target> # capture whole url there
66 (?P<url_scheme>%(url_scheme)s) # some scheme
67 \:
68 \S+? # anything non-whitespace
69 )
70 (?:$|(?=\s|[%(punct)s]+(\s|$))) # require either end of line or some whitespace or some punctuation+blank/eol afterwards
71 ''' % {
72 'url_scheme': url_scheme,
73 'punct': punct_pattern,
74 }
75
76 # this is for a free (non-bracketed) interwiki link - to avoid false positives,
77 # we are rather restrictive here (same as in moin 1.5: require that the
78 # interwiki_wiki name starts with an uppercase letter A-Z. Later, the code
79 # also checks whether the wiki name is in the interwiki map (if not, it renders
80 # normal text, no link):
81 interwiki_rule = ur'''
82 (?:^|(?<=\W)) # require either beginning of line or some non-alphanum char (whitespace, punctuation) to the left
83 (?P<interwiki_wiki>[A-Z][a-zA-Z]+) # interwiki wiki name
84 \:
85 (?P<interwiki_page> # interwiki page name
86 (?=[^ ]*[%(u)s%(l)s0..9][^ ]*\ ) # make sure there is something non-blank with at least one alphanum letter following
87 [^\s%(punct)s]+ # we take all until we hit some blank or punctuation char ...
88 )
89 ''' % {
90 'u': config.chars_upper,
91 'l': config.chars_lower,
92 'punct': punct_pattern,
93 }
94
95 # BE CAREFUL: if you do changes to word_rule, consider doing them also to word_rule_js (see below)
96 word_rule = ur'''
97 (?:
98 (?<![%(u)s%(l)s/]) # require anything not upper/lower/slash before
99 |
100 ^ # ... or beginning of line
101 )
102 (?P<word_bang>\!)? # configurable: avoid getting CamelCase rendered as link
103 (?P<word_name>
104 (?:
105 (%(parent)s)* # there might be either ../ parent prefix(es)
106 |
107 ((?<!%(child)s)%(child)s)? # or maybe a single / child prefix (but not if we already had it before)
108 )
109 (
110 ((?<!%(child)s)%(child)s)? # there might be / child prefix (but not if we already had it before)
111 (?:[%(u)s][%(l)s]+){2,} # at least 2 upper>lower transitions make CamelCase
112 )+ # we can have MainPage/SubPage/SubSubPage ...
113 (?:
114 \# # anchor separator TODO check if this does not make trouble at places where word_rule is used
115 (?P<word_anchor>\S+) # some anchor name
116 )?
117 )
118 (?:
119 (?![%(u)s%(l)s/]) # require anything not upper/lower/slash following
120 |
121 $ # ... or end of line
122 )
123 ''' % {
124 'u': config.chars_upper,
125 'l': config.chars_lower,
126 'child': re.escape(CHILD_PREFIX),
127 'parent': re.escape(PARENT_PREFIX),
128 }
129 # simplified word_rule for FCKeditor's "unlink" plugin (puts a ! in front of a WikiName if WikiName matches word_rule_js),
130 # because JavaScript can not use group names and verbose regular expressions!
131 word_rule_js = (
132 ur'''(?:(?<![%(u)s%(l)s/])|^)'''
133 ur'''(?:'''
134 ur'''(?:(%(parent)s)*|((?<!%(child)s)%(child)s)?)'''
135 ur'''(((?<!%(child)s)%(child)s)?(?:[%(u)s][%(l)s]+){2,})+'''
136 ur'''(?:\#(?:\S+))?'''
137 ur''')'''
138 ur'''(?:(?![%(u)s%(l)s/])|$)'''
139 ) % {
140 'u': config.chars_upper,
141 'l': config.chars_lower,
142 'child': re.escape(CHILD_PREFIX),
143 'parent': re.escape(PARENT_PREFIX),
144 }
145
146 # link targets:
147 extern_rule = r'(?P<extern_addr>(?P<extern_scheme>%s)\:.*)' % url_scheme
148 attach_rule = r'(?P<attach_scheme>attachment|drawing)\:(?P<attach_addr>.*)'
149 page_rule = r'(?P<page_name>.*)'
150
151 link_target_rules = r'|'.join([
152 extern_rule,
153 attach_rule,
154 page_rule,
155 ])
156 link_target_re = re.compile(link_target_rules, re.VERBOSE|re.UNICODE)
157
158 link_rule = r"""
159 (?P<link>
160 \[\[ # link target
161 \s* # strip space
162 (?P<link_target>[^|]+?)
163 \s* # strip space
164 (
165 \| # link description
166 \s* # strip space
167 (?P<link_desc>
168 (?: # 1. we have either a transclusion here (usually a image)
169 \{\{
170 \s*[^|]+?\s* # usually image target (strip space)
171 (\|\s*[^|]*?\s* # usually image alt text (optional, strip space)
172 (\|\s*[^|]*?\s* # transclusion parameters (usually key="value" format, optional, strip space)
173 )?
174 )?
175 \}\}
176 )
177 |
178 (?: # 2. or we have simple text here.
179 [^|]+?
180 )
181 )?
182 \s* # strip space
183 (
184 \| # link parameters
185 \s* # strip space
186 (?P<link_params>[^|]+?)?
187 \s* # strip space
188 )?
189 )?
190 \]\]
191 )
192 """
193
194 transclude_rule = r"""
195 (?P<transclude>
196 \{\{
197 \s*(?P<transclude_target>[^|]+?)\s* # usually image target (strip space)
198 (\|\s*(?P<transclude_desc>[^|]+?)?\s* # usually image alt text (optional, strip space)
199 (\|\s*(?P<transclude_params>[^|]+?)?\s* # transclusion parameters (usually key="value" format, optional, strip space)
200 )?
201 )?
202 \}\}
203 )
204 """
205 text_rule = r"""
206 (?P<simple_text>
207 [^|]+ # some text (not empty, does not contain separator)
208 )
209 """
210 # link descriptions:
211 link_desc_rules = r'|'.join([
212 transclude_rule,
213 text_rule,
214 ])
215 link_desc_re = re.compile(link_desc_rules, re.VERBOSE|re.UNICODE)
216
217 # transclude descriptions:
218 transclude_desc_rules = r'|'.join([
219 text_rule,
220 ])
221 transclude_desc_re = re.compile(transclude_desc_rules, re.VERBOSE|re.UNICODE)
222
223 # lists:
224 ol_rule = ur"""
225 ^\s+ # indentation
226 (?:[0-9]+|[aAiI])\. # arabic, alpha, roman counting
227 (?:\#\d+)? # optional start number
228 \s # require one blank afterwards
229 """
230 ol_re = re.compile(ol_rule, re.VERBOSE|re.UNICODE)
231
232 dl_rule = ur"""
233 ^\s+ # indentation
234 .*?:: # definition term::
235 \s # require on blank afterwards
236 """
237 dl_re = re.compile(dl_rule, re.VERBOSE|re.UNICODE)
238
239 # others
240 indent_re = re.compile(ur"^\s*", re.UNICODE)
241 eol_re = re.compile(r'\r?\n', re.UNICODE)
242
243 # this is used inside parser/pre sections (we just want to know when it's over):
244 parser_unique = u''
245 parser_scan_rule = ur"""
246 (?P<parser_end>
247 %s\}\}\} # in parser/pre, we only look for the end of the parser/pre
248 )
249 """
250
251
252 # the big, fat, less ugly one ;)
253 # please be very careful: blanks and # must be escaped with \ !
254 scan_rules = ur"""
255 (?P<emph_ibb>
256 '''''(?=[^']+''') # italic on, bold on, ..., bold off
257 )|(?P<emph_ibi>
258 '''''(?=[^']+'') # italic on, bold on, ..., italic off
259 )|(?P<emph_ib_or_bi>
260 '{5}(?=[^']) # italic and bold or bold and italic
261 )|(?P<emph>
262 '{2,3} # italic or bold
263 )|(?P<u>
264 __ # underline
265 )|(?P<small>
266 (
267 (?P<small_on>\~-\ ?) # small on (we eat a trailing blank if it is there)
268 |
269 (?P<small_off>-\~) # small off
270 )
271 )|(?P<big>
272 (
273 (?P<big_on>\~\+\ ?) # big on (eat trailing blank)
274 |
275 (?P<big_off>\+\~) # big off
276 )
277 )|(?P<strike>
278 (
279 (?P<strike_on>--\() # strike-through on
280 |
281 (?P<strike_off>\)--) # strike-through off
282 )
283 )|(?P<remark>
284 (
285 (^|(?<=\s)) # we require either beginning of line or some whitespace before a remark begin
286 (?P<remark_on>/\*\s) # inline remark on (require and eat whitespace after it)
287 )
288 |
289 (
290 (?P<remark_off>\s\*/) # off (require and eat whitespace before it)
291 (?=\s) # we require some whitespace after a remark end
292 )
293 )|(?P<sup>
294 \^ # superscript on
295 (?P<sup_text>.*?) # capture the text
296 \^ # off
297 )|(?P<sub>
298 ,, # subscript on
299 (?P<sub_text>.*?) # capture the text
300 ,, # off
301 )|(?P<tt>
302 \{\{\{ # teletype on
303 (?P<tt_text>.*?) # capture the text
304 \}\}\} # off
305 )|(?P<tt_bt>
306 ` # teletype (using a backtick) on
307 (?P<tt_bt_text>.*?) # capture the text
308 ` # off
309 )|(?P<interwiki>
310 %(interwiki_rule)s # OtherWiki:PageName
311 )|(?P<word> # must come AFTER interwiki rule!
312 %(word_rule)s # CamelCase wiki words
313 )|
314 %(link_rule)s
315 |
316 %(transclude_rule)s
317 |(?P<url>
318 %(url_rule)s
319 )|(?P<email>
320 [-\w._+]+ # name
321 \@ # at
322 [\w-]+(\.[\w-]+)+ # server/domain
323 )|(?P<smiley>
324 (^|(?<=\s)) # we require either beginning of line or some space before a smiley
325 (%(smiley)s) # one of the smileys
326 (?=\s) # we require some space after the smiley
327 )|(?P<macro>
328 <<
329 (?P<macro_name>\w+) # name of the macro
330 (?:\((?P<macro_args>.*?)\))? # optionally macro arguments
331 >>
332 )|(?P<heading>
333 ^(?P<hmarker>=+)\s+ # some === at beginning of line, eat trailing blanks
334 (?P<heading_text>.*?) # capture heading text
335 \s+(?P=hmarker)\s$ # some === at end of line (matching amount as we have seen), eat blanks
336 )|(?P<parser>
337 \{\{\{ # parser on
338 (?P<parser_unique>(\{*|\w*)) # either some more {{{{ or some chars to solve the nesting problem
339 (?P<parser_line>
340 (
341 \#! # hash bang
342 (?P<parser_name>\w*) # we have a parser name (can be empty) directly following the {{{
343 (
344 \s+ # some space ...
345 (?P<parser_args>.+?) # followed by parser args
346 )? # parser args are optional
347 \s* # followed by whitespace (eat it) until EOL
348 )
349 |
350 (?P<parser_nothing>\s*) # no parser name, only whitespace up to EOL (eat it)
351 )$
352 # "parser off" detection is done with parser_scan_rule!
353 )|(?P<comment>
354 ^\#\#.*$ # src code comment, rest of line
355 )|(?P<ol>
356 %(ol_rule)s # ordered list
357 )|(?P<dl>
358 %(dl_rule)s # definition list
359 )|(?P<li>
360 ^\s+\*\s* # unordered list
361 )|(?P<li_none>
362 ^\s+\.\s* # unordered list, no bullets
363 )|(?P<indent>
364 ^\s+ # indented by some spaces
365 )|(?P<tableZ>
366 \|\|\ $ # the right end of a table row
367 )|(?P<table>
368 (?:\|\|)+(?:<(?!<)[^>]*?>)?(?!\|?\s$) # a table
369 )|(?P<rule>
370 -{4,} # hor. rule, min. 4 -
371 )|(?P<entity>
372 &(
373 ([a-zA-Z]+) # symbolic entity, like ü
374 |
375 (\#(\d{1,5}|x[0-9a-fA-F]+)) # numeric entities, like * or B
376 );
377 )|(?P<sgml_entity> # must come AFTER entity rule!
378 [<>&] # needs special treatment for html/xml
379 )""" % {
380 'url_scheme': url_scheme,
381 'url_rule': url_rule,
382 'punct': punct_pattern,
383 'ol_rule': ol_rule,
384 'dl_rule': dl_rule,
385 'interwiki_rule': interwiki_rule,
386 'word_rule': word_rule,
387 'link_rule': link_rule,
388 'transclude_rule': transclude_rule,
389 'u': config.chars_upper,
390 'l': config.chars_lower,
391 'smiley': u'|'.join([re.escape(s) for s in config.smileys])}
392 scan_re = re.compile(scan_rules, re.UNICODE|re.VERBOSE)
393
394 # Don't start p before these
395 no_new_p_before = ("heading rule table tableZ tr td "
396 "ul ol dl dt dd li li_none indent "
397 "macro parser")
398 no_new_p_before = no_new_p_before.split()
399 no_new_p_before = dict(zip(no_new_p_before, [1] * len(no_new_p_before)))
400
401 def __init__(self, raw, request, **kw):
402 self.raw = raw
403 self.request = request
404 self.form = request.form # Macro object uses this
405 self._ = request.getText
406 self.cfg = request.cfg
407 self.line_anchors = kw.get('line_anchors', True)
408 self.start_line = kw.get('start_line', 0)
409 self.macro = None
410
411 if self.request.getPragma('camel','') in ['off']:
412 self.autocamellink = 0
413
414
415 # currently, there is only a single, optional argument to this parser and
416 # (when given), it is used as class(es) for a div wrapping the formatter output
417 # either use a single class like "comment" or multiple like "comment/red/dotted"
418 self.wrapping_div_class = kw.get('format_args', '').strip().replace('/', ' ')
419
420 self.is_em = 0 # must be int
421 self.is_b = 0 # must be int
422 self.is_u = False
423 self.is_strike = False
424 self.is_big = False
425 self.is_small = False
426 self.is_remark = False
427
428 self.lineno = 0
429 self.in_list = 0 # between <ul/ol/dl> and </ul/ol/dl>
430 self.in_li = 0 # between <li> and </li>
431 self.in_dd = 0 # between <dd> and </dd>
432
433 # states of the parser concerning being inside/outside of some "pre" section:
434 # None == we are not in any kind of pre section (was: 0)
435 # 'search_parser' == we didn't get a parser yet, still searching for it (was: 1)
436 # 'found_parser' == we found a valid parser (was: 2)
437 self.in_pre = None
438
439 self.in_table = 0
440 self.inhibit_p = 0 # if set, do not auto-create a <p>aragraph
441
442 # holds the nesting level (in chars) of open lists
443 self.list_indents = []
444 self.list_types = []
445
446 def _close_item(self, result):
447 #result.append("<!-- close item begin -->\n")
448 if self.in_table:
449 result.append(self.formatter.table(0))
450 self.in_table = 0
451 if self.in_li:
452 self.in_li = 0
453 if self.formatter.in_p:
454 result.append(self.formatter.paragraph(0))
455 result.append(self.formatter.listitem(0))
456 if self.in_dd:
457 self.in_dd = 0
458 if self.formatter.in_p:
459 result.append(self.formatter.paragraph(0))
460 result.append(self.formatter.definition_desc(0))
461 #result.append("<!-- close item end -->\n")
462
463 def _u_repl(self, word, groups):
464 """Handle underline."""
465 self.is_u = not self.is_u
466 return self.formatter.underline(self.is_u)
467
468 def _remark_repl(self, word, groups):
469 """Handle remarks."""
470 on = groups.get('remark_on')
471 if on and self.is_remark:
472 return self.formatter.text(word)
473 off = groups.get('remark_off')
474 if off and not self.is_remark:
475 return self.formatter.text(word)
476 self.is_remark = not self.is_remark
477 return self.formatter.span(self.is_remark, css_class='comment')
478 _remark_on_repl = _remark_repl
479 _remark_off_repl = _remark_repl
480
481 def _strike_repl(self, word, groups):
482 """Handle strikethrough."""
483 on = groups.get('strike_on')
484 if on and self.is_strike:
485 return self.formatter.text(word)
486 off = groups.get('strike_off')
487 if off and not self.is_strike:
488 return self.formatter.text(word)
489 self.is_strike = not self.is_strike
490 return self.formatter.strike(self.is_strike)
491 _strike_on_repl = _strike_repl
492 _strike_off_repl = _strike_repl
493
494 def _small_repl(self, word, groups):
495 """Handle small."""
496 on = groups.get('small_on')
497 if on and self.is_small:
498 return self.formatter.text(word)
499 off = groups.get('small_off')
500 if off and not self.is_small:
501 return self.formatter.text(word)
502 self.is_small = not self.is_small
503 return self.formatter.small(self.is_small)
504 _small_on_repl = _small_repl
505 _small_off_repl = _small_repl
506
507 def _big_repl(self, word, groups):
508 """Handle big."""
509 on = groups.get('big_on')
510 if on and self.is_big:
511 return self.formatter.text(word)
512 off = groups.get('big_off')
513 if off and not self.is_big:
514 return self.formatter.text(word)
515 self.is_big = not self.is_big
516 return self.formatter.big(self.is_big)
517 _big_on_repl = _big_repl
518 _big_off_repl = _big_repl
519
520 def _emph_repl(self, word, groups):
521 """Handle emphasis, i.e. '' and '''."""
522 if len(word) == 3:
523 self.is_b = not self.is_b
524 if self.is_em and self.is_b:
525 self.is_b = 2
526 return self.formatter.strong(self.is_b)
527 else:
528 self.is_em = not self.is_em
529 if self.is_em and self.is_b:
530 self.is_em = 2
531 return self.formatter.emphasis(self.is_em)
532
533 def _emph_ibb_repl(self, word, groups):
534 """Handle mixed emphasis, i.e. ''''' followed by '''."""
535 self.is_b = not self.is_b
536 self.is_em = not self.is_em
537 if self.is_em and self.is_b:
538 self.is_b = 2
539 return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b)
540
541 def _emph_ibi_repl(self, word, groups):
542 """Handle mixed emphasis, i.e. ''''' followed by ''."""
543 self.is_b = not self.is_b
544 self.is_em = not self.is_em
545 if self.is_em and self.is_b:
546 self.is_em = 2
547 return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em)
548
549 def _emph_ib_or_bi_repl(self, word, groups):
550 """Handle mixed emphasis, exactly five '''''."""
551 b_before_em = self.is_b > self.is_em > 0
552 self.is_b = not self.is_b
553 self.is_em = not self.is_em
554 if b_before_em:
555 return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em)
556 else:
557 return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b)
558
559 def _sup_repl(self, word, groups):
560 """Handle superscript."""
561 text = groups.get('sup_text', '')
562 return (self.formatter.sup(1) +
563 self.formatter.text(text) +
564 self.formatter.sup(0))
565 _sup_text_repl = _sup_repl
566
567 def _sub_repl(self, word, groups):
568 """Handle subscript."""
569 text = groups.get('sub_text', '')
570 return (self.formatter.sub(1) +
571 self.formatter.text(text) +
572 self.formatter.sub(0))
573 _sub_text_repl = _sub_repl
574
575 def _tt_repl(self, word, groups):
576 """Handle inline code."""
577 tt_text = groups.get('tt_text', '')
578 return (self.formatter.code(1) +
579 self.formatter.text(tt_text) +
580 self.formatter.code(0))
581 _tt_text_repl = _tt_repl
582
583 def _tt_bt_repl(self, word, groups):
584 """Handle backticked inline code."""
585 tt_bt_text = groups.get('tt_bt_text', '')
586 return (self.formatter.code(1, css="backtick") +
587 self.formatter.text(tt_bt_text) +
588 self.formatter.code(0))
589 _tt_bt_text_repl = _tt_bt_repl
590
591 def _rule_repl(self, word, groups):
592 """Handle sequences of dashes."""
593 result = self._undent() + self._closeP()
594 if len(word) <= 4:
595 result += self.formatter.rule()
596 else:
597 # Create variable rule size 1 - 6. Actual size defined in css.
598 size = min(len(word), 10) - 4
599 result += self.formatter.rule(size)
600 return result
601
602 def _interwiki_repl(self, word, groups):
603 """Handle InterWiki links."""
604 wiki = groups.get('interwiki_wiki')
605 page = groups.get('interwiki_page')
606
607 wikitag_bad = wikiutil.resolve_interwiki(self.request, wiki, page)[3]
608 if wikitag_bad:
609 text = groups.get('interwiki')
610 return self.formatter.text(text)
611 else:
612 return (self.formatter.interwikilink(1, wiki, page) +
613 self.formatter.text(page) +
614 self.formatter.interwikilink(0, wiki, page))
615 _interwiki_wiki_repl = _interwiki_repl
616 _interwiki_page_repl = _interwiki_repl
617
618 def _word_repl(self, word, groups):
619 """Handle WikiNames."""
620 bang = ''
621 bang_present = groups.get('word_bang')
622 if not bang_present:
623 bang_present = (self.autocamellink == 0)
624 if bang_present:
625 if self.cfg.bang_meta:
626 # handle !NotWikiNames
627 return self.formatter.nowikiword(word)
628 else:
629 bang = self.formatter.text('!')
630 name = groups.get('word_name')
631 current_page = self.formatter.page.page_name
632 abs_name = wikiutil.AbsPageName(current_page, name)
633 # if a simple, self-referencing link, emit it as plain text
634 if abs_name == current_page:
635 return self.formatter.text(word)
636 else:
637 # handle anchors
638 try:
639 abs_name, anchor = rsplit(abs_name, "#", 1)
640 except ValueError:
641 anchor = ""
642 return (bang +
643 self.formatter.pagelink(1, abs_name, anchor=anchor) +
644 self.formatter.text(word) +
645 self.formatter.pagelink(0, abs_name))
646 _word_bang_repl = _word_repl
647 _word_name_repl = _word_repl
648 _word_anchor_repl = _word_repl
649
650 def _url_repl(self, word, groups):
651 """Handle literal URLs."""
652 scheme = groups.get('url_scheme', 'http')
653 target = groups.get('url_target', '')
654 return (self.formatter.url(1, target, css=scheme) +
655 self.formatter.text(target) +
656 self.formatter.url(0))
657 _url_target_repl = _url_repl
658 _url_scheme_repl = _url_repl
659
660 def _transclude_description(self, desc, default_text=''):
661 """ parse a string <desc> valid as transclude description (text, ...)
662 and return the description.
663
664 We do NOT use wikiutil.escape here because it is html specific (the
665 html formatter, if used, does this for all html attributes).
666
667 We do NOT call formatter.text here because it sometimes is just used
668 for some alt and/or title attribute, but not emitted as text.
669
670 @param desc: the transclude description to parse
671 @param default_text: use this text if parsing desc returns nothing.
672 """
673 m = self.transclude_desc_re.match(desc)
674 if m:
675 if m.group('simple_text'):
676 desc = m.group('simple_text')
677 else:
678 desc = default_text
679 return desc
680
681 def _get_params(self, params, tag_attrs=None, acceptable_attrs=None, query_args=None):
682 """ parse the parameters of link/transclusion markup,
683 defaults can be a dict with some default key/values
684 that will be in the result as given, unless overriden
685 by the params.
686 """
687 if tag_attrs is None:
688 tag_attrs = {}
689 if query_args is None:
690 query_args = {}
691 if params:
692 fixed, kw, trailing = wikiutil.parse_quoted_separated(params)
693 # we ignore fixed and trailing args and only use kw args:
694 if acceptable_attrs is None:
695 acceptable_attrs = []
696 for key, val in kw.items():
697 # wikiutil.escape for key/val must be done by (html) formatter!
698 if key in acceptable_attrs:
699 # tag attributes must be string type
700 tag_attrs[str(key)] = val
701 elif key.startswith('&'):
702 key = key[1:]
703 query_args[key] = val
704 return tag_attrs, query_args
705
706 def _transclude_repl(self, word, groups):
707 """Handles transcluding content, usually embedding images."""
708 target = groups.get('transclude_target', '')
709 target = wikiutil.url_unquote(target, want_unicode=True)
710 desc = groups.get('transclude_desc', '') or ''
711 params = groups.get('transclude_params', u'') or u''
712 acceptable_attrs_img = ['class', 'title', 'longdesc', 'width', 'height', 'align', ] # no style because of JS
713 acceptable_attrs_object = ['class', 'title', 'width', 'height', # no style because of JS
714 'type', 'standby', ] # we maybe need a hack for <PARAM> here
715 m = self.link_target_re.match(target)
716 if m:
717 if m.group('extern_addr'):
718 # currently only supports ext. image inclusion
719 target = m.group('extern_addr')
720 desc = self._transclude_description(desc, target)
721 tag_attrs, query_args = self._get_params(params,
722 tag_attrs={'class': 'external_image',
723 'alt': desc,
724 'title': desc, },
725 acceptable_attrs=acceptable_attrs_img)
726 return self.formatter.image(src=target, **tag_attrs)
727 # FF2 has a bug with target mimetype detection, it looks at the url path
728 # and expects to find some "filename extension" there (like .png) and this
729 # (not the response http headers) will set the default content-type of
730 # the object. This will often work for staticly served files, but
731 # fails for MoinMoin attachments (they don't have the filename.ext in the
732 # path, but in the query string). FF3 seems to have this bug fixed, opera 9.2
733 # also works.
734 #return (self.formatter.transclusion(1, data=target) +
735 # desc +
736 # self.formatter.transclusion(0))
737
738 elif m.group('attach_scheme'):
739 scheme = m.group('attach_scheme')
740 url = wikiutil.url_unquote(m.group('attach_addr'), want_unicode=True)
741 if scheme == 'attachment':
742 mt = wikiutil.MimeType(filename=url)
743 if mt.major == 'text':
744 desc = self._transclude_description(desc, url)
745 return self.formatter.attachment_inlined(url, desc)
746 # destinguishs if browser need a plugin in place
747 elif mt.major == 'image' and mt.minor in config.browser_supported_images:
748 desc = self._transclude_description(desc, url)
749 tag_attrs, query_args = self._get_params(params,
750 tag_attrs={'alt': desc,
751 'title': desc, },
752 acceptable_attrs=acceptable_attrs_img)
753 return self.formatter.attachment_image(url, **tag_attrs)
754 else:
755 from MoinMoin.action import AttachFile
756 pagename = self.formatter.page.page_name
757 if AttachFile.exists(self.request, pagename, url):
758 href = AttachFile.getAttachUrl(pagename, url, self.request, escaped=0)
759 tag_attrs, query_args = self._get_params(params,
760 tag_attrs={'title': desc, },
761 acceptable_attrs=acceptable_attrs_object)
762 return (self.formatter.transclusion(1, data=href, type=mt.spoil(), **tag_attrs) +
763 self.formatter.text(self._transclude_description(desc, url)) +
764 self.formatter.transclusion(0))
765 else:
766 return (self.formatter.attachment_link(1, url) +
767 self.formatter.text(self._transclude_description(desc, url)) +
768 self.formatter.attachment_link(0))
769
770 #NOT USED CURRENTLY:
771
772 # use EmbedObject for other mimetypes
773 if mt is not None:
774 from MoinMoin import macro
775 macro.request = self.request
776 macro.formatter = self.request.html_formatter
777 p = Parser("##\n", request)
778 m = macro.Macro(p)
779 pagename = self.formatter.page.page_name
780 return m.execute('EmbedObject', u'target=%s' % url)
781 elif scheme == 'drawing':
782 desc = self._transclude_description(desc, url)
783 return self.formatter.attachment_drawing(url, desc)
784
785 elif m.group('page_name'):
786 # experimental client side transclusion
787 page_name_all = m.group('page_name')
788 if ':' in page_name_all:
789 wiki_name, page_name = page_name_all.split(':', 1)
790 wikitag, wikiurl, wikitail, err = wikiutil.resolve_interwiki(self.request, wiki_name, page_name)
791 else:
792 err = True
793 if err: # not a interwiki link / not in interwiki map
794 tag_attrs, query_args = self._get_params(params,
795 tag_attrs={'type': 'text/html',
796 'width': '100%', },
797 acceptable_attrs=acceptable_attrs_object)
798 if 'action' not in query_args:
799 query_args['action'] = 'content'
800 url = Page(self.request, page_name_all).url(self.request, querystr=query_args)
801 return (self.formatter.transclusion(1, data=url, **tag_attrs) +
802 self.formatter.text(self._transclude_description(desc, page_name_all)) +
803 self.formatter.transclusion(0))
804 #return u"Error: <<Include(%s,%s)>> emulation missing..." % (page_name, args)
805 else: # looks like a valid interwiki link
806 url = wikiutil.join_wiki(wikiurl, wikitail)
807 tag_attrs, query_args = self._get_params(params,
808 tag_attrs={'type': 'text/html',
809 'width': '100%', },
810 acceptable_attrs=acceptable_attrs_object)
811 if 'action' not in query_args:
812 query_args['action'] = 'content' # XXX moin specific
813 url += '?%s' % wikiutil.makeQueryString(query_args)
814 return (self.formatter.transclusion(1, data=url, **tag_attrs) +
815 self.formatter.text(self._transclude_description(desc, page_name)) +
816 self.formatter.transclusion(0))
817 #return u"Error: <<RemoteInclude(%s:%s,%s)>> still missing." % (wiki_name, page_name, args)
818
819 else:
820 desc = self._transclude_description(desc, target)
821 return self.formatter.text('{{%s|%s|%s}}' % (target, desc, params))
822 return word +'???'
823 _transclude_target_repl = _transclude_repl
824 _transclude_desc_repl = _transclude_repl
825 _transclude_params_repl = _transclude_repl
826
827 def _link_description(self, desc, target='', default_text=''):
828 """ parse a string <desc> valid as link description (text, transclusion, ...)
829 and return formatted content.
830
831 @param desc: the link description to parse
832 @param default_text: use this text (formatted as text) if parsing
833 desc returns nothing.
834 @param target: target of the link (as readable markup) - used for
835 transcluded image's description
836 """
837 m = self.link_desc_re.match(desc)
838 if m:
839 if m.group('simple_text'):
840 desc = m.group('simple_text')
841 desc = self.formatter.text(desc)
842 elif m.group('transclude'):
843 groupdict = m.groupdict()
844 if groupdict.get('transclude_desc') is None:
845 # if transcluded obj (image) has no description, use target for it
846 groupdict['transclude_desc'] = target
847 desc = m.group('transclude')
848 desc = self._transclude_repl(desc, groupdict)
849 else:
850 desc = default_text
851 if desc:
852 desc = self.formatter.text(desc)
853 return desc
854
855 def _link_repl(self, word, groups):
856 """Handle [[target|text]] links."""
857 target = groups.get('link_target', '')
858 desc = groups.get('link_desc', '') or ''
859 params = groups.get('link_params', u'') or u''
860 acceptable_attrs = ['class', 'title', 'target', 'accesskey', ] # no style because of JS
861 mt = self.link_target_re.match(target)
862 if mt:
863 if mt.group('page_name'):
864 page_name_and_anchor = mt.group('page_name')
865 if ':' in page_name_and_anchor:
866 wiki_name, page_name = page_name_and_anchor.split(':', 1)
867 wikitag, wikiurl, wikitail, err = wikiutil.resolve_interwiki(self.request, wiki_name, page_name)
868 else:
869 err = True
870 if err: # not a interwiki link / not in interwiki map
871 # handle anchors
872 try:
873 page_name, anchor = rsplit(page_name_and_anchor, "#", 1)
874 except ValueError:
875 page_name, anchor = page_name_and_anchor, ""
876 current_page = self.formatter.page.page_name
877 if not page_name:
878 page_name = current_page
879 # handle relative links
880 abs_page_name = wikiutil.AbsPageName(current_page, page_name)
881 tag_attrs, query_args = self._get_params(params,
882 tag_attrs={},
883 acceptable_attrs=acceptable_attrs)
884 return (self.formatter.pagelink(1, abs_page_name, anchor=anchor, querystr=query_args, **tag_attrs) +
885 self._link_description(desc, target, page_name_and_anchor) +
886 self.formatter.pagelink(0, abs_page_name))
887 else: # interwiki link
888 tag_attrs, query_args = self._get_params(params,
889 tag_attrs={},
890 acceptable_attrs=acceptable_attrs)
891 return (self.formatter.interwikilink(1, wiki_name, page_name, querystr=query_args, **tag_attrs) +
892 self._link_description(desc, target, page_name) +
893 self.formatter.interwikilink(0, wiki_name, page_name))
894
895 elif mt.group('extern_addr'):
896 scheme = mt.group('extern_scheme')
897 target = mt.group('extern_addr')
898 tag_attrs, query_args = self._get_params(params,
899 tag_attrs={'class': scheme, },
900 acceptable_attrs=acceptable_attrs)
901 return (self.formatter.url(1, target, **tag_attrs) +
902 self._link_description(desc, target, target) +
903 self.formatter.url(0))
904
905 elif mt.group('attach_scheme'):
906 scheme = mt.group('attach_scheme')
907 url = wikiutil.url_unquote(mt.group('attach_addr'), want_unicode=True)
908 tag_attrs, query_args = self._get_params(params,
909 tag_attrs={'title': desc, },
910 acceptable_attrs=acceptable_attrs)
911 if scheme == 'attachment':
912 return (self.formatter.attachment_link(1, url, querystr=query_args, **tag_attrs) +
913 self._link_description(desc, target, url) +
914 self.formatter.attachment_link(0))
915 elif scheme == 'drawing':
916 return self.formatter.attachment_drawing(url, desc, alt=desc, **tag_attrs)
917 else:
918 if desc:
919 desc = '|' + desc
920 return self.formatter.text('[[%s%s]]' % (target, desc))
921 _link_target_repl = _link_repl
922 _link_desc_repl = _link_repl
923 _link_params_repl = _link_repl
924
925 def _email_repl(self, word, groups):
926 """Handle email addresses (without a leading mailto:)."""
927 return (self.formatter.url(1, "mailto:%s" % word, css='mailto') +
928 self.formatter.text(word) +
929 self.formatter.url(0))
930
931 def _sgml_entity_repl(self, word, groups):
932 """Handle SGML entities."""
933 return self.formatter.text(word)
934
935 def _entity_repl(self, word, groups):
936 """Handle numeric (decimal and hexadecimal) and symbolic SGML entities."""
937 return self.formatter.rawHTML(word)
938
939 def _indent_repl(self, match, groups):
940 """Handle pure indentation (no - * 1. markup)."""
941 result = []
942 if not (self.in_li or self.in_dd):
943 self._close_item(result)
944 self.in_li = 1
945 css_class = None
946 if self.line_was_empty and not self.first_list_item:
947 css_class = 'gap'
948 result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none"))
949 return ''.join(result)
950
951 def _li_none_repl(self, match, groups):
952 """Handle type=none (" .") lists."""
953 result = []
954 self._close_item(result)
955 self.in_li = 1
956 css_class = None
957 if self.line_was_empty and not self.first_list_item:
958 css_class = 'gap'
959 result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none"))
960 return ''.join(result)
961
962 def _li_repl(self, match, groups):
963 """Handle bullet (" *") lists."""
964 result = []
965 self._close_item(result)
966 self.in_li = 1
967 css_class = None
968 if self.line_was_empty and not self.first_list_item:
969 css_class = 'gap'
970 result.append(self.formatter.listitem(1, css_class=css_class))
971 return ''.join(result)
972
973 def _ol_repl(self, match, groups):
974 """Handle numbered lists."""
975 return self._li_repl(match, groups)
976
977 def _dl_repl(self, match, groups):
978 """Handle definition lists."""
979 result = []
980 self._close_item(result)
981 self.in_dd = 1
982 result.extend([
983 self.formatter.definition_term(1),
984 self.formatter.text(match[1:-3].lstrip(' ')),
985 self.formatter.definition_term(0),
986 self.formatter.definition_desc(1),
987 ])
988 return ''.join(result)
989
990 def _indent_level(self):
991 """Return current char-wise indent level."""
992 return len(self.list_indents) and self.list_indents[-1]
993
994 def _indent_to(self, new_level, list_type, numtype, numstart):
995 """Close and open lists."""
996 openlist = [] # don't make one out of these two statements!
997 closelist = []
998
999 if self._indent_level() != new_level and self.in_table:
1000 closelist.append(self.formatter.table(0))
1001 self.in_table = 0
1002
1003 while self._indent_level() > new_level:
1004 self._close_item(closelist)
1005 if self.list_types[-1] == 'ol':
1006 tag = self.formatter.number_list(0)
1007 elif self.list_types[-1] == 'dl':
1008 tag = self.formatter.definition_list(0)
1009 else:
1010 tag = self.formatter.bullet_list(0)
1011 closelist.append(tag)
1012
1013 del self.list_indents[-1]
1014 del self.list_types[-1]
1015
1016 if self.list_types: # we are still in a list
1017 if self.list_types[-1] == 'dl':
1018 self.in_dd = 1
1019 else:
1020 self.in_li = 1
1021
1022 # Open new list, if necessary
1023 if self._indent_level() < new_level:
1024 self.list_indents.append(new_level)
1025 self.list_types.append(list_type)
1026
1027 if self.formatter.in_p:
1028 closelist.append(self.formatter.paragraph(0))
1029
1030 if list_type == 'ol':
1031 tag = self.formatter.number_list(1, numtype, numstart)
1032 elif list_type == 'dl':
1033 tag = self.formatter.definition_list(1)
1034 else:
1035 tag = self.formatter.bullet_list(1)
1036 openlist.append(tag)
1037
1038 self.first_list_item = 1
1039 self.in_li = 0
1040 self.in_dd = 0
1041
1042 # If list level changes, close an open table
1043 if self.in_table and (openlist or closelist):
1044 closelist[0:0] = [self.formatter.table(0)]
1045 self.in_table = 0
1046
1047 self.in_list = self.list_types != []
1048 return ''.join(closelist) + ''.join(openlist)
1049
1050 def _undent(self):
1051 """Close all open lists."""
1052 result = []
1053 #result.append("<!-- _undent start -->\n")
1054 self._close_item(result)
1055 for type in self.list_types[::-1]:
1056 if type == 'ol':
1057 result.append(self.formatter.number_list(0))
1058 elif type == 'dl':
1059 result.append(self.formatter.definition_list(0))
1060 else:
1061 result.append(self.formatter.bullet_list(0))
1062 #result.append("<!-- _undent end -->\n")
1063 self.list_indents = []
1064 self.list_types = []
1065 return ''.join(result)
1066
1067 def _getTableAttrs(self, attrdef):
1068 attr_rule = r'^(\|\|)*<(?!<)(?P<attrs>[^>]*?)>'
1069 m = re.match(attr_rule, attrdef, re.U)
1070 if not m:
1071 return {}, ''
1072 attrdef = m.group('attrs')
1073
1074 # extension for special table markup
1075 def table_extension(key, parser, attrs, wiki_parser=self):
1076 """ returns: tuple (found_flag, msg)
1077 found_flag: whether we found something and were able to process it here
1078 true for special stuff like 100% or - or #AABBCC
1079 false for style xxx="yyy" attributes
1080 msg: "" or an error msg
1081 """
1082 _ = wiki_parser._
1083 found = False
1084 msg = ''
1085 if key[0] in "0123456789":
1086 token = parser.get_token()
1087 if token != '%':
1088 wanted = '%'
1089 msg = _('Expected "%(wanted)s" after "%(key)s", got "%(token)s"') % {
1090 'wanted': wanted, 'key': key, 'token': token}
1091 else:
1092 try:
1093 dummy = int(key)
1094 except ValueError:
1095 msg = _('Expected an integer "%(key)s" before "%(token)s"') % {
1096 'key': key, 'token': token}
1097 else:
1098 found = True
1099 attrs['width'] = '"%s%%"' % key
1100 elif key == '-':
1101 arg = parser.get_token()
1102 try:
1103 dummy = int(arg)
1104 except ValueError:
1105 msg = _('Expected an integer "%(arg)s" after "%(key)s"') % {
1106 'arg': arg, 'key': key}
1107 else:
1108 found = True
1109 attrs['colspan'] = '"%s"' % arg
1110 elif key == '|':
1111 arg = parser.get_token()
1112 try:
1113 dummy = int(arg)
1114 except ValueError:
1115 msg = _('Expected an integer "%(arg)s" after "%(key)s"') % {
1116 'arg': arg, 'key': key}
1117 else:
1118 found = True
1119 attrs['rowspan'] = '"%s"' % arg
1120 elif key == '(':
1121 found = True
1122 attrs['align'] = '"left"'
1123 elif key == ':':
1124 found = True
1125 attrs['align'] = '"center"'
1126 elif key == ')':
1127 found = True
1128 attrs['align'] = '"right"'
1129 elif key == '^':
1130 found = True
1131 attrs['valign'] = '"top"'
1132 elif key == 'v':
1133 found = True
1134 attrs['valign'] = '"bottom"'
1135 elif key == '#':
1136 arg = parser.get_token()
1137 try:
1138 if len(arg) != 6:
1139 raise ValueError
1140 dummy = int(arg, 16)
1141 except ValueError:
1142 msg = _('Expected a color value "%(arg)s" after "%(key)s"') % {
1143 'arg': arg, 'key': key}
1144 else:
1145 found = True
1146 attrs['bgcolor'] = '"#%s"' % arg
1147 return found, self.formatter.rawHTML(msg)
1148
1149 # scan attributes
1150 attr, msg = wikiutil.parseAttributes(self.request, attrdef, '>', table_extension)
1151 if msg:
1152 msg = '<strong class="highlight">%s</strong>' % msg
1153 #logging.debug("parseAttributes returned %r" % attr)
1154 return attr, msg
1155
1156 def _tableZ_repl(self, word, groups):
1157 """Handle table row end."""
1158 if self.in_table:
1159 result = ''
1160 # REMOVED: check for self.in_li, p should always close
1161 if self.formatter.in_p:
1162 result = self.formatter.paragraph(0)
1163 result += self.formatter.table_cell(0) + self.formatter.table_row(0)
1164 return result
1165 else:
1166 return self.formatter.text(word)
1167
1168 def _table_repl(self, word, groups):
1169 """Handle table cell separator."""
1170 if self.in_table:
1171 result = []
1172 # check for attributes
1173 attrs, attrerr = self._getTableAttrs(word)
1174
1175 # start the table row?
1176 if self.table_rowstart:
1177 self.table_rowstart = 0
1178 result.append(self.formatter.table_row(1, attrs))
1179 else:
1180 # Close table cell, first closing open p
1181 # REMOVED check for self.in_li, paragraph should close always!
1182 if self.formatter.in_p:
1183 result.append(self.formatter.paragraph(0))
1184 result.append(self.formatter.table_cell(0))
1185
1186 # check for adjacent cell markers
1187 if word.count("|") > 2:
1188 if 'align' not in attrs and \
1189 not ('style' in attrs and 'text-align' in attrs['style'].lower()):
1190 # add center alignment if we don't have some alignment already
1191 attrs['align'] = '"center"'
1192 if 'colspan' not in attrs:
1193 attrs['colspan'] = '"%d"' % (word.count("|")/2)
1194
1195 # return the complete cell markup
1196 result.append(self.formatter.table_cell(1, attrs) + attrerr)
1197 result.append(self._line_anchordef())
1198 return ''.join(result)
1199 else:
1200 return self.formatter.text(word)
1201
1202 def _heading_repl(self, word, groups):
1203 """Handle section headings."""
1204 heading_text = groups.get('heading_text', '')
1205 depth = min(len(groups.get('hmarker')), 5)
1206 return ''.join([
1207 self._closeP(),
1208 self.formatter.heading(1, depth, id=heading_text),
1209 self.formatter.text(heading_text),
1210 self.formatter.heading(0, depth),
1211 ])
1212 _heading_text_repl = _heading_repl
1213
1214 def _parser_repl(self, word, groups):
1215 """Handle parsed code displays."""
1216 self.parser = None
1217 self.parser_name = None
1218 self.parser_lines = []
1219 parser_line = word = groups.get('parser_line', u'')
1220 parser_name = groups.get('parser_name', None)
1221 parser_args = groups.get('parser_args', None)
1222 parser_nothing = groups.get('parser_nothing', None)
1223 parser_unique = groups.get('parser_unique', u'') or u''
1224 #logging.debug("_parser_repl: parser_name %r parser_args %r parser_unique %r" % (parser_name, parser_args, parser_unique))
1225 if set(parser_unique) == set('{'): # just some more {{{{{{
1226 parser_unique = u'}' * len(parser_unique) # for symmetry cosmetic reasons
1227 self.parser_unique = parser_unique
1228 if parser_name is not None:
1229 # First try to find a parser for this
1230 if parser_name == u'':
1231 # empty bang paths lead to a normal code display
1232 # can be used to escape real, non-empty bang paths
1233 #logging.debug("_parser_repl: empty bangpath")
1234 parser_name = 'text'
1235 word = ''
1236 elif parser_nothing is None:
1237 # there was something non-whitespace following the {{{
1238 parser_name = 'text'
1239
1240 self.setParser(parser_name)
1241 if not self.parser and parser_name:
1242 # loading the desired parser didn't work, retry a safe option:
1243 wanted_parser = parser_name
1244 parser_name = 'text'
1245 self.setParser(parser_name)
1246 word = '%s %s (-)' % (wanted_parser, parser_args) # indication that it did not work
1247
1248 if self.parser:
1249 self.parser_name = parser_name
1250 self.in_pre = 'found_parser'
1251 if word:
1252 self.parser_lines.append(word)
1253 else:
1254 self.in_pre = 'search_parser'
1255
1256 #logging.debug("_parser_repl: in_pre %r line %d" % (self.in_pre, self.lineno))
1257 return ''
1258 _parser_unique_repl = _parser_repl
1259 _parser_line_repl = _parser_repl
1260 _parser_name_repl = _parser_repl
1261 _parser_args_repl = _parser_repl
1262 _parser_nothing_repl = _parser_repl
1263
1264 def _parser_content(self, line):
1265 """ handle state and collecting lines for parser in pre/parser sections """
1266 #logging.debug("parser_content: %r" % line)
1267 if self.in_pre == 'search_parser' and line.strip():
1268 # try to find a parser specification
1269 parser_name = ''
1270 if line.strip().startswith("#!"):
1271 parser_name = line.strip()[2:]
1272 if parser_name:
1273 parser_name = parser_name.split()[0]
1274 else:
1275 parser_name = 'text'
1276 self.setParser(parser_name)
1277
1278 if not self.parser:
1279 parser_name = 'text'
1280 self.setParser(parser_name)
1281
1282 if self.parser:
1283 self.in_pre = 'found_parser'
1284 self.parser_lines.append(line)
1285 self.parser_name = parser_name
1286
1287 elif self.in_pre == 'found_parser':
1288 # collect the content lines
1289 self.parser_lines.append(line)
1290
1291 return '' # we emit the content after reaching the end of the parser/pre section
1292
1293 def _parser_end_repl(self, word, groups):
1294 """ when we reach the end of a parser/pre section,
1295 we call the parser with the lines we collected
1296 """
1297 #if self.in_pre:
1298 self.in_pre = None
1299 self.inhibit_p = 0
1300 #logging.debug("_parser_end_repl: in_pre %r line %d" % (self.in_pre, self.lineno))
1301 self.request.write(self._closeP())
1302 if self.parser_name is None:
1303 # we obviously did not find a parser specification
1304 self.parser_name = 'text'
1305 result = self.formatter.parser(self.parser_name, self.parser_lines)
1306 del self.parser_lines
1307 self.in_pre = None
1308 self.parser = None
1309 return result
1310
1311 def _smiley_repl(self, word, groups):
1312 """Handle smileys."""
1313 return self.formatter.smiley(word)
1314
1315 def _comment_repl(self, word, groups):
1316 # if we are in a paragraph, we must close it so that normal text following
1317 # in the line below the comment will reopen a new paragraph.
1318 if self.formatter.in_p:
1319 self.formatter.paragraph(0)
1320 self.line_is_empty = 1 # markup following comment lines treats them as if they were empty
1321 return self.formatter.comment(word)
1322
1323 def _closeP(self):
1324 if self.formatter.in_p:
1325 return self.formatter.paragraph(0)
1326 return ''
1327
1328 def _macro_repl(self, word, groups):
1329 """Handle macros."""
1330 macro_name = groups.get('macro_name')
1331 macro_args = groups.get('macro_args')
1332 self.inhibit_p = 0 # 1 fixed macros like UserPreferences (in the past, gone now), 0 fixes paragraph formatting for macros
1333
1334 # create macro instance
1335 if self.macro is None:
1336 self.macro = macro.Macro(self)
1337 return self.formatter.macro(self.macro, macro_name, macro_args, markup=groups.get('macro'))
1338 _macro_name_repl = _macro_repl
1339 _macro_args_repl = _macro_repl
1340
1341 def scan(self, line, inhibit_p=False):
1342 """ Scans one line
1343 Append text before match, invoke replace() with match, and add text after match.
1344 """
1345 result = []
1346 lastpos = 0 # absolute position within line
1347 line_length = len(line)
1348
1349 ###result.append(u'<span class="info">[scan: <tt>"%s"</tt>]</span>' % line)
1350 while lastpos <= line_length: # it is <=, not <, because we need to process the empty line also
1351 parser_scan_re = re.compile(self.parser_scan_rule % re.escape(self.parser_unique), re.VERBOSE|re.UNICODE)
1352 scan_re = self.in_pre and parser_scan_re or self.scan_re
1353 match = scan_re.search(line, lastpos)
1354 if match:
1355 start = match.start()
1356 if lastpos < start:
1357 if self.in_pre:
1358 self._parser_content(line[lastpos:start])
1359 else:
1360 ###result.append(u'<span class="info">[add text before match: <tt>"%s"</tt>]</span>' % line[lastpos:match.start()])
1361 if not (inhibit_p or self.inhibit_p or self.in_pre or self.formatter.in_p):
1362 result.append(self.formatter.paragraph(1, css_class="line862"))
1363 # add the simple text in between lastpos and beginning of current match
1364 result.append(self.formatter.text(line[lastpos:start]))
1365
1366 # Replace match with markup
1367 if not (inhibit_p or self.inhibit_p or self.in_pre or self.formatter.in_p or
1368 self.in_table or self.in_list):
1369 result.append(self.formatter.paragraph(1, css_class="line867"))
1370 result.append(self.replace(match, inhibit_p))
1371 end = match.end()
1372 lastpos = end
1373 if start == end:
1374 # we matched an empty string
1375 lastpos += 1 # proceed, we don't want to match this again
1376 else:
1377 if self.in_pre:
1378 # ilastpos is more then 0 and result of line slice is empty make useless line
1379 if not (lastpos > 0 and line[lastpos:] == ''):
1380 self._parser_content(line[lastpos:])
1381 elif line[lastpos:]:
1382 ###result.append('<span class="info">[no match, add rest: <tt>"%s"<tt>]</span>' % line[lastpos:])
1383 if not (inhibit_p or self.inhibit_p or self.in_pre or self.formatter.in_p or
1384 self.in_li or self.in_dd):
1385 result.append(self.formatter.paragraph(1, css_class="line874"))
1386 # add the simple text (no markup) after last match
1387 result.append(self.formatter.text(line[lastpos:]))
1388 break # nothing left to do!
1389 return u''.join(result)
1390
1391 def _replace(self, match):
1392 """ Same as replace() but with no magic """
1393 for name, text in match.groupdict().iteritems():
1394 if text is not None:
1395 # Get replace method and replace text
1396 replace_func = getattr(self, '_%s_repl' % name)
1397 result = replace_func(text, match.groupdict())
1398 return result
1399
1400 def replace(self, match, inhibit_p=False):
1401 """ Replace match using type name """
1402 result = []
1403 for type, hit in match.groupdict().items():
1404 if hit is not None and not type in ["hmarker", ]:
1405
1406 ##result.append(u'<span class="info">[replace: %s: "%s"]</span>' % (type, hit))
1407 # Open p for certain types
1408 if not (inhibit_p or self.inhibit_p or self.formatter.in_p
1409 or self.in_pre or (type in self.no_new_p_before)):
1410 result.append(self.formatter.paragraph(1, css_class="line891"))
1411
1412 # Get replace method and replace hit
1413 replace_func = getattr(self, '_%s_repl' % type)
1414 result.append(replace_func(hit, match.groupdict()))
1415 return ''.join(result)
1416 else:
1417 # We should never get here
1418 import pprint
1419 raise Exception("Can't handle match %r\n%s\n%s" % (
1420 match,
1421 pprint.pformat(match.groupdict()),
1422 pprint.pformat(match.groups()),
1423 ))
1424
1425 return ""
1426
1427 def _line_anchordef(self):
1428 if self.line_anchors and not self.line_anchor_printed:
1429 self.line_anchor_printed = 1
1430 return self.formatter.line_anchordef(self.lineno)
1431 else:
1432 return ''
1433
1434 def format(self, formatter, inhibit_p=False):
1435 """ For each line, scan through looking for magic
1436 strings, outputting verbatim any intervening text.
1437 """
1438 self.formatter = formatter
1439 self.hilite_re = self.formatter.page.hilite_re
1440
1441 # get text and replace TABs
1442 rawtext = self.raw.expandtabs()
1443
1444 # go through the lines
1445 self.lineno = self.start_line
1446 self.lines = self.eol_re.split(rawtext)
1447 self.line_is_empty = 0
1448
1449 self.in_processing_instructions = 1
1450
1451 if self.wrapping_div_class:
1452 self.request.write(self.formatter.div(1, css_class=self.wrapping_div_class))
1453
1454 # Main loop
1455 for line in self.lines:
1456 self.lineno += 1
1457
1458 self.line_anchor_printed = 0
1459 if not self.in_table:
1460 self.request.write(self._line_anchordef())
1461 self.table_rowstart = 1
1462 self.line_was_empty = self.line_is_empty
1463 self.line_is_empty = 0
1464 self.first_list_item = 0
1465 self.inhibit_p = 0
1466
1467 # ignore processing instructions
1468 if self.in_processing_instructions:
1469 found = False
1470 for pi in ("##", "#format", "#refresh", "#redirect", "#deprecated",
1471 "#pragma", "#form", "#acl", "#language"):
1472 if line.lower().startswith(pi):
1473 self.request.write(self.formatter.comment(line))
1474 found = True
1475 break
1476 if not found:
1477 self.in_processing_instructions = 0
1478 else:
1479 continue # do not parse this line
1480
1481 if not self.in_pre:
1482 # we don't have \n as whitespace any more
1483 # This is the space between lines we join to one paragraph
1484 line += ' '
1485
1486 # Paragraph break on empty lines
1487 if not line.strip():
1488 if self.in_table:
1489 self.request.write(self.formatter.table(0))
1490 self.request.write(self._line_anchordef())
1491 self.in_table = 0
1492 # CHANGE: removed check for not self.list_types
1493 # p should close on every empty line
1494 if self.formatter.in_p:
1495 self.request.write(self.formatter.paragraph(0))
1496 self.line_is_empty = 1
1497 continue
1498
1499 # Check indent level
1500 indent = self.indent_re.match(line)
1501 indlen = len(indent.group(0))
1502 indtype = "ul"
1503 numtype = None
1504 numstart = None
1505 if indlen:
1506 match = self.ol_re.match(line)
1507 if match:
1508 numtype, numstart = match.group(0).strip().split('.')
1509 numtype = numtype[0]
1510
1511 if numstart and numstart[0] == "#":
1512 numstart = int(numstart[1:])
1513 else:
1514 numstart = None
1515
1516 indtype = "ol"
1517 else:
1518 match = self.dl_re.match(line)
1519 if match:
1520 indtype = "dl"
1521
1522 # output proper indentation tags
1523 self.request.write(self._indent_to(indlen, indtype, numtype, numstart))
1524
1525 # Table mode
1526 # TODO: move into function?
1527 if (not self.in_table and line[indlen:indlen + 2] == "||"
1528 and line.endswith("|| ") and len(line) >= 5 + indlen):
1529 # Start table
1530 if self.list_types and not self.in_li:
1531 self.request.write(self.formatter.listitem(1, style="list-style-type:none"))
1532 ## CHANGE: no automatic p on li
1533 ##self.request.write(self.formatter.paragraph(1))
1534 self.in_li = 1
1535
1536 # CHANGE: removed check for self.in_li
1537 # paragraph should end before table, always!
1538 if self.formatter.in_p:
1539 self.request.write(self.formatter.paragraph(0))
1540 attrs, attrerr = self._getTableAttrs(line[indlen+2:])
1541 self.request.write(self.formatter.table(1, attrs) + attrerr)
1542 self.in_table = True # self.lineno
1543 elif (self.in_table and not
1544 # intra-table comments should not break a table
1545 (line.startswith("##") or
1546 line[indlen:indlen + 2] == "||" and
1547 line.endswith("|| ") and
1548 len(line) >= 5 + indlen)):
1549
1550 # Close table
1551 self.request.write(self.formatter.table(0))
1552 self.request.write(self._line_anchordef())
1553 self.in_table = 0
1554
1555 # Scan line, format and write
1556 formatted_line = self.scan(line, inhibit_p=inhibit_p)
1557 self.request.write(formatted_line)
1558
1559
1560 # Close code displays, paragraphs, tables and open lists
1561 self.request.write(self._undent())
1562 if self.in_pre: self.request.write(self.formatter.preformatted(0))
1563 if self.formatter.in_p: self.request.write(self.formatter.paragraph(0))
1564 if self.in_table: self.request.write(self.formatter.table(0))
1565
1566 if self.wrapping_div_class:
1567 self.request.write(self.formatter.div(0))
1568
1569
1570 # Private helpers ------------------------------------------------------------
1571
1572 def setParser(self, name):
1573 """ Set parser to parser named 'name' """
1574 # XXX this is done by the formatter as well
1575 try:
1576 self.parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", name)
1577 except wikiutil.PluginMissingError:
1578 self.parser = None
1579
1580 del _
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.