# HG changeset patch # User Paul Boddie # Date 1326667034 -3600 # Node ID 0042f967eae05e4e95a33c12aef7e293e5444c48 A table parser offering a more flexible syntax than the standard Moin parser. diff -r 000000000000 -r 0042f967eae0 ImprovedTableParser.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ImprovedTableParser.py Sun Jan 15 23:37:14 2012 +0100 @@ -0,0 +1,166 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - ImprovedTableParser library + + @copyright: 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinMoin import wikiutil +from shlex import shlex +from StringIO import StringIO +import re + +# Regular expressions. + +syntax = { + # At start of line: + "rows" : (r"^==", re.MULTILINE), # == + # Within text: + "columns" : (r"\|\|[ \t]*", 0), # || whitespace + # At start of column text: + "column" : (r"^\s*<(.*?)>\s*(.*)", re.DOTALL), # whitespace < attributes > whitespace + } + +patterns = {} +for name, (value, flags) in syntax.items(): + patterns[name] = re.compile(value, re.UNICODE | flags) + +# Functions. + +def parse(s): + + "Parse 's', returning a table definition." + + rows = [] + table_attrs = {} + + # Extract each row from the definition. + + for row_text in patterns["rows"].split(s): + columns = [] + + # Extract each column from the row. + + for text in patterns["columns"].split(row_text): + + # Extract the attribute and text sections. + + match = patterns["column"].search(text) + if match: + attribute_text, text = match.groups() + columns.append((parseAttributes(attribute_text, True), text)) + else: + columns.append(({}, text)) + + # Extract row- and table-level attributes. + + row_attrs = {} + + if columns: + attrs, column = columns[0] + + for name, value in attrs.items(): + if name.startswith("row"): + row_attrs[name] = value + del attrs[name] + elif name.startswith("table"): + table_attrs[name] = value + del attrs[name] + + rows.append((row_attrs, columns)) + + return table_attrs, rows + +def parseAttributes(s, escape=True): + + """ + Parse the table attributes string 's', returning a mapping of names to + values. If 'escape' is set to a true value, the attributes will be suitable + for use with the formatter API. + """ + + attrs = {} + f = StringIO(s) + name = None + need_value = False + + for token in shlex(f): + + # Capture the name if needed. + + if name is None: + name = escape and wikiutil.escape(token) or token + + # Detect either an equals sign or another name. + + elif not need_value: + if token == "=": + need_value = True + else: + attrs[name.lower()] = escape and "true" or True + name = wikiutil.escape(token) + + # Otherwise, capture a value. + + else: + # Quoting of attributes done similarly to parseAttributes. + + if escape and token: + if token[0] in ("'", '"'): + token = wikiutil.escape(token) + else: + token = '"%s"' % wikiutil.escape(token, 1) + + attrs[name.lower()] = token + name = None + need_value = False + + return attrs + +# Formatting of embedded content. +# NOTE: Borrowed from EventAggregator. + +def getParserClass(request, format): + + """ + Return a parser class using the 'request' for the given 'format', returning + a plain text parser if no parser can be found for the specified 'format'. + """ + + try: + return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") + except wikiutil.PluginMissingError: + return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") + +def formatText(text, request, fmt): + + "Format the given 'text' using the specified 'request' and formatter 'fmt'." + + parser_cls = getParserClass(request, request.page.pi["format"]) + parser = parser_cls(text, request, line_anchors=False) + return request.redirectedOutput(parser.format, fmt, inhibit_p=True) + +# Common formatting functions. + +def formatTable(text, request, fmt): + + "Format the given 'text' using the specified 'request' and formatter 'fmt'." + + attrs, table = parse(text) + + request.write(fmt.table(1, attrs)) + + for row_attrs, columns in table: + request.write(fmt.table_row(1, row_attrs)) + + for column_attrs, column_text in columns: + request.write(fmt.table_cell(1, column_attrs)) + request.write(formatText(column_text, request, fmt)) + request.write(fmt.table_cell(0)) + + request.write(fmt.table_row(0)) + + request.write(fmt.table(0)) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r 0042f967eae0 parsers/table.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parsers/table.py Sun Jan 15 23:37:14 2012 +0100 @@ -0,0 +1,43 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - table (ImprovedTableParser) + + @copyright: 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from ImprovedTableParser import * + +Dependencies = ["pages"] + +# Parser support. + +class Parser: + + "Support a more flexible table syntax." + + Dependencies = Dependencies + extensions = [] + + def __init__(self, raw, request, **kw): + + """ + Initialise the parser with the given 'raw' data, 'request' and any + keyword arguments that may have been supplied. + """ + + self.raw = raw + self.request = request + self.attrs = parseAttributes(kw.get("format_args", ""), False) + + def format(self, fmt): + + "Format a table using the given formatter 'fmt'." + + request = self.request + page = request.page + _ = request.getText + + formatTable(self.raw, request, fmt) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r 0042f967eae0 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Sun Jan 15 23:37:14 2012 +0100 @@ -0,0 +1,13 @@ +#! /usr/bin/env python + +from distutils.core import setup + +setup( + name = "ImprovedTableParser", + description = "Support a more flexible table syntax in MoinMoin", + author = "Paul Boddie", + author_email = "paul@boddie.org.uk", + url = "http://moinmo.in/ParserMarket/ImprovedTableParser", + version = "0.1", + py_modules = ["ImprovedTableParser"] + ) diff -r 000000000000 -r 0042f967eae0 tests/test_table.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_table.py Sun Jan 15 23:37:14 2012 +0100 @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +from ImprovedTableParser import parse + +table = """ +Test||Outcome||Comments +== +Colspan +|| Test a column with attributes +== +Normal || Three columns || Adjacent column +== +Split +|| Three columns +|| Separate lines +== + * Item #1 + * Item #2 +|| + * Item #A +|| Not a list +""" + +attrs, rows = parse(table) + +print table +print attrs +print rows +print len(rows) == 5, ": length is", len(rows), "==", 5 +print +for (row_attrs, columns), expected in zip(rows, [3, 2, 3, 3, 3]): + print row_attrs + print columns + print len(columns) == expected, ": length is", len(columns), "==", expected + print + +# vim: tabstop=4 expandtab shiftwidth=4