1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ImprovedTableParser.py Sun Jan 15 23:37:14 2012 +0100
1.3 @@ -0,0 +1,166 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - ImprovedTableParser library
1.7 +
1.8 + @copyright: 2012 by Paul Boddie <paul@boddie.org.uk>
1.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.10 +"""
1.11 +
1.12 +from MoinMoin import wikiutil
1.13 +from shlex import shlex
1.14 +from StringIO import StringIO
1.15 +import re
1.16 +
1.17 +# Regular expressions.
1.18 +
1.19 +syntax = {
1.20 + # At start of line:
1.21 + "rows" : (r"^==", re.MULTILINE), # ==
1.22 + # Within text:
1.23 + "columns" : (r"\|\|[ \t]*", 0), # || whitespace
1.24 + # At start of column text:
1.25 + "column" : (r"^\s*<(.*?)>\s*(.*)", re.DOTALL), # whitespace < attributes > whitespace
1.26 + }
1.27 +
1.28 +patterns = {}
1.29 +for name, (value, flags) in syntax.items():
1.30 + patterns[name] = re.compile(value, re.UNICODE | flags)
1.31 +
1.32 +# Functions.
1.33 +
1.34 +def parse(s):
1.35 +
1.36 + "Parse 's', returning a table definition."
1.37 +
1.38 + rows = []
1.39 + table_attrs = {}
1.40 +
1.41 + # Extract each row from the definition.
1.42 +
1.43 + for row_text in patterns["rows"].split(s):
1.44 + columns = []
1.45 +
1.46 + # Extract each column from the row.
1.47 +
1.48 + for text in patterns["columns"].split(row_text):
1.49 +
1.50 + # Extract the attribute and text sections.
1.51 +
1.52 + match = patterns["column"].search(text)
1.53 + if match:
1.54 + attribute_text, text = match.groups()
1.55 + columns.append((parseAttributes(attribute_text, True), text))
1.56 + else:
1.57 + columns.append(({}, text))
1.58 +
1.59 + # Extract row- and table-level attributes.
1.60 +
1.61 + row_attrs = {}
1.62 +
1.63 + if columns:
1.64 + attrs, column = columns[0]
1.65 +
1.66 + for name, value in attrs.items():
1.67 + if name.startswith("row"):
1.68 + row_attrs[name] = value
1.69 + del attrs[name]
1.70 + elif name.startswith("table"):
1.71 + table_attrs[name] = value
1.72 + del attrs[name]
1.73 +
1.74 + rows.append((row_attrs, columns))
1.75 +
1.76 + return table_attrs, rows
1.77 +
1.78 +def parseAttributes(s, escape=True):
1.79 +
1.80 + """
1.81 + Parse the table attributes string 's', returning a mapping of names to
1.82 + values. If 'escape' is set to a true value, the attributes will be suitable
1.83 + for use with the formatter API.
1.84 + """
1.85 +
1.86 + attrs = {}
1.87 + f = StringIO(s)
1.88 + name = None
1.89 + need_value = False
1.90 +
1.91 + for token in shlex(f):
1.92 +
1.93 + # Capture the name if needed.
1.94 +
1.95 + if name is None:
1.96 + name = escape and wikiutil.escape(token) or token
1.97 +
1.98 + # Detect either an equals sign or another name.
1.99 +
1.100 + elif not need_value:
1.101 + if token == "=":
1.102 + need_value = True
1.103 + else:
1.104 + attrs[name.lower()] = escape and "true" or True
1.105 + name = wikiutil.escape(token)
1.106 +
1.107 + # Otherwise, capture a value.
1.108 +
1.109 + else:
1.110 + # Quoting of attributes done similarly to parseAttributes.
1.111 +
1.112 + if escape and token:
1.113 + if token[0] in ("'", '"'):
1.114 + token = wikiutil.escape(token)
1.115 + else:
1.116 + token = '"%s"' % wikiutil.escape(token, 1)
1.117 +
1.118 + attrs[name.lower()] = token
1.119 + name = None
1.120 + need_value = False
1.121 +
1.122 + return attrs
1.123 +
1.124 +# Formatting of embedded content.
1.125 +# NOTE: Borrowed from EventAggregator.
1.126 +
1.127 +def getParserClass(request, format):
1.128 +
1.129 + """
1.130 + Return a parser class using the 'request' for the given 'format', returning
1.131 + a plain text parser if no parser can be found for the specified 'format'.
1.132 + """
1.133 +
1.134 + try:
1.135 + return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain")
1.136 + except wikiutil.PluginMissingError:
1.137 + return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain")
1.138 +
1.139 +def formatText(text, request, fmt):
1.140 +
1.141 + "Format the given 'text' using the specified 'request' and formatter 'fmt'."
1.142 +
1.143 + parser_cls = getParserClass(request, request.page.pi["format"])
1.144 + parser = parser_cls(text, request, line_anchors=False)
1.145 + return request.redirectedOutput(parser.format, fmt, inhibit_p=True)
1.146 +
1.147 +# Common formatting functions.
1.148 +
1.149 +def formatTable(text, request, fmt):
1.150 +
1.151 + "Format the given 'text' using the specified 'request' and formatter 'fmt'."
1.152 +
1.153 + attrs, table = parse(text)
1.154 +
1.155 + request.write(fmt.table(1, attrs))
1.156 +
1.157 + for row_attrs, columns in table:
1.158 + request.write(fmt.table_row(1, row_attrs))
1.159 +
1.160 + for column_attrs, column_text in columns:
1.161 + request.write(fmt.table_cell(1, column_attrs))
1.162 + request.write(formatText(column_text, request, fmt))
1.163 + request.write(fmt.table_cell(0))
1.164 +
1.165 + request.write(fmt.table_row(0))
1.166 +
1.167 + request.write(fmt.table(0))
1.168 +
1.169 +# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/parsers/table.py Sun Jan 15 23:37:14 2012 +0100
2.3 @@ -0,0 +1,43 @@
2.4 +# -*- coding: iso-8859-1 -*-
2.5 +"""
2.6 + MoinMoin - table (ImprovedTableParser)
2.7 +
2.8 + @copyright: 2012 by Paul Boddie <paul@boddie.org.uk>
2.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
2.10 +"""
2.11 +
2.12 +from ImprovedTableParser import *
2.13 +
2.14 +Dependencies = ["pages"]
2.15 +
2.16 +# Parser support.
2.17 +
2.18 +class Parser:
2.19 +
2.20 + "Support a more flexible table syntax."
2.21 +
2.22 + Dependencies = Dependencies
2.23 + extensions = []
2.24 +
2.25 + def __init__(self, raw, request, **kw):
2.26 +
2.27 + """
2.28 + Initialise the parser with the given 'raw' data, 'request' and any
2.29 + keyword arguments that may have been supplied.
2.30 + """
2.31 +
2.32 + self.raw = raw
2.33 + self.request = request
2.34 + self.attrs = parseAttributes(kw.get("format_args", ""), False)
2.35 +
2.36 + def format(self, fmt):
2.37 +
2.38 + "Format a table using the given formatter 'fmt'."
2.39 +
2.40 + request = self.request
2.41 + page = request.page
2.42 + _ = request.getText
2.43 +
2.44 + formatTable(self.raw, request, fmt)
2.45 +
2.46 +# vim: tabstop=4 expandtab shiftwidth=4
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/setup.py Sun Jan 15 23:37:14 2012 +0100
3.3 @@ -0,0 +1,13 @@
3.4 +#! /usr/bin/env python
3.5 +
3.6 +from distutils.core import setup
3.7 +
3.8 +setup(
3.9 + name = "ImprovedTableParser",
3.10 + description = "Support a more flexible table syntax in MoinMoin",
3.11 + author = "Paul Boddie",
3.12 + author_email = "paul@boddie.org.uk",
3.13 + url = "http://moinmo.in/ParserMarket/ImprovedTableParser",
3.14 + version = "0.1",
3.15 + py_modules = ["ImprovedTableParser"]
3.16 + )
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/tests/test_table.py Sun Jan 15 23:37:14 2012 +0100
4.3 @@ -0,0 +1,38 @@
4.4 +#!/usr/bin/env python
4.5 +
4.6 +from ImprovedTableParser import parse
4.7 +
4.8 +table = """<tablestyle="border: 2px solid red; border-collapse: collapse;" rowstyle="background-color: #f77;">
4.9 +Test||Outcome||Comments
4.10 +==
4.11 +Colspan
4.12 +||<colspan="2"> Test a column with attributes
4.13 +==
4.14 +Normal || <style="
4.15 +text-align: center" colspan=1> Three columns || Adjacent column
4.16 +==
4.17 +Split
4.18 +|| Three columns
4.19 +|| Separate lines
4.20 +==
4.21 + * Item #1
4.22 + * Item #2
4.23 +||
4.24 + * Item #A
4.25 +|| Not a list
4.26 +"""
4.27 +
4.28 +attrs, rows = parse(table)
4.29 +
4.30 +print table
4.31 +print attrs
4.32 +print rows
4.33 +print len(rows) == 5, ": length is", len(rows), "==", 5
4.34 +print
4.35 +for (row_attrs, columns), expected in zip(rows, [3, 2, 3, 3, 3]):
4.36 + print row_attrs
4.37 + print columns
4.38 + print len(columns) == expected, ": length is", len(columns), "==", expected
4.39 + print
4.40 +
4.41 +# vim: tabstop=4 expandtab shiftwidth=4