# -*- coding: utf-8 -*-
#
# Copyright (C) 2003-2009 Edgewall Software
# Copyright (C) 2003-2005 Jonas Borgström
" % to_unicode(text)
return text
class Formatter(object):
"""Base Wiki formatter.
Parses and formats wiki text, in a given `Context`.
"""
flavor = 'default'
# 0.10 compatibility
INTERTRAC_SCHEME = WikiParser.INTERTRAC_SCHEME
QUOTED_STRING = WikiParser.QUOTED_STRING
LINK_SCHEME = WikiParser.LINK_SCHEME
def __init__(self, env, context):
"""Note: `req` is still temporarily used."""
self.env = env
self.context = context
self.req = context.req
self.href = context.href
self.resource = context.resource
self.perm = context.perm
self.db = self.env.get_db_cnx() # FIXME: remove
self.wiki = WikiSystem(self.env)
self.wikiparser = WikiParser(self.env)
self._anchors = {}
self._open_tags = []
def split_link(self, target):
"""Split a target along "?" and "#" in `(path, query, fragment)`."""
query = fragment = ''
idx = target.find('#')
if idx >= 0:
target, fragment = target[:idx], target[idx:]
idx = target.find('?')
if idx >= 0:
target, query = target[:idx], target[idx:]
return (target, query, fragment)
# -- Pre- IWikiSyntaxProvider rules (Font styles)
def tag_open_p(self, tag):
"""Do we currently have any open tag with `tag` as end-tag?"""
return tag in self._open_tags
def close_tag(self, tag):
tmp = ''
for i in xrange(len(self._open_tags)-1, -1, -1):
tmp += self._open_tags[i][1]
if self._open_tags[i][1] == tag:
del self._open_tags[i]
for j in xrange(i, len(self._open_tags)):
tmp += self._open_tags[j][0]
break
return tmp
def open_tag(self, open, close):
self._open_tags.append((open, close))
def simple_tag_handler(self, match, open_tag, close_tag):
"""Generic handler for simple binary style tags"""
if self.tag_open_p((open_tag, close_tag)):
return self.close_tag(close_tag)
else:
self.open_tag(open_tag, close_tag)
return open_tag
def _bolditalic_formatter(self, match, fullmatch):
italic = ('', '')
italic_open = self.tag_open_p(italic)
tmp = ''
if italic_open:
tmp += italic[1]
self.close_tag(italic[1])
tmp += self._bold_formatter(match, fullmatch)
if not italic_open:
tmp += italic[0]
self.open_tag(*italic)
return tmp
def _bold_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '', '')
def _italic_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '', '')
def _underline_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '',
'')
def _strike_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '', '')
def _subscript_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '', '')
def _superscript_formatter(self, match, fullmatch):
return self.simple_tag_handler(match, '', '')
def _inlinecode_formatter(self, match, fullmatch):
return tag.tt(fullmatch.group('inline'))
def _inlinecode2_formatter(self, match, fullmatch):
return tag.tt(fullmatch.group('inline2'))
# -- Post- IWikiSyntaxProvider rules
# E-mails
def _email_formatter(self, match, fullmatch):
from trac.web.chrome import Chrome
omatch = Chrome(self.env).format_emails(self.context, match)
if omatch == match: # not obfuscated, make a link
return self._make_mail_link('mailto:'+match, match)
else:
return omatch
# HTML escape of &, < and >
def _htmlescape_formatter(self, match, fullmatch):
return match == "&" and "&" or match == "<" and "<" or ">"
# Short form (shref) and long form (lhref) of TracLinks
def _unquote(self, text):
if text and text[0] in "'\"" and text[0] == text[-1]:
return text[1:-1]
else:
return text
def _shref_formatter(self, match, fullmatch):
ns = fullmatch.group('sns')
target = self._unquote(fullmatch.group('stgt'))
return self._make_link(ns, target, match, match, fullmatch)
def _lhref_formatter(self, match, fullmatch):
rel = fullmatch.group('rel')
ns = fullmatch.group('lns')
target = self._unquote(fullmatch.group('ltgt'))
label = fullmatch.group('label')
if not label: # e.g. `[http://target]` or `[wiki:target]`
if target:
if target.startswith('//'): # for `[http://target]`
label = ns+':'+target # use `http://target`
else: # for `wiki:target`
label = target # use only `target`
else: # e.g. `[search:]`
label = ns
else:
label = self._unquote(label)
if rel:
path, query, fragment = self.split_link(rel)
if path.startswith('//'):
path = '/' + path.lstrip('/')
elif path.startswith('/'):
path = self.href(path) or '/'
else:
resource = get_relative_resource(self.resource, path)
path = get_resource_url(self.env, resource, self.href)
if resource.id:
idx = path.find('?')
if idx >= 0:
if query:
query = path[idx:] + '&' + query.lstrip('?')
else:
query = path[idx:]
target = unicode(resource.id) + query + fragment
if resource.realm == 'wiki':
target = '/' + target # Avoid wiki page scoping
return self._make_link(resource.realm, target, match,
label or rel, fullmatch)
if '?' in path and query:
query = '&' + query.lstrip('?')
return tag.a(label or rel, href=path + query + fragment)
else:
return self._make_link(ns, target, match, label, fullmatch)
def _make_link(self, ns, target, match, label, fullmatch):
# first check for an alias defined in trac.ini
ns = self.env.config['intertrac'].get(ns, ns)
if ns in self.wikiparser.link_resolvers:
return self.wikiparser.link_resolvers[ns](self, ns, target,
escape(label, False))
elif target.startswith('//'):
return self._make_ext_link(ns+':'+target, label)
elif ns == "mailto":
from trac.web.chrome import Chrome
otarget = Chrome(self.env).format_emails(self.context, target)
olabel = Chrome(self.env).format_emails(self.context, label)
if (otarget, olabel) == (target, label):
return self._make_mail_link('mailto:'+target, label)
else:
return olabel or otarget
else:
if label == target and not fullmatch.group('label'):
# add ns for Inter* links when nothing is set
label = ns+':'+label
return self._make_intertrac_link(ns, target, label) or \
self._make_interwiki_link(ns, target, label) or \
escape(match)
def _make_intertrac_link(self, ns, target, label):
intertrac = self.env.config['intertrac']
url = intertrac.get(ns+'.url')
if not url and ns == 'trac':
url = 'http://trac.edgewall.org'
if url:
name = intertrac.get(ns+'.title', 'Trac project %s' % ns)
compat = intertrac.getbool(ns+'.compat', 'false')
# set `compat` default to False now that 0.10 is widely used
# TODO: remove compatibility code completely for 1.0 release
if compat:
sep = target.find(':')
if sep != -1:
url = '%s/%s/%s' % (url, target[:sep], target[sep + 1:])
else:
url = '%s/search?q=%s' % (url, unicode_quote_plus(target))
else:
url = '%s/intertrac/%s' % (url, unicode_quote(target))
if target:
title = '%s in %s' % (target, name)
else:
title = name
return self._make_ext_link(url, label, title)
else:
return None
def shorthand_intertrac_helper(self, ns, target, label, fullmatch):
if fullmatch: # short form
it_group = fullmatch.group('it_%s' % ns)
if it_group:
alias = it_group.strip()
intertrac = self.env.config['intertrac']
target = '%s:%s' % (ns, target[len(it_group):])
return self._make_intertrac_link(intertrac.get(alias, alias),
target, label) or label
return None
def _make_interwiki_link(self, ns, target, label):
from trac.wiki.interwiki import InterWikiMap
interwiki = InterWikiMap(self.env)
if ns in interwiki:
url, title = interwiki.url(ns, target)
return self._make_ext_link(url, label, title)
else:
return None
def _make_ext_link(self, url, text, title=''):
local_url = self.env.config.get('project', 'url') or \
(self.req or self.env).abs_href.base
if not url.startswith(local_url):
return tag.a(tag.span(u'\xa0', class_="icon"), text,
class_="ext-link", href=url, title=title or None)
else:
return tag.a(text, href=url, title=title or None)
def _make_mail_link(self, url, text, title=''):
return tag.a(tag.span(u'\xa0', class_="icon"), text,
class_="mail-link", href=url, title=title or None)
# WikiMacros
def _macro_formatter(self, match, fullmatch):
name = fullmatch.group('macroname')
if name.lower() == 'br':
return '
'
args = fullmatch.group('macroargs')
try:
macro = WikiProcessor(self, name)
return macro.process(args, in_paragraph=True)
except Exception, e:
self.env.log.error('Macro %s(%s) failed: %s' %
(name, args, exception_to_unicode(e, traceback=True)))
return system_message('Error: Macro %s(%s) failed' % (name, args),
e)
# Headings
def _parse_heading(self, match, fullmatch, shorten):
match = match.strip()
depth = min(len(fullmatch.group('hdepth')), 5)
anchor = fullmatch.group('hanchor') or ''
heading_text = match[depth+1:-depth-1-len(anchor)]
heading = format_to_oneliner(self.env, self.context, heading_text,
False)
if anchor:
anchor = anchor[1:]
else:
sans_markup = plaintext(heading, keeplinebreaks=False)
anchor = WikiParser._anchor_re.sub('', sans_markup)
if not anchor or anchor[0].isdigit() or anchor[0] in '.-':
# an ID must start with a Name-start character in XHTML
anchor = 'a' + anchor # keeping 'a' for backward compat
i = 1
anchor_base = anchor
while anchor in self._anchors:
anchor = anchor_base + str(i)
i += 1
self._anchors[anchor] = True
if shorten:
heading = format_to_oneliner(self.env, self.context, heading_text,
True)
return (depth, heading, anchor)
def _heading_formatter(self, match, fullmatch):
self.close_table()
self.close_paragraph()
self.close_indentation()
self.close_list()
self.close_def_list()
depth, heading, anchor = self._parse_heading(match, fullmatch, False)
self.out.write('
' % class_attr + os.linesep) if citation: for d in range(quote_depth+1, depth+1): open_one_quote(d) else: open_one_quote(depth) def close_quote(): self.close_table() self.close_paragraph() self._quote_stack.pop() self.out.write('' + os.linesep) quote_depth = self._get_quote_depth() if depth > quote_depth: self._set_tab(depth) tabstops = self._tabstops[::-1] while tabstops: tab = tabstops.pop() if tab > quote_depth: open_quote(tab) else: while self._quote_stack: deepest_offset = self._quote_stack[-1] if depth >= deepest_offset: break close_quote() if not citation and depth > 0: if self._quote_stack: old_offset = self._quote_stack[-1] if old_offset != depth: # adjust last depth self._quote_stack[-1] = depth else: open_quote(depth) if depth > 0: self.in_quote = True # Table def _last_table_cell_formatter(self, match, fullmatch): return '' def _table_cell_formatter(self, match, fullmatch): self.open_table() self.open_table_row() if self.in_table_cell: return '
' + os.linesep) self.paragraph_open = 1 def close_paragraph(self): if self.paragraph_open: while self._open_tags != []: self.out.write(self._open_tags.pop()[1]) self.out.write('
' + os.linesep) self.paragraph_open = 0 # Code blocks def handle_code_block(self, line): if line.strip() == WikiParser.STARTBLOCK: self.in_code_block += 1 if self.in_code_block == 1: self.code_processor = None self.code_buf = [] else: self.code_buf.append(line) if not self.code_processor: self.code_processor = WikiProcessor(self, 'default') elif line.strip() == WikiParser.ENDBLOCK: self.in_code_block -= 1 if self.in_code_block == 0 and self.code_processor: self.close_table() self.close_paragraph() if self.code_buf: self.code_buf.append('') code_text = os.linesep.join(self.code_buf) processed = self.code_processor.process(code_text) self.out.write(_markup_to_unicode(processed)) else: self.code_buf.append(line) elif not self.code_processor: match = WikiParser._processor_re.match(line) if match: name = match.group(1) args = WikiParser._processor_param_re.split(line[len(name):]) del args[::3] keys = [str(k) for k in args[::2]] # used as keyword parameters values = [(v and v[0] in '"\'' and [v[1:-1]] or [v])[0] for v in args[1::2]] args = dict(zip(keys, values)) if 'class' not in args: args['class'] = 'wikipage' self.code_processor = WikiProcessor(self, name, args) else: self.code_buf.append(line) self.code_processor = WikiProcessor(self, 'default') else: self.code_buf.append(line) def close_code_blocks(self): while self.in_code_block > 0: self.handle_code_block(WikiParser.ENDBLOCK) # -- Wiki engine def handle_match(self, fullmatch): for itype, match in fullmatch.groupdict().items(): if match and not itype in self.wikiparser.helper_patterns: # Check for preceding escape character '!' if match[0] == '!': return escape(match[1:]) if itype in self.wikiparser.external_handlers: external_handler = self.wikiparser.external_handlers[itype] return external_handler(self, match, fullmatch) else: internal_handler = getattr(self, '_%s_formatter' % itype) return internal_handler(match, fullmatch) def replace(self, fullmatch): """Replace one match with its corresponding expansion""" replacement = self.handle_match(fullmatch) if replacement: return _markup_to_unicode(replacement) def reset(self, source, out=None): self.source = source class NullOut(object): def write(self, data): pass self.out = out or NullOut() self._open_tags = [] self._list_stack = [] self._quote_stack = [] self._tabstops = [] self.in_code_block = 0 self.in_table = 0 self.in_def_list = 0 self.in_table_row = 0 self.in_table_cell = 0 self.paragraph_open = 0 def format(self, text, out=None, escape_newlines=False): self.reset(text, out) for line in text.splitlines(): # Handle code block if self.in_code_block or line.strip() == WikiParser.STARTBLOCK: self.handle_code_block(line) continue # Handle Horizontal ruler elif line[0:4] == '----': self.close_table() self.close_paragraph() self.close_indentation() self.close_list() self.close_def_list() self.out.write('