1.1 --- a/moinformat/__init__.py Tue May 02 16:39:30 2017 +0200
1.2 +++ b/moinformat/__init__.py Wed May 03 00:32:46 2017 +0200
1.3 @@ -21,8 +21,8 @@
1.4
1.5 from moinformat.tree import Block, Break, DefItem, DefTerm, FontStyle, Heading, \
1.6 Larger, ListItem, Monospace, Region, Rule, Smaller, \
1.7 - Subscript, Superscript, TableCell, TableRow, Text, \
1.8 - Underline
1.9 + Subscript, Superscript, TableAttr, TableAttrs, \
1.10 + TableCell, TableRow, Text, Underline
1.11 import re
1.12
1.13 # Regular expressions.
1.14 @@ -84,11 +84,25 @@
1.15 "listitemend" : r"^", # next line
1.16
1.17 # Table contents:
1.18 + "tableattrs" : r"<",
1.19 "tablecell" : r"\|\|",
1.20 "tableend" : r"(\s*?)^", # [ws...] next line
1.21 +
1.22 + # Table attributes:
1.23 + "tableattrsend" : r">",
1.24 + "halign" : r"([(:)])",
1.25 + "valign" : r"([v^])",
1.26 + "colour" : r"(\#[0-9A-F]{6})",
1.27 + "colspan" : r"\|(\d+)",
1.28 + "rowspan" : r"-(\d+)",
1.29 + "width" : r"(\d+%)",
1.30 + "attrname" : r"((?![-\d])[-\w]+)", # not-dash-or-digit dash-or-word-char...
1.31 + "attrvalue" : r"""=(?P<x>['"])(.*?)(?P=x)""",
1.32 }
1.33
1.34 -# Define inline pattern details.
1.35 +# Define pattern details.
1.36 +
1.37 +table_pattern_names = ["attrname", "colour", "colspan", "halign", "rowspan", "tableattrsend", "valign", "width"]
1.38
1.39 inline_pattern_names = ["fontstyle", "larger", "monospace", "smaller", "sub", "super", "underline"]
1.40
1.41 @@ -277,6 +291,19 @@
1.42
1.43 raise StopIteration
1.44
1.45 +def parse_attrname(items, attrs):
1.46 +
1.47 + "Handle an attribute name within 'attrs'."
1.48 +
1.49 + name = items.read_match()
1.50 + attr = TableAttr(name)
1.51 +
1.52 + preceding = items.read_until(["attrvalue"], False)
1.53 + if preceding == "":
1.54 + attr.value = items.read_match(2)
1.55 +
1.56 + attrs.append(attr)
1.57 +
1.58 def parse_break(items, region):
1.59
1.60 "Handle a paragraph break within 'region'."
1.61 @@ -352,6 +379,14 @@
1.62 parse_region_details(items, span, inline_pattern_names)
1.63 region.append_inline(span)
1.64
1.65 +def parse_halign(items, attrs):
1.66 +
1.67 + "Handle horizontal alignment within 'attrs'."
1.68 +
1.69 + value = items.read_match()
1.70 + attr = TableAttr("halign", value == "(" and "left" or value == ")" and "right" or "center", True)
1.71 + attrs.append(attr)
1.72 +
1.73 def parse_heading(items, region):
1.74
1.75 "Handle a heading."
1.76 @@ -416,6 +451,14 @@
1.77 else:
1.78 region.append_inline(Text(feature))
1.79
1.80 +def parse_table_attrs(items, cell):
1.81 +
1.82 + "Handle the start of table attributes within 'cell'."
1.83 +
1.84 + attrs = TableAttrs([])
1.85 + parse_region_details(items, attrs, table_pattern_names)
1.86 + cell.attrs = attrs
1.87 +
1.88 def parse_table_row(items, region):
1.89
1.90 "Handle the start of a table row within 'region'."
1.91 @@ -424,7 +467,7 @@
1.92
1.93 while True:
1.94 cell = TableCell([])
1.95 - parse_region_details(items, cell, ["tablecell", "tableend"])
1.96 + parse_region_details(items, cell, ["tableattrs", "tablecell", "tableend"])
1.97
1.98 # Handle the end of the row.
1.99
1.100 @@ -457,11 +500,21 @@
1.101 row.append(cell)
1.102 break
1.103
1.104 + # A cell separator has been found.
1.105 +
1.106 row.append(cell)
1.107
1.108 region.add(row)
1.109 new_block(region)
1.110
1.111 +def parse_valign(items, attrs):
1.112 +
1.113 + "Handle vertical alignment within 'attrs'."
1.114 +
1.115 + value = items.read_match()
1.116 + attr = TableAttr("valign", value == "^" and "top" or "bottom", True)
1.117 + attrs.append(attr)
1.118 +
1.119 # Inline formatting handlers.
1.120
1.121 def parse_inline(items, region, cls, pattern_name):
1.122 @@ -479,16 +532,34 @@
1.123 parse_super = lambda items, region: parse_inline(items, region, Superscript, "super")
1.124 parse_underline = lambda items, region: parse_inline(items, region, Underline, "underline")
1.125
1.126 +# Table attribute handlers.
1.127 +
1.128 +def parse_table_attr(items, attrs, pattern_name):
1.129 +
1.130 + "Handle a table attribute."
1.131 +
1.132 + value = items.read_match()
1.133 + attrs.append(TableAttr(pattern_name, value, True))
1.134 +
1.135 +parse_colour = lambda items, cell: parse_table_attr(items, cell, "colour")
1.136 +parse_colspan = lambda items, cell: parse_table_attr(items, cell, "colspan")
1.137 +parse_rowspan = lambda items, cell: parse_table_attr(items, cell, "rowspan")
1.138 +parse_width = lambda items, cell: parse_table_attr(items, cell, "width")
1.139 +
1.140 # Pattern handlers.
1.141
1.142 handlers = {
1.143 None : end_region,
1.144 + "attrname" : parse_attrname,
1.145 "break" : parse_break,
1.146 + "colour" : parse_colour,
1.147 + "colspan" : parse_colspan,
1.148 "defterm" : parse_defterm,
1.149 "defterm_empty" : parse_defterm_empty,
1.150 "deftermend" : end_region,
1.151 "deftermsep" : end_region,
1.152 "fontstyle" : parse_fontstyle,
1.153 + "halign" : parse_halign,
1.154 "heading" : parse_heading,
1.155 "headingend" : parse_heading_end,
1.156 "larger" : parse_larger,
1.157 @@ -503,6 +574,7 @@
1.158 "monospaceend" : end_region,
1.159 "regionstart" : parse_section,
1.160 "regionend" : parse_section_end,
1.161 + "rowspan" : parse_rowspan,
1.162 "rule" : parse_rule,
1.163 "smaller" : parse_smaller,
1.164 "smallerend" : end_region,
1.165 @@ -510,11 +582,15 @@
1.166 "subend" : end_region,
1.167 "super" : parse_super,
1.168 "superend" : end_region,
1.169 + "tableattrs" : parse_table_attrs,
1.170 + "tableattrsend" : end_region,
1.171 "tablerow" : parse_table_row,
1.172 "tablecell" : end_region,
1.173 "tableend" : end_region,
1.174 "underline" : parse_underline,
1.175 "underlineend" : end_region,
1.176 + "valign" : parse_valign,
1.177 + "width" : parse_width,
1.178 }
1.179
1.180 def new_block(region):
2.1 --- a/moinformat/serialisers.py Tue May 02 16:39:30 2017 +0200
2.2 +++ b/moinformat/serialisers.py Wed May 03 00:32:46 2017 +0200
2.3 @@ -19,7 +19,17 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 -from cgi import escape
2.8 +def escape_text(s):
2.9 +
2.10 + "Escape XML document text."
2.11 +
2.12 + return s.replace("&", "&").replace("<", "<").replace(">", ">")
2.13 +
2.14 +def escape_attr(s):
2.15 +
2.16 + "Escape XML document attribute."
2.17 +
2.18 + return escape_text(s).replace("'", "'").replace('"', """)
2.19
2.20 class Serialiser:
2.21
2.22 @@ -116,8 +126,15 @@
2.23 def end_superscript(self):
2.24 self.out("^")
2.25
2.26 - def start_table_cell(self):
2.27 - pass
2.28 + def start_table_attrs(self):
2.29 + self.out("<")
2.30 +
2.31 + def end_table_attrs(self):
2.32 + self.out(">")
2.33 +
2.34 + def start_table_cell(self, attrs):
2.35 + if attrs and not attrs.empty():
2.36 + attrs.to_string(self)
2.37
2.38 def end_table_cell(self):
2.39 self.out("||")
2.40 @@ -140,6 +157,17 @@
2.41 def rule(self, length):
2.42 self.out("-" * length)
2.43
2.44 + def table_attr(self, name, value, concise):
2.45 + if concise:
2.46 + if name == "colour": self.out(value)
2.47 + elif name == "colspan": self.out("-%s" % value)
2.48 + elif name == "halign" : self.out(value == "left" and "(" or value == "right" and ")" or ":")
2.49 + elif name == "rowspan": self.out("|%s" % value)
2.50 + elif name == "valign" : self.out(value == "top" and "^" or "v")
2.51 + elif name == "width" : self.out(value)
2.52 + else:
2.53 + self.out("%s%s" % (escape_text(name), value is not None and "='%s'" % escape_attr(value) or ""))
2.54 +
2.55 def text(self, s):
2.56 self.out(s)
2.57
2.58 @@ -159,7 +187,7 @@
2.59 # NOTE: Encode type details for CSS.
2.60
2.61 if type:
2.62 - out("type-%s" % escape(type, True))
2.63 + out("type-%s" % escape_attr(type))
2.64
2.65 self.out("<span class='%s'>" % " ".join(l))
2.66
2.67 @@ -238,8 +266,17 @@
2.68 def end_superscript(self):
2.69 self.out("</sup>")
2.70
2.71 - def start_table_cell(self):
2.72 - self.out("<td>")
2.73 + def start_table_attrs(self):
2.74 + pass
2.75 +
2.76 + def end_table_attrs(self):
2.77 + pass
2.78 +
2.79 + def start_table_cell(self, attrs):
2.80 + self.out("<td")
2.81 + if attrs and not attrs.empty():
2.82 + attrs.to_string(self)
2.83 + self.out(">")
2.84
2.85 def end_table_cell(self):
2.86 self.out("</td>")
2.87 @@ -262,8 +299,11 @@
2.88 def rule(self, length):
2.89 self.out("<hr style='height: %dpt' />" % min(length, 10))
2.90
2.91 + def table_attr(self, name, value, concise):
2.92 + self.out(" %s%s" % (escape_text(name), value is not None and "='%s'" % escape_attr(value) or ""))
2.93 +
2.94 def text(self, s):
2.95 - self.out(escape(s))
2.96 + self.out(escape_text(s))
2.97
2.98 # Top-level functions.
2.99
3.1 --- a/moinformat/tree.py Tue May 02 16:39:30 2017 +0200
3.2 +++ b/moinformat/tree.py Wed May 03 00:32:46 2017 +0200
3.3 @@ -284,19 +284,41 @@
3.4 self._to_string(out)
3.5 out.end_listitem(self.indent, self.marker)
3.6
3.7 +class TableAttrs(Container):
3.8 +
3.9 + "A collection of table attributes."
3.10 +
3.11 + def __repr__(self):
3.12 + return "TableAttrs(%r)" % self.nodes
3.13 +
3.14 + def prettyprint(self, indent=""):
3.15 + l = ["%sTableAttrs:" % indent]
3.16 + return self._prettyprint(l, indent)
3.17 +
3.18 + def to_string(self, out):
3.19 + out.start_table_attrs()
3.20 + self._to_string(out)
3.21 + out.end_table_attrs()
3.22 +
3.23 class TableCell(Container):
3.24
3.25 "A table cell."
3.26
3.27 + def __init__(self, nodes, attrs=None):
3.28 + Container.__init__(self, nodes)
3.29 + self.attrs = attrs
3.30 +
3.31 def __repr__(self):
3.32 - return "TableCell(%r)" % self.nodes
3.33 + return "TableCell(%r, %f)" % (self.nodes, self.attrs)
3.34
3.35 def prettyprint(self, indent=""):
3.36 l = ["%sTableCell:" % indent]
3.37 + if self.attrs:
3.38 + l.append(self.attrs.prettyprint(indent + " "))
3.39 return self._prettyprint(l, indent)
3.40
3.41 def to_string(self, out):
3.42 - out.start_table_cell()
3.43 + out.start_table_cell(self.attrs)
3.44 self._to_string(out)
3.45 out.end_table_cell()
3.46
3.47 @@ -420,11 +442,29 @@
3.48 return "Rule(%d)" % self.length
3.49
3.50 def prettyprint(self, indent=""):
3.51 - return "%sRule: %d" % (indent, self.length)
3.52 + return "%sRule: length=%d" % (indent, self.length)
3.53
3.54 def to_string(self, out):
3.55 out.rule(self.length)
3.56
3.57 +class TableAttr(Node):
3.58 +
3.59 + "A table attribute."
3.60 +
3.61 + def __init__(self, name, value=None, concise=False):
3.62 + self.name = name
3.63 + self.value = value
3.64 + self.concise = concise
3.65 +
3.66 + def __repr__(self):
3.67 + return "TableAttr(%r, %r, %r)" % (self.name, self.value, self.concise)
3.68 +
3.69 + def prettyprint(self, indent=""):
3.70 + return "%sTableAttr: name=%r value=%r concise=%r" % (indent, self.name, self.value, self.concise)
3.71 +
3.72 + def to_string(self, out):
3.73 + out.table_attr(self.name, self.value, self.concise)
3.74 +
3.75 class Text(Node):
3.76
3.77 "A text node."