1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/moinformat/tree/moin.py Tue Jul 24 12:58:43 2018 +0200
1.3 @@ -0,0 +1,605 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Moin wiki format document tree nodes.
1.8 +
1.9 +Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +class Container:
1.26 +
1.27 + "A container of document nodes."
1.28 +
1.29 + def __init__(self, nodes):
1.30 + self.nodes = nodes
1.31 +
1.32 + # In principle, allow blocks within containers. Some nodes may forbid
1.33 + # them to simplify the document structure.
1.34 +
1.35 + self.allow_blocks = True
1.36 +
1.37 + def append(self, node):
1.38 + self.nodes.append(node)
1.39 +
1.40 + def append_many(self, nodes):
1.41 + for node in nodes:
1.42 + self.append(node)
1.43 +
1.44 + add = append
1.45 +
1.46 + append_inline = append
1.47 +
1.48 + def append_inline_many(self, nodes):
1.49 + for node in nodes:
1.50 + self.append_inline(node)
1.51 +
1.52 + def empty(self):
1.53 + return not self.nodes
1.54 +
1.55 + def node(self, index):
1.56 + try:
1.57 + return self.nodes[index]
1.58 + except IndexError:
1.59 + return None
1.60 +
1.61 + def normalise(self):
1.62 +
1.63 + "Combine adjacent text nodes."
1.64 +
1.65 + nodes = self.nodes
1.66 + self.nodes = []
1.67 + text = None
1.68 +
1.69 + for node in nodes:
1.70 +
1.71 + # Open a text node or merge text into an open node.
1.72 +
1.73 + if isinstance(node, Text):
1.74 + if not text:
1.75 + text = node
1.76 + else:
1.77 + text.merge(node)
1.78 +
1.79 + # Close any open text node and append the current node.
1.80 +
1.81 + else:
1.82 + if text:
1.83 + self.append(text)
1.84 + text = None
1.85 + self.append(node)
1.86 +
1.87 + # Add any open text node.
1.88 +
1.89 + if text:
1.90 + self.append(text)
1.91 +
1.92 + def __str__(self):
1.93 + return self.prettyprint()
1.94 +
1.95 + def _prettyprint(self, l, indent=""):
1.96 + for node in self.nodes:
1.97 + l.append(node.prettyprint(indent + " "))
1.98 + return "\n".join(l)
1.99 +
1.100 + def _to_string(self, out):
1.101 + for node in self.nodes:
1.102 + node.to_string(out)
1.103 +
1.104 +class Region(Container):
1.105 +
1.106 + "A region of the page."
1.107 +
1.108 + def __init__(self, nodes, level=0, indent=0, type=None, transparent=True, extra=None):
1.109 + Container.__init__(self, nodes)
1.110 + self.level = level
1.111 + self.indent = indent
1.112 + self.type = type
1.113 + self.transparent = transparent
1.114 + self.extra = extra
1.115 +
1.116 + def add(self, node):
1.117 + last = self.node(-1)
1.118 + if last and last.empty():
1.119 + self.nodes[-1] = node
1.120 + else:
1.121 + self.append(node)
1.122 +
1.123 + def append_inline(self, node):
1.124 + if self.transparent:
1.125 + self.nodes[-1].append(node)
1.126 + else:
1.127 + self.append(node)
1.128 +
1.129 + def have_end(self, s):
1.130 + return self.level and s.startswith("}") and self.level == len(s)
1.131 +
1.132 + def __repr__(self):
1.133 + return "Region(%r, %r, %r, %r, %r, %r)" % (self.nodes, self.level,
1.134 + self.indent, self.type, self.transparent, self.extra)
1.135 +
1.136 + def prettyprint(self, indent=""):
1.137 + l = ["%sRegion: level=%d indent=%d type=%s extra=%r" % (indent,
1.138 + self.level, self.indent, self.type, self.extra)]
1.139 + return self._prettyprint(l, indent)
1.140 +
1.141 + def to_string(self, out):
1.142 + out.start_region(self.level, self.indent, self.type, self.extra)
1.143 +
1.144 + # Obtain a serialiser for the region, if appropriate.
1.145 +
1.146 + serialiser = out.formats and out.formats.get(self.type)
1.147 + region_out = serialiser and serialiser(out.out, out.formats) or out
1.148 +
1.149 + # Serialise the region.
1.150 +
1.151 + self._to_string(region_out)
1.152 +
1.153 + out.end_region(self.level, self.indent, self.type, self.extra)
1.154 +
1.155 +
1.156 +
1.157 +# Block nodes.
1.158 +
1.159 +class Block(Container):
1.160 +
1.161 + "A block in the page."
1.162 +
1.163 + def __repr__(self):
1.164 + return "Block(%r)" % self.nodes
1.165 +
1.166 + def prettyprint(self, indent=""):
1.167 + l = ["%sBlock" % indent]
1.168 + return self._prettyprint(l, indent)
1.169 +
1.170 + def to_string(self, out):
1.171 + out.start_block()
1.172 + self._to_string(out)
1.173 + out.end_block()
1.174 +
1.175 +class DefItem(Container):
1.176 +
1.177 + "A definition item."
1.178 +
1.179 + def __init__(self, nodes, pad, extra):
1.180 + Container.__init__(self, nodes)
1.181 + self.pad = pad
1.182 + self.extra = extra
1.183 +
1.184 + def __repr__(self):
1.185 + return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra)
1.186 +
1.187 + def prettyprint(self, indent=""):
1.188 + l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)]
1.189 + return self._prettyprint(l, indent)
1.190 +
1.191 + def to_string(self, out):
1.192 + out.start_defitem(self.pad, self.extra)
1.193 + self._to_string(out)
1.194 + out.end_defitem(self.pad, self.extra)
1.195 +
1.196 +class DefTerm(Container):
1.197 +
1.198 + "A definition term."
1.199 +
1.200 + def __init__(self, nodes, pad):
1.201 + Container.__init__(self, nodes)
1.202 + self.pad = pad
1.203 +
1.204 + def __repr__(self):
1.205 + return "DefTerm(%r, %r)" % (self.nodes, self.pad)
1.206 +
1.207 + def prettyprint(self, indent=""):
1.208 + l = ["%sDefTerm: pad=%r" % (indent, self.pad)]
1.209 + return self._prettyprint(l, indent)
1.210 +
1.211 + def to_string(self, out):
1.212 + out.start_defterm(self.pad)
1.213 + self._to_string(out)
1.214 + out.end_defterm(self.pad)
1.215 +
1.216 +class FontStyle(Container):
1.217 +
1.218 + "Emphasised and/or strong text."
1.219 +
1.220 + def __init__(self, nodes, emphasis=False, strong=False):
1.221 + Container.__init__(self, nodes)
1.222 + self.emphasis = emphasis
1.223 + self.strong = strong
1.224 +
1.225 + def close_emphasis(self):
1.226 + if self.strong:
1.227 + span = FontStyle(self.nodes, emphasis=True)
1.228 + self.nodes = [span]
1.229 + self.emphasis = False
1.230 + return self.strong
1.231 +
1.232 + def close_strong(self):
1.233 + if self.emphasis:
1.234 + span = FontStyle(self.nodes, strong=True)
1.235 + self.nodes = [span]
1.236 + self.strong = False
1.237 + return self.emphasis
1.238 +
1.239 + def __repr__(self):
1.240 + return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong)
1.241 +
1.242 + def prettyprint(self, indent=""):
1.243 + l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)]
1.244 + return self._prettyprint(l, indent)
1.245 +
1.246 + def to_string(self, out):
1.247 + if self.emphasis:
1.248 + out.start_emphasis()
1.249 + elif self.strong:
1.250 + out.start_strong()
1.251 + self._to_string(out)
1.252 + if self.emphasis:
1.253 + out.end_emphasis()
1.254 + elif self.strong:
1.255 + out.end_strong()
1.256 +
1.257 +class Heading(Container):
1.258 +
1.259 + "A heading."
1.260 +
1.261 + def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""):
1.262 + Container.__init__(self, nodes)
1.263 + self.level = level
1.264 + self.start_extra = start_extra
1.265 + self.start_pad = start_pad
1.266 + self.end_pad = end_pad
1.267 + self.end_extra = end_extra
1.268 +
1.269 + def __repr__(self):
1.270 + return "Heading(%r, %d, %r, %r, %r, %r)" % (
1.271 + self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)
1.272 +
1.273 + def prettyprint(self, indent=""):
1.274 + l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % (
1.275 + indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)]
1.276 + return self._prettyprint(l, indent)
1.277 +
1.278 + def to_string(self, out):
1.279 + out.start_heading(self.level, self.start_extra, self.start_pad)
1.280 + self._to_string(out)
1.281 + out.end_heading(self.level, self.end_pad, self.end_extra)
1.282 +
1.283 +class List(Container):
1.284 +
1.285 + "A list."
1.286 +
1.287 + def __init__(self, nodes, indent, marker, num):
1.288 + Container.__init__(self, nodes)
1.289 + self.indent = indent
1.290 + self.marker = marker
1.291 + self.num = num
1.292 +
1.293 + def __repr__(self):
1.294 + return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num)
1.295 +
1.296 + def prettyprint(self, indent=""):
1.297 + l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)]
1.298 + return self._prettyprint(l, indent)
1.299 +
1.300 + def to_string(self, out):
1.301 + out.start_list(self.indent, self.marker, self.num)
1.302 + self._to_string(out)
1.303 + out.end_list(self.indent, self.marker, self.num)
1.304 +
1.305 +class ListItem(Container):
1.306 +
1.307 + "A list item."
1.308 +
1.309 + def __init__(self, nodes, indent, marker, space, num):
1.310 + Container.__init__(self, nodes)
1.311 + self.indent = indent
1.312 + self.marker = marker
1.313 + self.space = space
1.314 + self.num = num
1.315 +
1.316 + # Forbid blocks within list items for simpler structure.
1.317 +
1.318 + self.allow_blocks = False
1.319 +
1.320 + def __repr__(self):
1.321 + return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num)
1.322 +
1.323 + def prettyprint(self, indent=""):
1.324 + l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)]
1.325 + return self._prettyprint(l, indent)
1.326 +
1.327 + def to_string(self, out):
1.328 + out.start_listitem(self.indent, self.marker, self.space, self.num)
1.329 + self._to_string(out)
1.330 + out.end_listitem(self.indent, self.marker, self.space, self.num)
1.331 +
1.332 +class TableAttrs(Container):
1.333 +
1.334 + "A collection of table attributes."
1.335 +
1.336 + def __repr__(self):
1.337 + return "TableAttrs(%r)" % self.nodes
1.338 +
1.339 + def prettyprint(self, indent=""):
1.340 + l = ["%sTableAttrs:" % indent]
1.341 + return self._prettyprint(l, indent)
1.342 +
1.343 + def to_string(self, out):
1.344 + out.start_table_attrs()
1.345 + self._to_string(out)
1.346 + out.end_table_attrs()
1.347 +
1.348 +class Table(Container):
1.349 +
1.350 + "A table."
1.351 +
1.352 + def __repr__(self):
1.353 + return "Table(%r)" % self.nodes
1.354 +
1.355 + def prettyprint(self, indent=""):
1.356 + l = ["%sTable:" % indent]
1.357 + return self._prettyprint(l, indent)
1.358 +
1.359 + def to_string(self, out):
1.360 + out.start_table()
1.361 + self._to_string(out)
1.362 + out.end_table()
1.363 +
1.364 +class TableCell(Container):
1.365 +
1.366 + "A table cell."
1.367 +
1.368 + def __init__(self, nodes, attrs=None):
1.369 + Container.__init__(self, nodes)
1.370 + self.attrs = attrs
1.371 +
1.372 + def __repr__(self):
1.373 + return "TableCell(%r, %r)" % (self.nodes, self.attrs)
1.374 +
1.375 + def prettyprint(self, indent=""):
1.376 + l = ["%sTableCell:" % indent]
1.377 + return self._prettyprint(l, indent)
1.378 +
1.379 + def to_string(self, out):
1.380 + out.start_table_cell(self.attrs)
1.381 + for node in self.nodes:
1.382 + if node is not self.attrs:
1.383 + node.to_string(out)
1.384 + out.end_table_cell()
1.385 +
1.386 +class TableRow(Container):
1.387 +
1.388 + "A table row."
1.389 +
1.390 + def __init__(self, nodes, trailing=""):
1.391 + Container.__init__(self, nodes)
1.392 + self.trailing = trailing
1.393 +
1.394 + def __repr__(self):
1.395 + return "TableRow(%r, %r)" % (self.nodes, self.trailing)
1.396 +
1.397 + def prettyprint(self, indent=""):
1.398 + l = ["%sTableRow: trailing=%r" % (indent, self.trailing)]
1.399 + return self._prettyprint(l, indent)
1.400 +
1.401 + def to_string(self, out):
1.402 + out.start_table_row()
1.403 + self._to_string(out)
1.404 + out.end_table_row(self.trailing)
1.405 +
1.406 +
1.407 +
1.408 +# Inline nodes with children.
1.409 +
1.410 +class Inline(Container):
1.411 +
1.412 + "Generic inline formatting."
1.413 +
1.414 + def __repr__(self):
1.415 + return "%s(%r)" % (self.__class__.__name__, self.nodes)
1.416 +
1.417 + def prettyprint(self, indent=""):
1.418 + l = ["%s%s" % (indent, self.__class__.__name__)]
1.419 + return self._prettyprint(l, indent)
1.420 +
1.421 +class Larger(Inline):
1.422 +
1.423 + "Larger text."
1.424 +
1.425 + def to_string(self, out):
1.426 + out.start_larger()
1.427 + self._to_string(out)
1.428 + out.end_larger()
1.429 +
1.430 +class Link(Container):
1.431 +
1.432 + "Link details."
1.433 +
1.434 + def __init__(self, nodes, target):
1.435 + Container.__init__(self, nodes)
1.436 + self.target = target
1.437 +
1.438 + def __repr__(self):
1.439 + return "Link(%r, %r)" % (self.nodes, self.target)
1.440 +
1.441 + def prettyprint(self, indent=""):
1.442 + l = ["%sLink: target=%r" % (indent, self.target)]
1.443 + return self._prettyprint(l, indent)
1.444 +
1.445 + def to_string(self, out):
1.446 + out.start_link(self.target)
1.447 + if self.nodes:
1.448 + out.start_linktext()
1.449 + self._to_string(out)
1.450 + out.end_linktext()
1.451 + out.end_link()
1.452 +
1.453 +class Monospace(Inline):
1.454 +
1.455 + "Monospaced text."
1.456 +
1.457 + def to_string(self, out):
1.458 + out.start_monospace()
1.459 + self._to_string(out)
1.460 + out.end_monospace()
1.461 +
1.462 +class Smaller(Inline):
1.463 +
1.464 + "Smaller text."
1.465 +
1.466 + def to_string(self, out):
1.467 + out.start_smaller()
1.468 + self._to_string(out)
1.469 + out.end_smaller()
1.470 +
1.471 +class Strikethrough(Inline):
1.472 +
1.473 + "Crossed-out text."
1.474 +
1.475 + def to_string(self, out):
1.476 + out.start_strikethrough()
1.477 + self._to_string(out)
1.478 + out.end_strikethrough()
1.479 +
1.480 +class Subscript(Inline):
1.481 +
1.482 + "Subscripted text."
1.483 +
1.484 + def to_string(self, out):
1.485 + out.start_subscript()
1.486 + self._to_string(out)
1.487 + out.end_subscript()
1.488 +
1.489 +class Superscript(Inline):
1.490 +
1.491 + "Superscripted text."
1.492 +
1.493 + def to_string(self, out):
1.494 + out.start_superscript()
1.495 + self._to_string(out)
1.496 + out.end_superscript()
1.497 +
1.498 +class Underline(Inline):
1.499 +
1.500 + "Underlined text."
1.501 +
1.502 + def to_string(self, out):
1.503 + out.start_underline()
1.504 + self._to_string(out)
1.505 + out.end_underline()
1.506 +
1.507 +
1.508 +
1.509 +# Nodes without children.
1.510 +
1.511 +class Node:
1.512 +
1.513 + "A document node without children."
1.514 +
1.515 + def empty(self):
1.516 + return False
1.517 +
1.518 +class Break(Node):
1.519 +
1.520 + "A paragraph break."
1.521 +
1.522 + def __repr__(self):
1.523 + return "Break()"
1.524 +
1.525 + def prettyprint(self, indent=""):
1.526 + return "%sBreak" % indent
1.527 +
1.528 + def to_string(self, out):
1.529 + out.break_()
1.530 +
1.531 +class Macro(Node):
1.532 +
1.533 + "Macro details."
1.534 +
1.535 + def __init__(self, name, args):
1.536 + self.name = name
1.537 + self.args = args
1.538 +
1.539 + def __repr__(self):
1.540 + return "Macro(%r, %r)" % (self.name, self.args)
1.541 +
1.542 + def prettyprint(self, indent=""):
1.543 + return "%sMacro: name=%r args=%r" % (indent, self.name, self.args)
1.544 +
1.545 + def to_string(self, out):
1.546 + out.macro(self.name, self.args)
1.547 +
1.548 +class Rule(Node):
1.549 +
1.550 + "A horizontal rule."
1.551 +
1.552 + def __init__(self, length):
1.553 + self.length = length
1.554 +
1.555 + def __repr__(self):
1.556 + return "Rule(%d)" % self.length
1.557 +
1.558 + def prettyprint(self, indent=""):
1.559 + return "%sRule: length=%d" % (indent, self.length)
1.560 +
1.561 + def to_string(self, out):
1.562 + out.rule(self.length)
1.563 +
1.564 +class TableAttr(Node):
1.565 +
1.566 + "A table attribute."
1.567 +
1.568 + def __init__(self, name, value=None, concise=False, quote=None):
1.569 + self.name = name
1.570 + self.value = value
1.571 + self.concise = concise
1.572 + self.quote = quote
1.573 +
1.574 + def __repr__(self):
1.575 + return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote)
1.576 +
1.577 + def prettyprint(self, indent=""):
1.578 + return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote)
1.579 +
1.580 + def to_string(self, out):
1.581 + out.table_attr(self.name, self.value, self.concise, self.quote)
1.582 +
1.583 +class Text(Node):
1.584 +
1.585 + "A text node."
1.586 +
1.587 + def __init__(self, s):
1.588 + self.s = s
1.589 +
1.590 + def empty(self):
1.591 + return not self.s
1.592 +
1.593 + def multiline(self):
1.594 + return "\n" in self.s
1.595 +
1.596 + def merge(self, text):
1.597 + self.s += text.s
1.598 +
1.599 + def __repr__(self):
1.600 + return "Text(%r)" % self.s
1.601 +
1.602 + def prettyprint(self, indent=""):
1.603 + return "%sText: %r" % (indent, self.s)
1.604 +
1.605 + def to_string(self, out):
1.606 + out.text(self.s)
1.607 +
1.608 +# vim: tabstop=4 expandtab shiftwidth=4