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:

  1. Somewhere there is a context defined in terms of transformations (like google -> [http://www.google.com google]).

  2. 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:

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.


CategoryFeatureRequest

MoinMoin: FeatureRequests/ContextTransformation (last edited 2009-09-05 15:55:27 by ReimarBauer)