MoinLight

moinformat/tree.py

70:ab7b40f5af6d
2018-07-17 Paul Boddie Attempt to fix and simplify opaque region handling.
     1 #!/usr/bin/env python     2      3 """     4 Moin wiki format document tree nodes.     5      6 Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 class Container:    23     24     "A container of document nodes."    25     26     def __init__(self, nodes):    27         self.nodes = nodes    28     29         # In principle, allow blocks within containers. Some nodes may forbid    30         # them to simplify the document structure.    31     32         self.allow_blocks = True    33     34     def append(self, node):    35         self.nodes.append(node)    36     37     def append_many(self, nodes):    38         for node in nodes:    39             self.append(node)    40     41     add = append    42     43     append_inline = append    44     45     def append_inline_many(self, nodes):    46         for node in nodes:    47             self.append_inline(node)    48     49     def empty(self):    50         return not self.nodes    51     52     def node(self, index):    53         try:    54             return self.nodes[index]    55         except IndexError:    56             return None    57     58     def normalise(self):    59     60         "Combine adjacent text nodes."    61     62         nodes = self.nodes    63         self.nodes = []    64         text = None    65     66         for node in nodes:    67     68             # Open a text node or merge text into an open node.    69     70             if isinstance(node, Text):    71                 if not text:    72                     text = node    73                 else:    74                     text.merge(node)    75     76             # Close any open text node and append the current node.    77     78             else:    79                 if text:    80                     self.append(text)    81                     text = None    82                 self.append(node)    83     84         # Add any open text node.    85     86         if text:    87             self.append(text)    88     89     def __str__(self):    90         return self.prettyprint()    91     92     def _prettyprint(self, l, indent=""):    93         for node in self.nodes:    94             l.append(node.prettyprint(indent + "  "))    95         return "\n".join(l)    96     97     def _to_string(self, out):    98         for node in self.nodes:    99             node.to_string(out)   100    101 class Region(Container):   102    103     "A region of the page."   104    105     def __init__(self, nodes, level=0, indent=0, type=None, transparent=True, extra=None):   106         Container.__init__(self, nodes)   107         self.level = level   108         self.indent = indent   109         self.type = type   110         self.transparent = transparent   111         self.extra = extra   112    113     def add(self, node):   114         last = self.node(-1)   115         if last and last.empty():   116             self.nodes[-1] = node   117         else:   118             self.append(node)   119    120     def append_inline(self, node):   121         if self.transparent:   122             self.nodes[-1].append(node)   123         else:   124             self.append(node)   125    126     def have_end(self, s):   127         return self.level and s.startswith("}") and self.level == len(s)   128    129     def __repr__(self):   130         return "Region(%r, %r, %r, %r, %r, %r)" % (self.nodes, self.level,   131             self.indent, self.type, self.transparent, self.extra)   132    133     def prettyprint(self, indent=""):   134         l = ["%sRegion: level=%d indent=%d type=%s extra=%r" % (indent,   135              self.level, self.indent, self.type, self.extra)]   136         return self._prettyprint(l, indent)   137    138     def to_string(self, out):   139         out.start_region(self.level, self.indent, self.type, self.extra)   140    141         # Obtain a serialiser for the region, if appropriate.   142    143         serialiser = out.formats and out.formats.get(self.type)   144         region_out = serialiser and serialiser(out.out, out.formats) or out   145    146         # Serialise the region.   147    148         self._to_string(region_out)   149    150         out.end_region(self.level, self.indent, self.type, self.extra)   151    152    153    154 class Block(Container):   155    156     "A block in the page."   157    158     def __repr__(self):   159         return "Block(%r)" % self.nodes   160    161     def prettyprint(self, indent=""):   162         l = ["%sBlock" % indent]   163         return self._prettyprint(l, indent)   164    165     def to_string(self, out):   166         out.start_block()   167         self._to_string(out)   168         out.end_block()   169    170 class DefItem(Container):   171    172     "A definition item."   173    174     def __init__(self, nodes, pad, extra):   175         Container.__init__(self, nodes)   176         self.pad = pad   177         self.extra = extra   178    179     def __repr__(self):   180         return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra)   181    182     def prettyprint(self, indent=""):   183         l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)]   184         return self._prettyprint(l, indent)   185    186     def to_string(self, out):   187         out.start_defitem(self.pad, self.extra)   188         self._to_string(out)   189         out.end_defitem(self.pad, self.extra)   190    191 class DefTerm(Container):   192    193     "A definition term."   194    195     def __init__(self, nodes, pad):   196         Container.__init__(self, nodes)   197         self.pad = pad   198    199     def __repr__(self):   200         return "DefTerm(%r, %r)" % (self.nodes, self.pad)   201    202     def prettyprint(self, indent=""):   203         l = ["%sDefTerm: pad=%r" % (indent, self.pad)]   204         return self._prettyprint(l, indent)   205    206     def to_string(self, out):   207         out.start_defterm(self.pad)   208         self._to_string(out)   209         out.end_defterm(self.pad)   210    211 class FontStyle(Container):   212    213     "Emphasised and/or strong text."   214    215     def __init__(self, nodes, emphasis=False, strong=False):   216         Container.__init__(self, nodes)   217         self.emphasis = emphasis   218         self.strong = strong   219    220     def close_emphasis(self):   221         if self.strong:   222             span = FontStyle(self.nodes, emphasis=True)   223             self.nodes = [span]   224             self.emphasis = False   225         return self.strong   226    227     def close_strong(self):   228         if self.emphasis:   229             span = FontStyle(self.nodes, strong=True)   230             self.nodes = [span]   231             self.strong = False   232         return self.emphasis   233    234     def __repr__(self):   235         return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong)   236    237     def prettyprint(self, indent=""):   238         l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)]   239         return self._prettyprint(l, indent)   240    241     def to_string(self, out):   242         if self.emphasis:   243             out.start_emphasis()   244         elif self.strong:   245             out.start_strong()   246         self._to_string(out)   247         if self.emphasis:   248             out.end_emphasis()   249         elif self.strong:   250             out.end_strong()   251    252 class Heading(Container):   253    254     "A heading."   255    256     def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""):   257         Container.__init__(self, nodes)   258         self.level = level   259         self.start_extra = start_extra   260         self.start_pad = start_pad   261         self.end_pad = end_pad   262         self.end_extra = end_extra   263    264     def __repr__(self):   265         return "Heading(%r, %d, %r, %r, %r, %r)" % (   266             self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)   267    268     def prettyprint(self, indent=""):   269         l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % (   270                 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)]   271         return self._prettyprint(l, indent)   272    273     def to_string(self, out):   274         out.start_heading(self.level, self.start_extra, self.start_pad)   275         self._to_string(out)   276         out.end_heading(self.level, self.end_pad, self.end_extra)   277    278 class List(Container):   279    280     "A list."   281    282     def __init__(self, nodes, indent, marker, num):   283         Container.__init__(self, nodes)   284         self.indent = indent   285         self.marker = marker   286         self.num = num   287    288     def __repr__(self):   289         return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num)   290    291     def prettyprint(self, indent=""):   292         l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)]   293         return self._prettyprint(l, indent)   294    295     def to_string(self, out):   296         out.start_list(self.indent, self.marker, self.num)   297         self._to_string(out)   298         out.end_list(self.indent, self.marker, self.num)   299    300 class ListItem(Container):   301    302     "A list item."   303    304     def __init__(self, nodes, indent, marker, space, num):   305         Container.__init__(self, nodes)   306         self.indent = indent   307         self.marker = marker   308         self.space = space   309         self.num = num   310    311         # Forbid blocks within list items for simpler structure.   312    313         self.allow_blocks = False   314    315     def __repr__(self):   316         return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num)   317    318     def prettyprint(self, indent=""):   319         l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)]   320         return self._prettyprint(l, indent)   321    322     def to_string(self, out):   323         out.start_listitem(self.indent, self.marker, self.space, self.num)   324         self._to_string(out)   325         out.end_listitem(self.indent, self.marker, self.space, self.num)   326    327 class TableAttrs(Container):   328    329     "A collection of table attributes."   330    331     def __repr__(self):   332         return "TableAttrs(%r)" % self.nodes   333    334     def prettyprint(self, indent=""):   335         l = ["%sTableAttrs:" % indent]   336         return self._prettyprint(l, indent)   337    338     def to_string(self, out):   339         out.start_table_attrs()   340         self._to_string(out)   341         out.end_table_attrs()   342    343 class Table(Container):   344    345     "A table."   346    347     def __repr__(self):   348         return "Table(%r)" % self.nodes   349    350     def prettyprint(self, indent=""):   351         l = ["%sTable:" % indent]   352         return self._prettyprint(l, indent)   353    354     def to_string(self, out):   355         out.start_table()   356         self._to_string(out)   357         out.end_table()   358    359 class TableCell(Container):   360    361     "A table cell."   362    363     def __init__(self, nodes, attrs=None):   364         Container.__init__(self, nodes)   365         self.attrs = attrs   366    367     def __repr__(self):   368         return "TableCell(%r, %r)" % (self.nodes, self.attrs)   369    370     def prettyprint(self, indent=""):   371         l = ["%sTableCell:" % indent]   372         return self._prettyprint(l, indent)   373    374     def to_string(self, out):   375         out.start_table_cell(self.attrs)   376         for node in self.nodes:   377             if node is not self.attrs:   378                 node.to_string(out)   379         out.end_table_cell()   380    381 class TableRow(Container):   382    383     "A table row."   384    385     def __init__(self, nodes, trailing=""):   386         Container.__init__(self, nodes)   387         self.trailing = trailing   388    389     def __repr__(self):   390         return "TableRow(%r, %r)" % (self.nodes, self.trailing)   391    392     def prettyprint(self, indent=""):   393         l = ["%sTableRow: trailing=%r" % (indent, self.trailing)]   394         return self._prettyprint(l, indent)   395    396     def to_string(self, out):   397         out.start_table_row()   398         self._to_string(out)   399         out.end_table_row(self.trailing)   400    401    402    403 class Inline(Container):   404    405     "Generic inline formatting."   406    407     def __repr__(self):   408         return "%s(%r)" % (self.__class__.__name__, self.nodes)   409    410     def prettyprint(self, indent=""):   411         l = ["%s%s" % (indent, self.__class__.__name__)]   412         return self._prettyprint(l, indent)   413    414 class Larger(Inline):   415    416     "Larger text."   417    418     def to_string(self, out):   419         out.start_larger()   420         self._to_string(out)   421         out.end_larger()   422    423 class Link(Container):   424    425     "Link details."   426    427     def __init__(self, nodes, target):   428         Container.__init__(self, nodes)   429         self.target = target   430    431     def __repr__(self):   432         return "Link(%r, %r)" % (self.nodes, self.target)   433    434     def prettyprint(self, indent=""):   435         l = ["%sLink: target=%r" % (indent, self.target)]   436         return self._prettyprint(l, indent)   437    438     def to_string(self, out):   439         out.start_link(self.target)   440         if self.nodes:   441             out.start_linktext()   442             self._to_string(out)   443             out.end_linktext()   444         out.end_link()   445    446 class Monospace(Inline):   447    448     "Monospaced text."   449    450     def to_string(self, out):   451         out.start_monospace()   452         self._to_string(out)   453         out.end_monospace()   454    455 class Smaller(Inline):   456    457     "Smaller text."   458    459     def to_string(self, out):   460         out.start_smaller()   461         self._to_string(out)   462         out.end_smaller()   463    464 class Strikethrough(Inline):   465    466     "Crossed-out text."   467    468     def to_string(self, out):   469         out.start_strikethrough()   470         self._to_string(out)   471         out.end_strikethrough()   472    473 class Subscript(Inline):   474    475     "Subscripted text."   476    477     def to_string(self, out):   478         out.start_subscript()   479         self._to_string(out)   480         out.end_subscript()   481    482 class Superscript(Inline):   483    484     "Superscripted text."   485    486     def to_string(self, out):   487         out.start_superscript()   488         self._to_string(out)   489         out.end_superscript()   490    491 class Underline(Inline):   492    493     "Underlined text."   494    495     def to_string(self, out):   496         out.start_underline()   497         self._to_string(out)   498         out.end_underline()   499    500    501    502 class Node:   503    504     "A document node without children."   505    506     def empty(self):   507         return False   508    509 class Break(Node):   510    511     "A paragraph break."   512    513     def __repr__(self):   514         return "Break()"   515    516     def prettyprint(self, indent=""):   517         return "%sBreak" % indent   518    519     def to_string(self, out):   520         out.break_()   521    522 class Rule(Node):   523    524     "A horizontal rule."   525    526     def __init__(self, length):   527         self.length = length   528    529     def __repr__(self):   530         return "Rule(%d)" % self.length   531    532     def prettyprint(self, indent=""):   533         return "%sRule: length=%d" % (indent, self.length)   534    535     def to_string(self, out):   536         out.rule(self.length)   537    538 class TableAttr(Node):   539    540     "A table attribute."   541    542     def __init__(self, name, value=None, concise=False, quote=None):   543         self.name = name   544         self.value = value   545         self.concise = concise   546         self.quote = quote   547    548     def __repr__(self):   549         return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote)   550    551     def prettyprint(self, indent=""):   552         return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote)   553    554     def to_string(self, out):   555         out.table_attr(self.name, self.value, self.concise, self.quote)   556    557 class Text(Node):   558    559     "A text node."   560    561     def __init__(self, s):   562         self.s = s   563    564     def empty(self):   565         return not self.s   566    567     def multiline(self):   568         return "\n" in self.s   569    570     def merge(self, text):   571         self.s += text.s   572    573     def __repr__(self):   574         return "Text(%r)" % self.s   575    576     def prettyprint(self, indent=""):   577         return "%sText: %r" % (indent, self.s)   578    579     def to_string(self, out):   580         out.text(self.s)   581    582 # vim: tabstop=4 expandtab shiftwidth=4