Automatic transformation of wiki content using contexts
The idea came during creation of a wiki version of a manual with lots of specific terms. For ease of reading it was supposed to make all those terms with hyper links to glossary, but soon enough it became clear that it is quite difficult to manage all those hyper links in wiki markup and keep them in order. That is when the idea of a context came as a desired solution.
Basically, it is supposed to work like this:
Somewhere there is a context defined in terms of transformations (like google -> [http://www.google.com google]).
- Then there is a way to mark the wiki page as being related to the specific context, and it will make it to be rendered using context transformations.
Unable to find existing solutions I took a first step to achieve the desired functionality by myself.
I am new to moin and python, so feel free to correct me.
This is my current approach to the solution:
Contexts are defined inside processor regions as key value pairs, where key is a regular expression and value is a string to be used with MatchObject.expand command. Any wiki page can be used to define them like this:
(?P<sample_bug>(^|\s+)(BUG-)(?P<sample_bug_id>\d+)) \2[http://bugtracker.com/\3\4 \3\4] (?P<sample_logo>&logo;) [[ImageLink(MainPage/Logo.jpg)]]
To apply the context, wiki page should have a wiki format pi with a context parameter (context="WikiPage:ContextName") like this: #format wiki context="Contexts/Sample:Sample"
All it takes to make it work is a modified version of wiki parser:
1) additions
def find_blocks(source, blockStart=ur'{{{', blockEnd=ur'}}}', start=0, end=-1, limit=-1): blocks = []; if end < 0: end = len(source) s = start; while True: if limit == 0: break s = source.find(blockStart, s, end) if s < 0: break s = s + len(blockStart) i = 1; e = 0; t = s while i > 0: s1 = source.find(blockStart, t, end) e = source.find(blockEnd, t, end) if e < 0: break elif s1 < 0 or e < s1: i = i - 1 t = e + len(blockEnd) else: i = i + 1 t = s1 + len(blockStart) if e >= 0: blocks.append((s, e - s)) if limit > 0: limit = limit - 1 return blocks class Context: """ Supports contexts defined in the following format (indentation is arbitrary): {{{#!context CONTEXT_NAME (regex_pattern1) substitution1 (regex_pattern2) substitution2 ... }}} """ __pattern = None __substitutions = [] def __init__(self, args, request, **kw): self.request = request ctx_re = re.compile(r'context=(?P<q>[\'"])(?P<src>.+?):(?P<ctx>.+?)(?P=q)') for arg in args.split(): c = arg and ctx_re.match(arg) if not c: continue src_name = wikiutil.AbsPageName(request, request.page.page_name, c.group('src')) src_page = Page(request, src_name) if not src_page.exists(): continue self.__parse_contexts(src_page.get_raw_body(), c.group('ctx').split(';')) def __parse_contexts(self, source, contexts): p = []; v = [] ctx_re = re.compile(r'(?P<context>#!context\s+(?P<name>.+)\s*)') for s,ln in find_blocks(source): ctx = ctx_re.match(source[s:]) if not ctx or not ctx.group('name') in contexts: continue block = source[s+len(ctx.group('context')):s+ln] t = [line.strip() for line in block.split('\n')] for i in range(len(t)): if t[i]: continue t[i:] = [] break c = len(p) p[c:] = t[::2] v[c:] = t[1::2] self.__substitutions = zip(p, v) self.__pattern = '|'.join(p) return def __sub(self, it): c = it.group(0) for p, s in self.__substitutions: r = re.compile(p) m = r.match(c) if m: return m.expand(s) return c def apply(self, target): if not self.__pattern: return target return re.sub(self.__pattern, self.__sub, target)
2) changes in Parser class:
def __init__(self, raw, request, **kw): # self.raw = raw self.raw = Context(kw.get('format_args',''), request).apply(raw)
That's it.
Planned enhancements:
- Avoid substitutions in the processor regions. Needs additional considerations. In some cases like CSV regions it can be still useful to have the substitutions work.
Apparently, I will be enhancing the solution according to my requirements, but I will be glad to have a powerful version of such feature in the mainstream version of the moin wiki.