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 &uuml;
 374       |
 375       (\#(\d{1,5}|x[0-9a-fA-F]+))  # numeric entities, like &#42; or &#x42;
 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.
  • [get | view] (2008-11-13 22:24:06, 64.4 KB) [[attachment:FeatureRequests-PragmaCamelCase-text_moin_wiki.py]]
  • [get | view] (2008-11-30 01:29:04, 3.6 KB) [[attachment:camelcase_setting.diff]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.