# HG changeset patch # User Paul Boddie # Date 1493764366 -7200 # Node ID 4d0950edf5f278b928de630a98e682d230d11762 # Parent e35326be8a028bb0244267295bf4e96bed85dd83 Added initial table attribute support. diff -r e35326be8a02 -r 4d0950edf5f2 moinformat/__init__.py --- a/moinformat/__init__.py Tue May 02 16:39:30 2017 +0200 +++ b/moinformat/__init__.py Wed May 03 00:32:46 2017 +0200 @@ -21,8 +21,8 @@ from moinformat.tree import Block, Break, DefItem, DefTerm, FontStyle, Heading, \ Larger, ListItem, Monospace, Region, Rule, Smaller, \ - Subscript, Superscript, TableCell, TableRow, Text, \ - Underline + Subscript, Superscript, TableAttr, TableAttrs, \ + TableCell, TableRow, Text, Underline import re # Regular expressions. @@ -84,11 +84,25 @@ "listitemend" : r"^", # next line # Table contents: + "tableattrs" : r"<", "tablecell" : r"\|\|", "tableend" : r"(\s*?)^", # [ws...] next line + + # Table attributes: + "tableattrsend" : r">", + "halign" : r"([(:)])", + "valign" : r"([v^])", + "colour" : r"(\#[0-9A-F]{6})", + "colspan" : r"\|(\d+)", + "rowspan" : r"-(\d+)", + "width" : r"(\d+%)", + "attrname" : r"((?![-\d])[-\w]+)", # not-dash-or-digit dash-or-word-char... + "attrvalue" : r"""=(?P['"])(.*?)(?P=x)""", } -# Define inline pattern details. +# Define pattern details. + +table_pattern_names = ["attrname", "colour", "colspan", "halign", "rowspan", "tableattrsend", "valign", "width"] inline_pattern_names = ["fontstyle", "larger", "monospace", "smaller", "sub", "super", "underline"] @@ -277,6 +291,19 @@ raise StopIteration +def parse_attrname(items, attrs): + + "Handle an attribute name within 'attrs'." + + name = items.read_match() + attr = TableAttr(name) + + preceding = items.read_until(["attrvalue"], False) + if preceding == "": + attr.value = items.read_match(2) + + attrs.append(attr) + def parse_break(items, region): "Handle a paragraph break within 'region'." @@ -352,6 +379,14 @@ parse_region_details(items, span, inline_pattern_names) region.append_inline(span) +def parse_halign(items, attrs): + + "Handle horizontal alignment within 'attrs'." + + value = items.read_match() + attr = TableAttr("halign", value == "(" and "left" or value == ")" and "right" or "center", True) + attrs.append(attr) + def parse_heading(items, region): "Handle a heading." @@ -416,6 +451,14 @@ else: region.append_inline(Text(feature)) +def parse_table_attrs(items, cell): + + "Handle the start of table attributes within 'cell'." + + attrs = TableAttrs([]) + parse_region_details(items, attrs, table_pattern_names) + cell.attrs = attrs + def parse_table_row(items, region): "Handle the start of a table row within 'region'." @@ -424,7 +467,7 @@ while True: cell = TableCell([]) - parse_region_details(items, cell, ["tablecell", "tableend"]) + parse_region_details(items, cell, ["tableattrs", "tablecell", "tableend"]) # Handle the end of the row. @@ -457,11 +500,21 @@ row.append(cell) break + # A cell separator has been found. + row.append(cell) region.add(row) new_block(region) +def parse_valign(items, attrs): + + "Handle vertical alignment within 'attrs'." + + value = items.read_match() + attr = TableAttr("valign", value == "^" and "top" or "bottom", True) + attrs.append(attr) + # Inline formatting handlers. def parse_inline(items, region, cls, pattern_name): @@ -479,16 +532,34 @@ parse_super = lambda items, region: parse_inline(items, region, Superscript, "super") parse_underline = lambda items, region: parse_inline(items, region, Underline, "underline") +# Table attribute handlers. + +def parse_table_attr(items, attrs, pattern_name): + + "Handle a table attribute." + + value = items.read_match() + attrs.append(TableAttr(pattern_name, value, True)) + +parse_colour = lambda items, cell: parse_table_attr(items, cell, "colour") +parse_colspan = lambda items, cell: parse_table_attr(items, cell, "colspan") +parse_rowspan = lambda items, cell: parse_table_attr(items, cell, "rowspan") +parse_width = lambda items, cell: parse_table_attr(items, cell, "width") + # Pattern handlers. handlers = { None : end_region, + "attrname" : parse_attrname, "break" : parse_break, + "colour" : parse_colour, + "colspan" : parse_colspan, "defterm" : parse_defterm, "defterm_empty" : parse_defterm_empty, "deftermend" : end_region, "deftermsep" : end_region, "fontstyle" : parse_fontstyle, + "halign" : parse_halign, "heading" : parse_heading, "headingend" : parse_heading_end, "larger" : parse_larger, @@ -503,6 +574,7 @@ "monospaceend" : end_region, "regionstart" : parse_section, "regionend" : parse_section_end, + "rowspan" : parse_rowspan, "rule" : parse_rule, "smaller" : parse_smaller, "smallerend" : end_region, @@ -510,11 +582,15 @@ "subend" : end_region, "super" : parse_super, "superend" : end_region, + "tableattrs" : parse_table_attrs, + "tableattrsend" : end_region, "tablerow" : parse_table_row, "tablecell" : end_region, "tableend" : end_region, "underline" : parse_underline, "underlineend" : end_region, + "valign" : parse_valign, + "width" : parse_width, } def new_block(region): diff -r e35326be8a02 -r 4d0950edf5f2 moinformat/serialisers.py --- a/moinformat/serialisers.py Tue May 02 16:39:30 2017 +0200 +++ b/moinformat/serialisers.py Wed May 03 00:32:46 2017 +0200 @@ -19,7 +19,17 @@ this program. If not, see . """ -from cgi import escape +def escape_text(s): + + "Escape XML document text." + + return s.replace("&", "&").replace("<", "<").replace(">", ">") + +def escape_attr(s): + + "Escape XML document attribute." + + return escape_text(s).replace("'", "'").replace('"', """) class Serialiser: @@ -116,8 +126,15 @@ def end_superscript(self): self.out("^") - def start_table_cell(self): - pass + def start_table_attrs(self): + self.out("<") + + def end_table_attrs(self): + self.out(">") + + def start_table_cell(self, attrs): + if attrs and not attrs.empty(): + attrs.to_string(self) def end_table_cell(self): self.out("||") @@ -140,6 +157,17 @@ def rule(self, length): self.out("-" * length) + def table_attr(self, name, value, concise): + if concise: + if name == "colour": self.out(value) + elif name == "colspan": self.out("-%s" % value) + elif name == "halign" : self.out(value == "left" and "(" or value == "right" and ")" or ":") + elif name == "rowspan": self.out("|%s" % value) + elif name == "valign" : self.out(value == "top" and "^" or "v") + elif name == "width" : self.out(value) + else: + self.out("%s%s" % (escape_text(name), value is not None and "='%s'" % escape_attr(value) or "")) + def text(self, s): self.out(s) @@ -159,7 +187,7 @@ # NOTE: Encode type details for CSS. if type: - out("type-%s" % escape(type, True)) + out("type-%s" % escape_attr(type)) self.out("" % " ".join(l)) @@ -238,8 +266,17 @@ def end_superscript(self): self.out("") - def start_table_cell(self): - self.out("") + def start_table_attrs(self): + pass + + def end_table_attrs(self): + pass + + def start_table_cell(self, attrs): + self.out("") def end_table_cell(self): self.out("") @@ -262,8 +299,11 @@ def rule(self, length): self.out("
" % min(length, 10)) + def table_attr(self, name, value, concise): + self.out(" %s%s" % (escape_text(name), value is not None and "='%s'" % escape_attr(value) or "")) + def text(self, s): - self.out(escape(s)) + self.out(escape_text(s)) # Top-level functions. diff -r e35326be8a02 -r 4d0950edf5f2 moinformat/tree.py --- a/moinformat/tree.py Tue May 02 16:39:30 2017 +0200 +++ b/moinformat/tree.py Wed May 03 00:32:46 2017 +0200 @@ -284,19 +284,41 @@ self._to_string(out) out.end_listitem(self.indent, self.marker) +class TableAttrs(Container): + + "A collection of table attributes." + + def __repr__(self): + return "TableAttrs(%r)" % self.nodes + + def prettyprint(self, indent=""): + l = ["%sTableAttrs:" % indent] + return self._prettyprint(l, indent) + + def to_string(self, out): + out.start_table_attrs() + self._to_string(out) + out.end_table_attrs() + class TableCell(Container): "A table cell." + def __init__(self, nodes, attrs=None): + Container.__init__(self, nodes) + self.attrs = attrs + def __repr__(self): - return "TableCell(%r)" % self.nodes + return "TableCell(%r, %f)" % (self.nodes, self.attrs) def prettyprint(self, indent=""): l = ["%sTableCell:" % indent] + if self.attrs: + l.append(self.attrs.prettyprint(indent + " ")) return self._prettyprint(l, indent) def to_string(self, out): - out.start_table_cell() + out.start_table_cell(self.attrs) self._to_string(out) out.end_table_cell() @@ -420,11 +442,29 @@ return "Rule(%d)" % self.length def prettyprint(self, indent=""): - return "%sRule: %d" % (indent, self.length) + return "%sRule: length=%d" % (indent, self.length) def to_string(self, out): out.rule(self.length) +class TableAttr(Node): + + "A table attribute." + + def __init__(self, name, value=None, concise=False): + self.name = name + self.value = value + self.concise = concise + + def __repr__(self): + return "TableAttr(%r, %r, %r)" % (self.name, self.value, self.concise) + + def prettyprint(self, indent=""): + return "%sTableAttr: name=%r value=%r concise=%r" % (indent, self.name, self.value, self.concise) + + def to_string(self, out): + out.table_attr(self.name, self.value, self.concise) + class Text(Node): "A text node." diff -r e35326be8a02 -r 4d0950edf5f2 tests/test_parser.py --- a/tests/test_parser.py Tue May 02 16:39:30 2017 +0200 +++ b/tests/test_parser.py Wed May 03 00:32:46 2017 +0200 @@ -112,8 +112,8 @@ """) sl.append("""\ -|| Cell 1 || Cell 2 || -|| Cell 3 || Cell 4 || +||<20%|2-2)^> Cell 1 || Cell 2 || +|| Cell 3 ||<#FF0000 width="15%"> Cell 4 || || Not a table || Also not a table