| 1 | | # -*- coding: utf-8 -* |
| 2 | | """nodoctest |
| 3 | | """ |
| 4 | | |
| 5 | | ############################################################################# |
| 6 | | # Copyright (C) 2007 William Stein <wstein@gmail.com> |
| 7 | | # Distributed under the terms of the GNU General Public License (GPL) |
| 8 | | # The full text of the GPL is available at: |
| 9 | | # http://www.gnu.org/licenses/ |
| 10 | | ############################################################################# |
| 11 | | |
| 12 | | """ |
| 13 | | Wiki to HTML converter |
| 14 | | |
| 15 | | Adopted from the Moin Moin Markup Parser (which is GPL'd) and |
| 16 | | @copyright: 2000, 2001, 2002 by Jurgen Hermann <jh@web.de> |
| 17 | | @license: GNU GPL, see COPYING for details. |
| 18 | | |
| 19 | | AUTHOR: |
| 20 | | -- Jurgen Hermann: Moin Moin version |
| 21 | | -- William Stein: adoption for Sage |
| 22 | | """ |
| 23 | | |
| 24 | | import os, re |
| 25 | | from MoinMoin import config, wikimacro, wikiutil |
| 26 | | from MoinMoin.Page import Page |
| 27 | | from MoinMoin.util import web |
| 28 | | |
| 29 | | Dependencies = [] |
| 30 | | |
| 31 | | class Parser: |
| 32 | | """ |
| 33 | | Object that turns Wiki markup into HTML. |
| 34 | | |
| 35 | | All formatting commands can be parsed one line at a time, though |
| 36 | | some state is carried over between lines. |
| 37 | | |
| 38 | | Methods named like _*_repl() are responsible to handle the named regex |
| 39 | | patterns defined in print_html(). |
| 40 | | """ |
| 41 | | |
| 42 | | # allow caching |
| 43 | | caching = 1 |
| 44 | | Dependencies = [] |
| 45 | | |
| 46 | | # some common strings |
| 47 | | PARENT_PREFIX = wikiutil.PARENT_PREFIX |
| 48 | | attachment_schemas = ["attachment", "inline", "drawing"] |
| 49 | | punct_pattern = re.escape(u'''"\'}]|:,.)?!''') |
| 50 | | url_pattern = (u'http|https|ftp|nntp|news|mailto|telnet|wiki|file|irc|' + |
| 51 | | u'|'.join(attachment_schemas) + |
| 52 | | (config.url_schemas and u'|' + u'|'.join(config.url_schemas) or '')) |
| 53 | | |
| 54 | | # some common rules |
| 55 | | word_rule = ur'(?:(?<![%(u)s%(l)s])|^)%(parent)s(?:%(subpages)s(?:[%(u)s][%(l)s]+){2,})+(?![%(u)s%(l)s]+)' % { |
| 56 | | 'u': config.chars_upper, |
| 57 | | 'l': config.chars_lower, |
| 58 | | 'subpages': wikiutil.CHILD_PREFIX + '?', |
| 59 | | 'parent': ur'(?:%s)?' % re.escape(PARENT_PREFIX), |
| 60 | | } |
| 61 | | url_rule = ur'%(url_guard)s(%(url)s)\:([^\s\<%(punct)s]|([%(punct)s][^\s\<%(punct)s]))+' % { |
| 62 | | 'url_guard': u'(^|(?<!\w))', |
| 63 | | 'url': url_pattern, |
| 64 | | 'punct': punct_pattern, |
| 65 | | } |
| 66 | | |
| 67 | | ol_rule = ur"^\s+(?:[0-9]+|[aAiI])\.(?:#\d+)?\s" |
| 68 | | dl_rule = ur"^\s+.*?::\s" |
| 69 | | |
| 70 | | # the big, fat, ugly one ;) |
| 71 | | formatting_rules = ur"""(?P<ent_numeric>&#(\d{1,5}|x[0-9a-fA-F]+);) |
| 72 | | (?:(?P<emph_ibb>'''''(?=[^']+''')) |
| 73 | | (?P<emph_ibi>'''''(?=[^']+'')) |
| 74 | | (?P<emph_ib_or_bi>'{5}(?=[^'])) |
| 75 | | (?P<emph>'{2,3}) |
| 76 | | (?P<u>__) |
| 77 | | (?P<sup>\^.*?\^) |
| 78 | | (?P<sub>,,[^,]{1,40},,) |
| 79 | | (?P<tt>\{\{\{.*?\}\}\}) |
| 80 | | (?P<processor>(\{\{\{(#!.*|\s*$))) |
| 81 | | (?P<pre>(\{\{\{ ?|\}\}\})) |
| 82 | | (?P<small>(\~- ?|-\~)) |
| 83 | | (?P<big>(\~\+ ?|\+\~)) |
| 84 | | (?P<strike>(--\(|\)--)) |
| 85 | | (?P<rule>-{4,}) |
| 86 | | (?P<comment>^\#\#.*$) |
| 87 | | (?P<macro>\[\[(%%(macronames)s)(?:\(.*?\))?\]\])) |
| 88 | | (?P<ol>%(ol_rule)s) |
| 89 | | (?P<dl>%(dl_rule)s) |
| 90 | | (?P<li>^\s+\*\s*) |
| 91 | | (?P<li_none>^\s+\.\s*) |
| 92 | | (?P<indent>^\s+) |
| 93 | | (?P<tableZ>\|\| $) |
| 94 | | (?P<table>(?:\|\|)+(?:<[^>]*?>)?(?!\|? $)) |
| 95 | | (?P<heading>^\s*(?P<hmarker>=+)\s.*\s(?P=hmarker) $) |
| 96 | | (?P<interwiki>[A-Z][a-zA-Z]+\:[^\s'\"\:\<\|]([^\s%(punct)s]|([%(punct)s][^\s%(punct)s]))+) |
| 97 | | (?P<word>%(word_rule)s) |
| 98 | | (?P<url_bracket>\[((%(url)s)\:|#|\:)[^\s\]]+(\s[^\]]+)?\]) |
| 99 | | (?P<url>%(url_rule)s) |
| 100 | | (?P<email>[-\w._+]+\@[\w-]+(\.[\w-]+)+) |
| 101 | | (?P<smiley>(?<=\s)(%(smiley)s)(?=\s)) |
| 102 | | (?P<smileyA>^(%(smiley)s)(?=\s)) |
| 103 | | (?P<ent_symbolic>&[a-zA-Z]+;) |
| 104 | | (?P<ent>[<>&]) |
| 105 | | (?P<wikiname_bracket>\[".*?"\]) |
| 106 | | (?P<tt_bt>`.*?`)""" % { |
| 107 | | |
| 108 | | 'url': url_pattern, |
| 109 | | 'punct': punct_pattern, |
| 110 | | 'ol_rule': ol_rule, |
| 111 | | 'dl_rule': dl_rule, |
| 112 | | 'url_rule': url_rule, |
| 113 | | 'word_rule': word_rule, |
| 114 | | 'smiley': u'|'.join(map(re.escape, config.smileys.keys()))} |
| 115 | | |
| 116 | | # Don't start p before these |
| 117 | | no_new_p_before = ("heading rule table tableZ tr td " |
| 118 | | "ul ol dl dt dd li li_none indent " |
| 119 | | "macro processor pre") |
| 120 | | no_new_p_before = no_new_p_before.split() |
| 121 | | no_new_p_before = dict(zip(no_new_p_before, [1] * len(no_new_p_before))) |
| 122 | | |
| 123 | | def __init__(self, raw, request, **kw): |
| 124 | | self.raw = raw |
| 125 | | self.request = request |
| 126 | | self.form = request.form |
| 127 | | self._ = request.getText |
| 128 | | self.cfg = request.cfg |
| 129 | | self.line_anchors = kw.get('line_anchors', True) |
| 130 | | self.macro = None |
| 131 | | self.start_line = kw.get('start_line', 0) |
| 132 | | |
| 133 | | self.is_em = 0 |
| 134 | | self.is_b = 0 |
| 135 | | self.is_u = 0 |
| 136 | | self.is_strike = 0 |
| 137 | | self.lineno = 0 |
| 138 | | self.in_list = 0 # between <ul/ol/dl> and </ul/ol/dl> |
| 139 | | self.in_li = 0 # between <li> and </li> |
| 140 | | self.in_dd = 0 # between <dd> and </dd> |
| 141 | | self.in_pre = 0 |
| 142 | | self.in_table = 0 |
| 143 | | self.is_big = False |
| 144 | | self.is_small = False |
| 145 | | self.inhibit_p = 0 # if set, do not auto-create a <p>aragraph |
| 146 | | self.titles = request._page_headings |
| 147 | | |
| 148 | | # holds the nesting level (in chars) of open lists |
| 149 | | self.list_indents = [] |
| 150 | | self.list_types = [] |
| 151 | | |
| 152 | | self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(wikimacro.getNames(self.cfg))} |
| 153 | | |
| 154 | | def _close_item(self, result): |
| 155 | | #result.append("<!-- close item begin -->\n") |
| 156 | | if self.in_table: |
| 157 | | result.append(self.formatter.table(0)) |
| 158 | | self.in_table = 0 |
| 159 | | if self.in_li: |
| 160 | | self.in_li = 0 |
| 161 | | if self.formatter.in_p: |
| 162 | | result.append(self.formatter.paragraph(0)) |
| 163 | | result.append(self.formatter.listitem(0)) |
| 164 | | if self.in_dd: |
| 165 | | self.in_dd = 0 |
| 166 | | if self.formatter.in_p: |
| 167 | | result.append(self.formatter.paragraph(0)) |
| 168 | | result.append(self.formatter.definition_desc(0)) |
| 169 | | #result.append("<!-- close item end -->\n") |
| 170 | | |
| 171 | | |
| 172 | | def interwiki(self, url_and_text, **kw): |
| 173 | | # TODO: maybe support [wiki:Page http://wherever/image.png] ? |
| 174 | | if len(url_and_text) == 1: |
| 175 | | url = url_and_text[0] |
| 176 | | text = None |
| 177 | | else: |
| 178 | | url, text = url_and_text |
| 179 | | |
| 180 | | # keep track of whether this is a self-reference, so links |
| 181 | | # are always shown even the page doesn't exist. |
| 182 | | is_self_reference = 0 |
| 183 | | url2 = url.lower() |
| 184 | | if url2.startswith('wiki:self:'): |
| 185 | | url = url[10:] # remove "wiki:self:" |
| 186 | | is_self_reference = 1 |
| 187 | | elif url2.startswith('wiki:'): |
| 188 | | url = url[5:] # remove "wiki:" |
| 189 | | |
| 190 | | tag, tail = wikiutil.split_wiki(url) |
| 191 | | if text is None: |
| 192 | | if tag: |
| 193 | | text = tail |
| 194 | | else: |
| 195 | | text = url |
| 196 | | url = "" |
| 197 | | elif (url.startswith(wikiutil.CHILD_PREFIX) or # fancy link to subpage [wiki:/SubPage text] |
| 198 | | is_self_reference or # [wiki:Self:LocalPage text] or [:LocalPage:text] |
| 199 | | Page(self.request, url).exists()): # fancy link to local page [wiki:LocalPage text] |
| 200 | | return self._word_repl(url, text) |
| 201 | | |
| 202 | | wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, url) |
| 203 | | href = wikiutil.join_wiki(wikiurl, wikitail) |
| 204 | | |
| 205 | | # check for image URL, and possibly return IMG tag |
| 206 | | if not kw.get('pretty_url', 0) and wikiutil.isPicture(wikitail): |
| 207 | | return self.formatter.image(src=href) |
| 208 | | |
| 209 | | # link to self? |
| 210 | | if wikitag is None: |
| 211 | | return self._word_repl(wikitail) |
| 212 | | |
| 213 | | return (self.formatter.interwikilink(1, tag, tail) + |
| 214 | | self.formatter.text(text) + |
| 215 | | self.formatter.interwikilink(0, tag, tail)) |
| 216 | | |
| 217 | | def attachment(self, url_and_text, **kw): |
| 218 | | """ This gets called on attachment URLs. |
| 219 | | """ |
| 220 | | _ = self._ |
| 221 | | if len(url_and_text) == 1: |
| 222 | | url = url_and_text[0] |
| 223 | | text = None |
| 224 | | else: |
| 225 | | url, text = url_and_text |
| 226 | | |
| 227 | | inline = url[0] == 'i' |
| 228 | | drawing = url[0] == 'd' |
| 229 | | url = url.split(":", 1)[1] |
| 230 | | url = wikiutil.url_unquote(url, want_unicode=True) |
| 231 | | text = text or url |
| 232 | | |
| 233 | | from MoinMoin.action import AttachFile |
| 234 | | if drawing: |
| 235 | | return self.formatter.attachment_drawing(url, text) |
| 236 | | |
| 237 | | # check for image URL, and possibly return IMG tag |
| 238 | | # (images are always inlined, just like for other URLs) |
| 239 | | if not kw.get('pretty_url', 0) and wikiutil.isPicture(url): |
| 240 | | return self.formatter.attachment_image(url) |
| 241 | | |
| 242 | | # inline the attachment |
| 243 | | if inline: |
| 244 | | return self.formatter.attachment_inlined(url, text) |
| 245 | | |
| 246 | | return self.formatter.attachment_link(url, text) |
| 247 | | |
| 248 | | def _u_repl(self, word): |
| 249 | | """Handle underline.""" |
| 250 | | self.is_u = not self.is_u |
| 251 | | return self.formatter.underline(self.is_u) |
| 252 | | |
| 253 | | def _strike_repl(self, word): |
| 254 | | """Handle strikethrough.""" |
| 255 | | # XXX we don't really enforce the correct sequence --( ... )-- here |
| 256 | | self.is_strike = not self.is_strike |
| 257 | | return self.formatter.strike(self.is_strike) |
| 258 | | |
| 259 | | def _small_repl(self, word): |
| 260 | | """Handle small.""" |
| 261 | | if word.strip() == '~-' and self.is_small: |
| 262 | | return self.formatter.text(word) |
| 263 | | if word.strip() == '-~' and not self.is_small: |
| 264 | | return self.formatter.text(word) |
| 265 | | self.is_small = not self.is_small |
| 266 | | return self.formatter.small(self.is_small) |
| 267 | | |
| 268 | | def _big_repl(self, word): |
| 269 | | """Handle big.""" |
| 270 | | if word.strip() == '~+' and self.is_big: |
| 271 | | return self.formatter.text(word) |
| 272 | | if word.strip() == '+~' and not self.is_big: |
| 273 | | return self.formatter.text(word) |
| 274 | | self.is_big = not self.is_big |
| 275 | | return self.formatter.big(self.is_big) |
| 276 | | |
| 277 | | def _emph_repl(self, word): |
| 278 | | """Handle emphasis, i.e. '' and '''.""" |
| 279 | | ##print "#", self.is_b, self.is_em, "#" |
| 280 | | if len(word) == 3: |
| 281 | | self.is_b = not self.is_b |
| 282 | | if self.is_em and self.is_b: |
| 283 | | self.is_b = 2 |
| 284 | | return self.formatter.strong(self.is_b) |
| 285 | | else: |
| 286 | | self.is_em = not self.is_em |
| 287 | | if self.is_em and self.is_b: |
| 288 | | self.is_em = 2 |
| 289 | | return self.formatter.emphasis(self.is_em) |
| 290 | | |
| 291 | | def _emph_ibb_repl(self, word): |
| 292 | | """Handle mixed emphasis, i.e. ''''' followed by '''.""" |
| 293 | | self.is_b = not self.is_b |
| 294 | | self.is_em = not self.is_em |
| 295 | | if self.is_em and self.is_b: |
| 296 | | self.is_b = 2 |
| 297 | | return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b) |
| 298 | | |
| 299 | | def _emph_ibi_repl(self, word): |
| 300 | | """Handle mixed emphasis, i.e. ''''' followed by ''.""" |
| 301 | | self.is_b = not self.is_b |
| 302 | | self.is_em = not self.is_em |
| 303 | | if self.is_em and self.is_b: |
| 304 | | self.is_em = 2 |
| 305 | | return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em) |
| 306 | | |
| 307 | | def _emph_ib_or_bi_repl(self, word): |
| 308 | | """Handle mixed emphasis, exactly five '''''.""" |
| 309 | | ##print "*", self.is_b, self.is_em, "*" |
| 310 | | b_before_em = self.is_b > self.is_em > 0 |
| 311 | | self.is_b = not self.is_b |
| 312 | | self.is_em = not self.is_em |
| 313 | | if b_before_em: |
| 314 | | return self.formatter.strong(self.is_b) + self.formatter.emphasis(self.is_em) |
| 315 | | else: |
| 316 | | return self.formatter.emphasis(self.is_em) + self.formatter.strong(self.is_b) |
| 317 | | |
| 318 | | |
| 319 | | def _sup_repl(self, word): |
| 320 | | """Handle superscript.""" |
| 321 | | return self.formatter.sup(1) + \ |
| 322 | | self.formatter.text(word[1:-1]) + \ |
| 323 | | self.formatter.sup(0) |
| 324 | | |
| 325 | | def _sub_repl(self, word): |
| 326 | | """Handle subscript.""" |
| 327 | | return self.formatter.sub(1) + \ |
| 328 | | self.formatter.text(word[2:-2]) + \ |
| 329 | | self.formatter.sub(0) |
| 330 | | |
| 331 | | |
| 332 | | def _rule_repl(self, word): |
| 333 | | """Handle sequences of dashes.""" |
| 334 | | result = self._undent() + self._closeP() |
| 335 | | if len(word) <= 4: |
| 336 | | result = result + self.formatter.rule() |
| 337 | | else: |
| 338 | | # Create variable rule size 1 - 6. Actual size defined in css. |
| 339 | | size = min(len(word), 10) - 4 |
| 340 | | result = result + self.formatter.rule(size) |
| 341 | | return result |
| 342 | | |
| 343 | | |
| 344 | | def _word_repl(self, word, text=None): |
| 345 | | """Handle WikiNames.""" |
| 346 | | |
| 347 | | # check for parent links |
| 348 | | # !!! should use wikiutil.AbsPageName here, but setting `text` |
| 349 | | # correctly prevents us from doing this for now |
| 350 | | if word.startswith(wikiutil.PARENT_PREFIX): |
| 351 | | if not text: |
| 352 | | text = word |
| 353 | | word = '/'.join(filter(None, self.formatter.page.page_name.split('/')[:-1] + [word[wikiutil.PARENT_PREFIX_LEN:]])) |
| 354 | | |
| 355 | | if not text: |
| 356 | | # if a simple, self-referencing link, emit it as plain text |
| 357 | | if word == self.formatter.page.page_name: |
| 358 | | return self.formatter.text(word) |
| 359 | | text = word |
| 360 | | if word.startswith(wikiutil.CHILD_PREFIX): |
| 361 | | word = self.formatter.page.page_name + '/' + word[wikiutil.CHILD_PREFIX_LEN:] |
| 362 | | |
| 363 | | # handle anchors |
| 364 | | parts = word.split("#", 1) |
| 365 | | anchor = "" |
| 366 | | if len(parts)==2: |
| 367 | | word, anchor = parts |
| 368 | | |
| 369 | | return (self.formatter.pagelink(1, word, anchor=anchor) + |
| 370 | | self.formatter.text(text) + |
| 371 | | self.formatter.pagelink(0, word)) |
| 372 | | |
| 373 | | def _notword_repl(self, word): |
| 374 | | """Handle !NotWikiNames.""" |
| 375 | | return self.formatter.nowikiword(word[1:]) |
| 376 | | |
| 377 | | def _interwiki_repl(self, word): |
| 378 | | """Handle InterWiki links.""" |
| 379 | | wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, word) |
| 380 | | if wikitag_bad: |
| 381 | | return self.formatter.text(word) |
| 382 | | else: |
| 383 | | return self.interwiki(["wiki:" + word]) |
| 384 | | |
| 385 | | |
| 386 | | def _url_repl(self, word): |
| 387 | | """Handle literal URLs including inline images.""" |
| 388 | | scheme = word.split(":", 1)[0] |
| 389 | | |
| 390 | | if scheme == "wiki": |
| 391 | | return self.interwiki([word]) |
| 392 | | if scheme in self.attachment_schemas: |
| 393 | | return self.attachment([word]) |
| 394 | | |
| 395 | | if wikiutil.isPicture(word): |
| 396 | | word = wikiutil.mapURL(self.request, word) |
| 397 | | # Get image name http://here.com/dir/image.gif -> image |
| 398 | | name = word.split('/')[-1] |
| 399 | | name = ''.join(name.split('.')[:-1]) |
| 400 | | return self.formatter.image(src=word, alt=name) |
| 401 | | else: |
| 402 | | return (self.formatter.url(1, word, css=scheme) + |
| 403 | | self.formatter.text(word) + |
| 404 | | self.formatter.url(0)) |
| 405 | | |
| 406 | | |
| 407 | | def _wikiname_bracket_repl(self, word): |
| 408 | | """Handle special-char wikinames.""" |
| 409 | | wikiname = word[2:-2] |
| 410 | | if wikiname: |
| 411 | | return self._word_repl(wikiname) |
| 412 | | else: |
| 413 | | return self.formatter.text(word) |
| 414 | | |
| 415 | | |
| 416 | | def _url_bracket_repl(self, word): |
| 417 | | """Handle bracketed URLs.""" |
| 418 | | |
| 419 | | # Local extended link? |
| 420 | | if word[1] == ':': |
| 421 | | words = word[2:-1].split(':', 1) |
| 422 | | if len(words) == 1: |
| 423 | | words = words * 2 |
| 424 | | words[0] = 'wiki:Self:%s' % words[0] |
| 425 | | return self.interwiki(words, pretty_url=1) |
| 426 | | #return self._word_repl(words[0], words[1]) |
| 427 | | |
| 428 | | # Traditional split on space |
| 429 | | words = word[1:-1].split(None, 1) |
| 430 | | if len(words) == 1: |
| 431 | | words = words * 2 |
| 432 | | |
| 433 | | if words[0][0] == '#': |
| 434 | | # anchor link |
| 435 | | return (self.formatter.url(1, words[0]) + |
| 436 | | self.formatter.text(words[1]) + |
| 437 | | self.formatter.url(0)) |
| 438 | | |
| 439 | | scheme = words[0].split(":", 1)[0] |
| 440 | | if scheme == "wiki": |
| 441 | | return self.interwiki(words, pretty_url=1) |
| 442 | | if scheme in self.attachment_schemas: |
| 443 | | return self.attachment(words, pretty_url=1) |
| 444 | | |
| 445 | | if wikiutil.isPicture(words[1]) and re.match(self.url_rule, words[1]): |
| 446 | | return (self.formatter.url(1, words[0], css='external', do_escape=0) + |
| 447 | | self.formatter.image(title=words[0], alt=words[0], src=words[1]) + |
| 448 | | self.formatter.url(0)) |
| 449 | | else: |
| 450 | | return (self.formatter.url(1, words[0], css=scheme, do_escape=0) + |
| 451 | | self.formatter.text(words[1]) + |
| 452 | | self.formatter.url(0)) |
| 453 | | |
| 454 | | |
| 455 | | def _email_repl(self, word): |
| 456 | | """Handle email addresses (without a leading mailto:).""" |
| 457 | | return (self.formatter.url(1, "mailto:" + word, css='mailto') + |
| 458 | | self.formatter.text(word) + |
| 459 | | self.formatter.url(0)) |
| 460 | | |
| 461 | | |
| 462 | | def _ent_repl(self, word): |
| 463 | | """Handle SGML entities.""" |
| 464 | | return self.formatter.text(word) |
| 465 | | #return {'&': '&', |
| 466 | | # '<': '<', |
| 467 | | # '>': '>'}[word] |
| 468 | | |
| 469 | | def _ent_numeric_repl(self, word): |
| 470 | | """Handle numeric (decimal and hexadecimal) SGML entities.""" |
| 471 | | return self.formatter.rawHTML(word) |
| 472 | | |
| 473 | | def _ent_symbolic_repl(self, word): |
| 474 | | """Handle symbolic SGML entities.""" |
| 475 | | return self.formatter.rawHTML(word) |
| 476 | | |
| 477 | | def _indent_repl(self, match): |
| 478 | | """Handle pure indentation (no - * 1. markup).""" |
| 479 | | result = [] |
| 480 | | if not (self.in_li or self.in_dd): |
| 481 | | self._close_item(result) |
| 482 | | self.in_li = 1 |
| 483 | | css_class = None |
| 484 | | if self.line_was_empty and not self.first_list_item: |
| 485 | | css_class = 'gap' |
| 486 | | result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none")) |
| 487 | | return ''.join(result) |
| 488 | | |
| 489 | | def _li_none_repl(self, match): |
| 490 | | """Handle type=none (" .") lists.""" |
| 491 | | result = [] |
| 492 | | self._close_item(result) |
| 493 | | self.in_li = 1 |
| 494 | | css_class = None |
| 495 | | if self.line_was_empty and not self.first_list_item: |
| 496 | | css_class = 'gap' |
| 497 | | result.append(self.formatter.listitem(1, css_class=css_class, style="list-style-type:none")) |
| 498 | | return ''.join(result) |
| 499 | | |
| 500 | | def _li_repl(self, match): |
| 501 | | """Handle bullet (" *") lists.""" |
| 502 | | result = [] |
| 503 | | self._close_item(result) |
| 504 | | self.in_li = 1 |
| 505 | | css_class = None |
| 506 | | if self.line_was_empty and not self.first_list_item: |
| 507 | | css_class = 'gap' |
| 508 | | result.append(self.formatter.listitem(1, css_class=css_class)) |
| 509 | | return ''.join(result) |
| 510 | | |
| 511 | | def _ol_repl(self, match): |
| 512 | | """Handle numbered lists.""" |
| 513 | | return self._li_repl(match) |
| 514 | | |
| 515 | | def _dl_repl(self, match): |
| 516 | | """Handle definition lists.""" |
| 517 | | result = [] |
| 518 | | self._close_item(result) |
| 519 | | self.in_dd = 1 |
| 520 | | result.extend([ |
| 521 | | self.formatter.definition_term(1), |
| 522 | | self.formatter.text(match[1:-3].lstrip(' ')), |
| 523 | | self.formatter.definition_term(0), |
| 524 | | self.formatter.definition_desc(1), |
| 525 | | ]) |
| 526 | | return ''.join(result) |
| 527 | | |
| 528 | | |
| 529 | | def _indent_level(self): |
| 530 | | """Return current char-wise indent level.""" |
| 531 | | return len(self.list_indents) and self.list_indents[-1] |
| 532 | | |
| 533 | | |
| 534 | | def _indent_to(self, new_level, list_type, numtype, numstart): |
| 535 | | """Close and open lists.""" |
| 536 | | open = [] # don't make one out of these two statements! |
| 537 | | close = [] |
| 538 | | |
| 539 | | if self._indent_level() != new_level and self.in_table: |
| 540 | | close.append(self.formatter.table(0)) |
| 541 | | self.in_table = 0 |
| 542 | | |
| 543 | | while self._indent_level() > new_level: |
| 544 | | self._close_item(close) |
| 545 | | if self.list_types[-1] == 'ol': |
| 546 | | tag = self.formatter.number_list(0) |
| 547 | | elif self.list_types[-1] == 'dl': |
| 548 | | tag = self.formatter.definition_list(0) |
| 549 | | else: |
| 550 | | tag = self.formatter.bullet_list(0) |
| 551 | | close.append(tag) |
| 552 | | |
| 553 | | del self.list_indents[-1] |
| 554 | | del self.list_types[-1] |
| 555 | | |
| 556 | | if self.list_types: # we are still in a list |
| 557 | | if self.list_types[-1] == 'dl': |
| 558 | | self.in_dd = 1 |
| 559 | | else: |
| 560 | | self.in_li = 1 |
| 561 | | |
| 562 | | # Open new list, if necessary |
| 563 | | if self._indent_level() < new_level: |
| 564 | | self.list_indents.append(new_level) |
| 565 | | self.list_types.append(list_type) |
| 566 | | |
| 567 | | if self.formatter.in_p: |
| 568 | | close.append(self.formatter.paragraph(0)) |
| 569 | | |
| 570 | | if list_type == 'ol': |
| 571 | | tag = self.formatter.number_list(1, numtype, numstart) |
| 572 | | elif list_type == 'dl': |
| 573 | | tag = self.formatter.definition_list(1) |
| 574 | | else: |
| 575 | | tag = self.formatter.bullet_list(1) |
| 576 | | open.append(tag) |
| 577 | | |
| 578 | | self.first_list_item = 1 |
| 579 | | self.in_li = 0 |
| 580 | | self.in_dd = 0 |
| 581 | | |
| 582 | | # If list level changes, close an open table |
| 583 | | if self.in_table and (open or close): |
| 584 | | close[0:0] = [self.formatter.table(0)] |
| 585 | | self.in_table = 0 |
| 586 | | |
| 587 | | self.in_list = self.list_types != [] |
| 588 | | return ''.join(close) + ''.join(open) |
| 589 | | |
| 590 | | |
| 591 | | def _undent(self): |
| 592 | | """Close all open lists.""" |
| 593 | | result = [] |
| 594 | | #result.append("<!-- _undent start -->\n") |
| 595 | | self._close_item(result) |
| 596 | | for type in self.list_types[::-1]: |
| 597 | | if type == 'ol': |
| 598 | | result.append(self.formatter.number_list(0)) |
| 599 | | elif type == 'dl': |
| 600 | | result.append(self.formatter.definition_list(0)) |
| 601 | | else: |
| 602 | | result.append(self.formatter.bullet_list(0)) |
| 603 | | #result.append("<!-- _undent end -->\n") |
| 604 | | self.list_indents = [] |
| 605 | | self.list_types = [] |
| 606 | | return ''.join(result) |
| 607 | | |
| 608 | | |
| 609 | | def _tt_repl(self, word): |
| 610 | | """Handle inline code.""" |
| 611 | | return self.formatter.code(1) + \ |
| 612 | | self.formatter.text(word[3:-3]) + \ |
| 613 | | self.formatter.code(0) |
| 614 | | |
| 615 | | |
| 616 | | def _tt_bt_repl(self, word): |
| 617 | | """Handle backticked inline code.""" |
| 618 | | # if len(word) == 2: return "" // removed for FCK editor |
| 619 | | return self.formatter.code(1, css="backtick") + \ |
| 620 | | self.formatter.text(word[1:-1]) + \ |
| 621 | | self.formatter.code(0) |
| 622 | | |
| 623 | | |
| 624 | | def _getTableAttrs(self, attrdef): |
| 625 | | # skip "|" and initial "<" |
| 626 | | while attrdef and attrdef[0] == "|": |
| 627 | | attrdef = attrdef[1:] |
| 628 | | if not attrdef or attrdef[0] != "<": |
| 629 | | return {}, '' |
| 630 | | attrdef = attrdef[1:] |
| 631 | | |
| 632 | | # extension for special table markup |
| 633 | | def table_extension(key, parser, attrs, wiki_parser=self): |
| 634 | | """ returns: tuple (found_flag, msg) |
| 635 | | found_flag: whether we found something and were able to process it here |
| 636 | | true for special stuff like 100% or - or #AABBCC |
| 637 | | false for style xxx="yyy" attributes |
| 638 | | msg: "" or an error msg |
| 639 | | """ |
| 640 | | _ = wiki_parser._ |
| 641 | | found = False |
| 642 | | msg = '' |
| 643 | | if key[0] in "0123456789": |
| 644 | | token = parser.get_token() |
| 645 | | if token != '%': |
| 646 | | wanted = '%' |
| 647 | | msg = _('Expected "%(wanted)s" after "%(key)s", got "%(token)s"') % { |
| 648 | | 'wanted': wanted, 'key': key, 'token': token} |
| 649 | | else: |
| 650 | | try: |
| 651 | | dummy = int(key) |
| 652 | | except ValueError: |
| 653 | | msg = _('Expected an integer "%(key)s" before "%(token)s"') % { |
| 654 | | 'key': key, 'token': token} |
| 655 | | else: |
| 656 | | found = True |
| 657 | | attrs['width'] = '"%s%%"' % key |
| 658 | | elif key == '-': |
| 659 | | arg = parser.get_token() |
| 660 | | try: |
| 661 | | dummy = int(arg) |
| 662 | | except ValueError: |
| 663 | | msg = _('Expected an integer "%(arg)s" after "%(key)s"') % { |
| 664 | | 'arg': arg, 'key': key} |
| 665 | | else: |
| 666 | | found = True |
| 667 | | attrs['colspan'] = '"%s"' % arg |
| 668 | | elif key == '|': |
| 669 | | arg = parser.get_token() |
| 670 | | try: |
| 671 | | dummy = int(arg) |
| 672 | | except ValueError: |
| 673 | | msg = _('Expected an integer "%(arg)s" after "%(key)s"') % { |
| 674 | | 'arg': arg, 'key': key} |
| 675 | | else: |
| 676 | | found = True |
| 677 | | attrs['rowspan'] = '"%s"' % arg |
| 678 | | elif key == '(': |
| 679 | | found = True |
| 680 | | attrs['align'] = '"left"' |
| 681 | | elif key == ':': |
| 682 | | found = True |
| 683 | | attrs['align'] = '"center"' |
| 684 | | elif key == ')': |
| 685 | | found = True |
| 686 | | attrs['align'] = '"right"' |
| 687 | | elif key == '^': |
| 688 | | found = True |
| 689 | | attrs['valign'] = '"top"' |
| 690 | | elif key == 'v': |
| 691 | | found = True |
| 692 | | attrs['valign'] = '"bottom"' |
| 693 | | elif key == '#': |
| 694 | | arg = parser.get_token() |
| 695 | | try: |
| 696 | | if len(arg) != 6: raise ValueError |
| 697 | | dummy = int(arg, 16) |
| 698 | | except ValueError: |
| 699 | | msg = _('Expected a color value "%(arg)s" after "%(key)s"') % { |
| 700 | | 'arg': arg, 'key': key} |
| 701 | | else: |
| 702 | | found = True |
| 703 | | attrs['bgcolor'] = '"#%s"' % arg |
| 704 | | return found, self.formatter.rawHTML(msg) |
| 705 | | |
| 706 | | # scan attributes |
| 707 | | attr, msg = wikiutil.parseAttributes(self.request, attrdef, '>', table_extension) |
| 708 | | if msg: |
| 709 | | msg = '<strong class="highlight">%s</strong>' % msg |
| 710 | | #self.request.log("parseAttributes returned %r" % attr) |
| 711 | | return attr, msg |
| 712 | | |
| 713 | | def _tableZ_repl(self, word): |
| 714 | | """Handle table row end.""" |
| 715 | | if self.in_table: |
| 716 | | result = '' |
| 717 | | # REMOVED: check for self.in_li, p should always close |
| 718 | | if self.formatter.in_p: |
| 719 | | result = self.formatter.paragraph(0) |
| 720 | | result += self.formatter.table_cell(0) + self.formatter.table_row(0) |
| 721 | | return result |
| 722 | | else: |
| 723 | | return self.formatter.text(word) |
| 724 | | |
| 725 | | def _table_repl(self, word): |
| 726 | | """Handle table cell separator.""" |
| 727 | | if self.in_table: |
| 728 | | result = [] |
| 729 | | # check for attributes |
| 730 | | attrs, attrerr = self._getTableAttrs(word) |
| 731 | | |
| 732 | | # start the table row? |
| 733 | | if self.table_rowstart: |
| 734 | | self.table_rowstart = 0 |
| 735 | | result.append(self.formatter.table_row(1, attrs)) |
| 736 | | else: |
| 737 | | # Close table cell, first closing open p |
| 738 | | # REMOVED check for self.in_li, paragraph should close always! |
| 739 | | if self.formatter.in_p: |
| 740 | | result.append(self.formatter.paragraph(0)) |
| 741 | | result.append(self.formatter.table_cell(0)) |
| 742 | | |
| 743 | | # check for adjacent cell markers |
| 744 | | if word.count("|") > 2: |
| 745 | | if not attrs.has_key('align') and \ |
| 746 | | not (attrs.has_key('style') and 'text-align' in attrs['style'].lower()): |
| 747 | | # add center alignment if we don't have some alignment already |
| 748 | | attrs['align'] = '"center"' |
| 749 | | if not attrs.has_key('colspan'): |
| 750 | | attrs['colspan'] = '"%d"' % (word.count("|")/2) |
| 751 | | |
| 752 | | # return the complete cell markup |
| 753 | | result.append(self.formatter.table_cell(1, attrs) + attrerr) |
| 754 | | result.append(self._line_anchordef()) |
| 755 | | return ''.join(result) |
| 756 | | else: |
| 757 | | return self.formatter.text(word) |
| 758 | | |
| 759 | | |
| 760 | | def _heading_repl(self, word): |
| 761 | | """Handle section headings.""" |
| 762 | | from hashlib import sha1 |
| 763 | | |
| 764 | | h = word.strip() |
| 765 | | level = 1 |
| 766 | | while h[level:level+1] == '=': |
| 767 | | level += 1 |
| 768 | | depth = min(5,level) |
| 769 | | |
| 770 | | # this is needed for Included pages |
| 771 | | # TODO but it might still result in unpredictable results |
| 772 | | # when included the same page multiple times |
| 773 | | title_text = h[level:-level].strip() |
| 774 | | pntt = self.formatter.page.page_name + title_text |
| 775 | | self.titles.setdefault(pntt, 0) |
| 776 | | self.titles[pntt] += 1 |
| 777 | | |
| 778 | | unique_id = '' |
| 779 | | if self.titles[pntt] > 1: |
| 780 | | unique_id = '-%d' % self.titles[pntt] |
| 781 | | result = self._closeP() |
| 782 | | result += self.formatter.heading(1, depth, id="head-"+sha1.new(pntt.encode(config.charset)).hexdigest()+unique_id) |
| 783 | | |
| 784 | | return (result + self.formatter.text(title_text) + |
| 785 | | self.formatter.heading(0, depth)) |
| 786 | | |
| 787 | | def _processor_repl(self, word): |
| 788 | | """Handle processed code displays.""" |
| 789 | | if word[:3] == '{{{': |
| 790 | | word = word[3:] |
| 791 | | |
| 792 | | self.processor = None |
| 793 | | self.processor_name = None |
| 794 | | self.processor_is_parser = 0 |
| 795 | | s_word = word.strip() |
| 796 | | if s_word == '#!': |
| 797 | | # empty bang paths lead to a normal code display |
| 798 | | # can be used to escape real, non-empty bang paths |
| 799 | | word = '' |
| 800 | | self.in_pre = 3 |
| 801 | | return self._closeP() + self.formatter.preformatted(1) |
| 802 | | elif s_word[:2] == '#!': |
| 803 | | # First try to find a processor for this (will go away in 2.0) |
| 804 | | processor_name = s_word[2:].split()[0] |
| 805 | | self.setProcessor(processor_name) |
| 806 | | |
| 807 | | if self.processor: |
| 808 | | self.processor_name = processor_name |
| 809 | | self.in_pre = 2 |
| 810 | | self.colorize_lines = [word] |
| 811 | | return '' |
| 812 | | elif s_word: |
| 813 | | self.in_pre = 3 |
| 814 | | return self._closeP() + self.formatter.preformatted(1) + \ |
| 815 | | self.formatter.text(s_word + ' (-)') |
| 816 | | else: |
| 817 | | self.in_pre = 1 |
| 818 | | return '' |
| 819 | | |
| 820 | | def _pre_repl(self, word): |
| 821 | | """Handle code displays.""" |
| 822 | | word = word.strip() |
| 823 | | if word == '{{{' and not self.in_pre: |
| 824 | | self.in_pre = 3 |
| 825 | | return self._closeP() + self.formatter.preformatted(self.in_pre) |
| 826 | | elif word == '}}}' and self.in_pre: |
| 827 | | self.in_pre = 0 |
| 828 | | self.inhibit_p = 0 |
| 829 | | return self.formatter.preformatted(self.in_pre) |
| 830 | | return self.formatter.text(word) |
| 831 | | |
| 832 | | |
| 833 | | def _smiley_repl(self, word): |
| 834 | | """Handle smileys.""" |
| 835 | | return self.formatter.smiley(word) |
| 836 | | |
| 837 | | _smileyA_repl = _smiley_repl |
| 838 | | |
| 839 | | |
| 840 | | def _comment_repl(self, word): |
| 841 | | # if we are in a paragraph, we must close it so that normal text following |
| 842 | | # in the line below the comment will reopen a new paragraph. |
| 843 | | if self.formatter.in_p: |
| 844 | | self.formatter.paragraph(0) |
| 845 | | self.line_is_empty = 1 # markup following comment lines treats them as if they were empty |
| 846 | | return self.formatter.comment(word) |
| 847 | | |
| 848 | | def _closeP(self): |
| 849 | | if self.formatter.in_p: |
| 850 | | return self.formatter.paragraph(0) |
| 851 | | return '' |
| 852 | | |
| 853 | | def _macro_repl(self, word): |
| 854 | | """Handle macros ([[macroname]]).""" |
| 855 | | macro_name = word[2:-2] |
| 856 | | self.inhibit_p = 0 # 1 fixes UserPreferences, 0 fixes paragraph formatting for macros |
| 857 | | |
| 858 | | # check for arguments |
| 859 | | args = None |
| 860 | | if macro_name.count("("): |
| 861 | | macro_name, args = macro_name.split('(', 1) |
| 862 | | args = args[:-1] |
| 863 | | |
| 864 | | # create macro instance |
| 865 | | if self.macro is None: |
| 866 | | self.macro = wikimacro.Macro(self) |
| 867 | | return self.formatter.macro(self.macro, macro_name, args) |
| 868 | | |
| 869 | | def scan(self, scan_re, line): |
| 870 | | """ Scans one line |
| 871 | | |
| 872 | | Append text before match, invoke replace() with match, and add text after match. |
| 873 | | """ |
| 874 | | result = [] |
| 875 | | lastpos = 0 |
| 876 | | |
| 877 | | ###result.append(u'<span class="info">[scan: <tt>"%s"</tt>]</span>' % line) |
| 878 | | |
| 879 | | for match in scan_re.finditer(line): |
| 880 | | # Add text before the match |
| 881 | | if lastpos < match.start(): |
| 882 | | |
| 883 | | ###result.append(u'<span class="info">[add text before match: <tt>"%s"</tt>]</span>' % line[lastpos:match.start()]) |
| 884 | | |
| 885 | | if not (self.inhibit_p or self.in_pre or self.formatter.in_p): |
| 886 | | result.append(self.formatter.paragraph(1, css_class="line862")) |
| 887 | | result.append(self.formatter.text(line[lastpos:match.start()])) |
| 888 | | |
| 889 | | # Replace match with markup |
| 890 | | if not (self.inhibit_p or self.in_pre or self.formatter.in_p or |
| 891 | | self.in_table or self.in_list): |
| 892 | | result.append(self.formatter.paragraph(1, css_class="line867")) |
| 893 | | result.append(self.replace(match)) |
| 894 | | lastpos = match.end() |
| 895 | | |
| 896 | | ###result.append('<span class="info">[no match, add rest: <tt>"%s"<tt>]</span>' % line[lastpos:]) |
| 897 | | |
| 898 | | # Add paragraph with the remainder of the line |
| 899 | | if not (self.in_pre or self.in_li or self.in_dd or self.inhibit_p or |
| 900 | | self.formatter.in_p) and lastpos < len(line): |
| 901 | | result.append(self.formatter.paragraph(1, css_class="line874")) |
| 902 | | result.append(self.formatter.text(line[lastpos:])) |
| 903 | | return u''.join(result) |
| 904 | | |
| 905 | | def replace(self, match): |
| 906 | | """ Replace match using type name """ |
| 907 | | result = [] |
| 908 | | for type, hit in match.groupdict().items(): |
| 909 | | if hit is not None and type != "hmarker": |
| 910 | | |
| 911 | | ###result.append(u'<span class="info">[replace: %s: "%s"]</span>' % (type, hit)) |
| 912 | | if self.in_pre and type not in ['pre', 'ent']: |
| 913 | | return self.formatter.text(hit) |
| 914 | | else: |
| 915 | | # Open p for certain types |
| 916 | | if not (self.inhibit_p or self.formatter.in_p |
| 917 | | or self.in_pre or (type in self.no_new_p_before)): |
| 918 | | result.append(self.formatter.paragraph(1, css_class="line891")) |
| 919 | | |
| 920 | | # Get replace method and replece hit |
| 921 | | replace = getattr(self, '_' + type + '_repl') |
| 922 | | result.append(replace(hit)) |
| 923 | | return ''.join(result) |
| 924 | | else: |
| 925 | | # We should never get here |
| 926 | | import pprint |
| 927 | | raise Exception("Can't handle match " + `match` |
| 928 | | + "\n" + pprint.pformat(match.groupdict()) |
| 929 | | + "\n" + pprint.pformat(match.groups()) ) |
| 930 | | |
| 931 | | return "" |
| 932 | | |
| 933 | | def _line_anchordef(self): |
| 934 | | if self.line_anchors and not self.line_anchor_printed: |
| 935 | | self.line_anchor_printed = 1 |
| 936 | | return self.formatter.line_anchordef(self.lineno) |
| 937 | | else: |
| 938 | | return '' |
| 939 | | |
| 940 | | def format(self, formatter): |
| 941 | | """ For each line, scan through looking for magic |
| 942 | | strings, outputting verbatim any intervening text. |
| 943 | | """ |
| 944 | | self.formatter = formatter |
| 945 | | self.hilite_re = self.formatter.page.hilite_re |
| 946 | | |
| 947 | | # prepare regex patterns |
| 948 | | rules = self.formatting_rules.replace('\n', '|') |
| 949 | | if self.cfg.bang_meta: |
| 950 | | rules = ur'(?P<notword>!%(word_rule)s)|%(rules)s' % { |
| 951 | | 'word_rule': self.word_rule, |
| 952 | | 'rules': rules, |
| 953 | | } |
| 954 | | self.request.clock.start('compile_huge_and_ugly') |
| 955 | | scan_re = re.compile(rules, re.UNICODE) |
| 956 | | number_re = re.compile(self.ol_rule, re.UNICODE) |
| 957 | | term_re = re.compile(self.dl_rule, re.UNICODE) |
| 958 | | indent_re = re.compile("^\s*", re.UNICODE) |
| 959 | | eol_re = re.compile(r'\r?\n', re.UNICODE) |
| 960 | | self.request.clock.stop('compile_huge_and_ugly') |
| 961 | | |
| 962 | | # get text and replace TABs |
| 963 | | rawtext = self.raw.expandtabs() |
| 964 | | |
| 965 | | # go through the lines |
| 966 | | self.lineno = self.start_line |
| 967 | | self.lines = eol_re.split(rawtext) |
| 968 | | self.line_is_empty = 0 |
| 969 | | |
| 970 | | self.in_processing_instructions = 1 |
| 971 | | |
| 972 | | # Main loop |
| 973 | | for line in self.lines: |
| 974 | | self.lineno += 1 |
| 975 | | self.line_anchor_printed = 0 |
| 976 | | if not self.in_table: |
| 977 | | self.request.write(self._line_anchordef()) |
| 978 | | self.table_rowstart = 1 |
| 979 | | self.line_was_empty = self.line_is_empty |
| 980 | | self.line_is_empty = 0 |
| 981 | | self.first_list_item = 0 |
| 982 | | self.inhibit_p = 0 |
| 983 | | |
| 984 | | # ignore processing instructions |
| 985 | | if self.in_processing_instructions: |
| 986 | | found = False |
| 987 | | for pi in ("##", "#format", "#refresh", "#redirect", "#deprecated", |
| 988 | | "#pragma", "#form", "#acl", "#language"): |
| 989 | | if line.lower().startswith(pi): |
| 990 | | self.request.write(self.formatter.comment(line)) |
| 991 | | found = True |
| 992 | | break |
| 993 | | if not found: |
| 994 | | self.in_processing_instructions = 0 |
| 995 | | else: |
| 996 | | continue # do not parse this line |
| 997 | | if self.in_pre: |
| 998 | | # TODO: move this into function |
| 999 | | # still looking for processing instructions |
| 1000 | | # TODO: use strings for pre state, not numbers |
| 1001 | | if self.in_pre == 1: |
| 1002 | | self.processor = None |
| 1003 | | self.processor_is_parser = 0 |
| 1004 | | processor_name = '' |
| 1005 | | if (line.strip()[:2] == "#!"): |
| 1006 | | processor_name = line.strip()[2:].split()[0] |
| 1007 | | self.setProcessor(processor_name) |
| 1008 | | |
| 1009 | | if self.processor: |
| 1010 | | self.in_pre = 2 |
| 1011 | | self.colorize_lines = [line] |
| 1012 | | self.processor_name = processor_name |
| 1013 | | continue |
| 1014 | | else: |
| 1015 | | self.request.write(self._closeP() + |
| 1016 | | self.formatter.preformatted(1)) |
| 1017 | | self.in_pre = 3 |
| 1018 | | if self.in_pre == 2: |
| 1019 | | # processing mode |
| 1020 | | endpos = line.find("}}}") |
| 1021 | | if endpos == -1: |
| 1022 | | self.colorize_lines.append(line) |
| 1023 | | continue |
| 1024 | | if line[:endpos]: |
| 1025 | | self.colorize_lines.append(line[:endpos]) |
| 1026 | | |
| 1027 | | # Close p before calling processor |
| 1028 | | # TODO: do we really need this? |
| 1029 | | self.request.write(self._closeP()) |
| 1030 | | res = self.formatter.processor(self.processor_name, |
| 1031 | | self.colorize_lines, |
| 1032 | | self.processor_is_parser) |
| 1033 | | self.request.write(res) |
| 1034 | | del self.colorize_lines |
| 1035 | | self.in_pre = 0 |
| 1036 | | self.processor = None |
| 1037 | | |
| 1038 | | # send rest of line through regex machinery |
| 1039 | | line = line[endpos+3:] |
| 1040 | | if not line.strip(): # just in the case "}}} " when we only have blanks left... |
| 1041 | | continue |
| 1042 | | else: |
| 1043 | | # we don't have \n as whitespace any more |
| 1044 | | # This is the space between lines we join to one paragraph |
| 1045 | | line += ' ' |
| 1046 | | |
| 1047 | | # Paragraph break on empty lines |
| 1048 | | if not line.strip(): |
| 1049 | | if self.in_table: |
| 1050 | | self.request.write(self.formatter.table(0)) |
| 1051 | | self.request.write(self._line_anchordef()) |
| 1052 | | self.in_table = 0 |
| 1053 | | # CHANGE: removed check for not self.list_types |
| 1054 | | # p should close on every empty line |
| 1055 | | if self.formatter.in_p: |
| 1056 | | self.request.write(self.formatter.paragraph(0)) |
| 1057 | | self.line_is_empty = 1 |
| 1058 | | continue |
| 1059 | | |
| 1060 | | # Check indent level |
| 1061 | | indent = indent_re.match(line) |
| 1062 | | indlen = len(indent.group(0)) |
| 1063 | | indtype = "ul" |
| 1064 | | numtype = None |
| 1065 | | numstart = None |
| 1066 | | if indlen: |
| 1067 | | match = number_re.match(line) |
| 1068 | | if match: |
| 1069 | | numtype, numstart = match.group(0).strip().split('.') |
| 1070 | | numtype = numtype[0] |
| 1071 | | |
| 1072 | | if numstart and numstart[0] == "#": |
| 1073 | | numstart = int(numstart[1:]) |
| 1074 | | else: |
| 1075 | | numstart = None |
| 1076 | | |
| 1077 | | indtype = "ol" |
| 1078 | | else: |
| 1079 | | match = term_re.match(line) |
| 1080 | | if match: |
| 1081 | | indtype = "dl" |
| 1082 | | |
| 1083 | | # output proper indentation tags |
| 1084 | | self.request.write(self._indent_to(indlen, indtype, numtype, numstart)) |
| 1085 | | |
| 1086 | | # Table mode |
| 1087 | | # TODO: move into function? |
| 1088 | | if (not self.in_table and line[indlen:indlen + 2] == "||" |
| 1089 | | and line[-3:] == "|| " and len(line) >= 5 + indlen): |
| 1090 | | # Start table |
| 1091 | | if self.list_types and not self.in_li: |
| 1092 | | self.request.write(self.formatter.listitem(1, style="list-style-type:none")) |
| 1093 | | ## CHANGE: no automatic p on li |
| 1094 | | ##self.request.write(self.formatter.paragraph(1)) |
| 1095 | | self.in_li = 1 |
| 1096 | | |
| 1097 | | # CHANGE: removed check for self.in_li |
| 1098 | | # paragraph should end before table, always! |
| 1099 | | if self.formatter.in_p: |
| 1100 | | self.request.write(self.formatter.paragraph(0)) |
| 1101 | | attrs, attrerr = self._getTableAttrs(line[indlen+2:]) |
| 1102 | | self.request.write(self.formatter.table(1, attrs) + attrerr) |
| 1103 | | self.in_table = True # self.lineno |
| 1104 | | elif (self.in_table and not |
| 1105 | | # intra-table comments should not break a table |
| 1106 | | (line[:2] == "##" or |
| 1107 | | line[indlen:indlen + 2] == "||" and |
| 1108 | | line[-3:] == "|| " and |
| 1109 | | len(line) >= 5 + indlen)): |
| 1110 | | |
| 1111 | | # Close table |
| 1112 | | self.request.write(self.formatter.table(0)) |
| 1113 | | self.request.write(self._line_anchordef()) |
| 1114 | | self.in_table = 0 |
| 1115 | | |
| 1116 | | # Scan line, format and write |
| 1117 | | formatted_line = self.scan(scan_re, line) |
| 1118 | | self.request.write(formatted_line) |
| 1119 | | |
| 1120 | | if self.in_pre == 3: |
| 1121 | | self.request.write(self.formatter.linebreak()) |
| 1122 | | |
| 1123 | | # Close code displays, paragraphs, tables and open lists |
| 1124 | | self.request.write(self._undent()) |
| 1125 | | if self.in_pre: self.request.write(self.formatter.preformatted(0)) |
| 1126 | | if self.formatter.in_p: self.request.write(self.formatter.paragraph(0)) |
| 1127 | | if self.in_table: self.request.write(self.formatter.table(0)) |
| 1128 | | |
| 1129 | | # -------------------------------------------------------------------- |
| 1130 | | # Private helpers |
| 1131 | | |
| 1132 | | def setProcessor(self, name): |
| 1133 | | """ Set processer to either processor or parser named 'name' """ |
| 1134 | | cfg = self.request.cfg |
| 1135 | | try: |
| 1136 | | self.processor = wikiutil.importPlugin(cfg, "processor", name, |
| 1137 | | "process") |
| 1138 | | self.processor_is_parser = 0 |
| 1139 | | except wikiutil.PluginMissingError: |
| 1140 | | try: |
| 1141 | | self.processor = wikiutil.importPlugin(cfg, "parser", name, |
| 1142 | | "Parser") |
| 1143 | | self.processor_is_parser = 1 |
| 1144 | | except wikiutil.PluginMissingError: |
| 1145 | | self.processor = None |
| 1146 | | |
| 1147 | | |
| 1148 | | |
| 1149 | | class Request: |
| 1150 | | def __init__(self): |
| 1151 | | self._s = '' |
| 1152 | | def clock(self, *args, **kwds): |
| 1153 | | pass |
| 1154 | | def write(self, w): |
| 1155 | | print "writing ", w |
| 1156 | | self._s += w |
| 1157 | | def getText(self): |
| 1158 | | return '' |
| 1159 | | |
| 1160 | | class Page: |
| 1161 | | pass |
| 1162 | | |
| 1163 | | class Cfg: |
| 1164 | | pass |
| 1165 | | |
| 1166 | | |
| 1167 | | def wiki2html(s): |
| 1168 | | """ |
| 1169 | | INPUT: |
| 1170 | | s -- a string formatted using wiki markup |
| 1171 | | OUTPUT: |
| 1172 | | string -- formatted as HTML |
| 1173 | | """ |
| 1174 | | request = Request() |
| 1175 | | request.form = '' |
| 1176 | | request.rootpage = Page() |
| 1177 | | request.cfg = Cfg() |
| 1178 | | request.cfg.siteid = '' |
| 1179 | | request.cfg.data_underlay_dir = '' |
| 1180 | | request._page_headings = '' |
| 1181 | | |