MoinLight

moinformat/tree.py

76:04558a6a6219
2018-07-23 Paul Boddie Improved the comments slightly.
     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 # Block nodes.   155    156 class Block(Container):   157    158     "A block in the page."   159    160     def __repr__(self):   161         return "Block(%r)" % self.nodes   162    163     def prettyprint(self, indent=""):   164         l = ["%sBlock" % indent]   165         return self._prettyprint(l, indent)   166    167     def to_string(self, out):   168         out.start_block()   169         self._to_string(out)   170         out.end_block()   171    172 class DefItem(Container):   173    174     "A definition item."   175    176     def __init__(self, nodes, pad, extra):   177         Container.__init__(self, nodes)   178         self.pad = pad   179         self.extra = extra   180    181     def __repr__(self):   182         return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra)   183    184     def prettyprint(self, indent=""):   185         l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)]   186         return self._prettyprint(l, indent)   187    188     def to_string(self, out):   189         out.start_defitem(self.pad, self.extra)   190         self._to_string(out)   191         out.end_defitem(self.pad, self.extra)   192    193 class DefTerm(Container):   194    195     "A definition term."   196    197     def __init__(self, nodes, pad):   198         Container.__init__(self, nodes)   199         self.pad = pad   200    201     def __repr__(self):   202         return "DefTerm(%r, %r)" % (self.nodes, self.pad)   203    204     def prettyprint(self, indent=""):   205         l = ["%sDefTerm: pad=%r" % (indent, self.pad)]   206         return self._prettyprint(l, indent)   207    208     def to_string(self, out):   209         out.start_defterm(self.pad)   210         self._to_string(out)   211         out.end_defterm(self.pad)   212    213 class FontStyle(Container):   214    215     "Emphasised and/or strong text."   216    217     def __init__(self, nodes, emphasis=False, strong=False):   218         Container.__init__(self, nodes)   219         self.emphasis = emphasis   220         self.strong = strong   221    222     def close_emphasis(self):   223         if self.strong:   224             span = FontStyle(self.nodes, emphasis=True)   225             self.nodes = [span]   226             self.emphasis = False   227         return self.strong   228    229     def close_strong(self):   230         if self.emphasis:   231             span = FontStyle(self.nodes, strong=True)   232             self.nodes = [span]   233             self.strong = False   234         return self.emphasis   235    236     def __repr__(self):   237         return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong)   238    239     def prettyprint(self, indent=""):   240         l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)]   241         return self._prettyprint(l, indent)   242    243     def to_string(self, out):   244         if self.emphasis:   245             out.start_emphasis()   246         elif self.strong:   247             out.start_strong()   248         self._to_string(out)   249         if self.emphasis:   250             out.end_emphasis()   251         elif self.strong:   252             out.end_strong()   253    254 class Heading(Container):   255    256     "A heading."   257    258     def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""):   259         Container.__init__(self, nodes)   260         self.level = level   261         self.start_extra = start_extra   262         self.start_pad = start_pad   263         self.end_pad = end_pad   264         self.end_extra = end_extra   265    266     def __repr__(self):   267         return "Heading(%r, %d, %r, %r, %r, %r)" % (   268             self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)   269    270     def prettyprint(self, indent=""):   271         l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % (   272                 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)]   273         return self._prettyprint(l, indent)   274    275     def to_string(self, out):   276         out.start_heading(self.level, self.start_extra, self.start_pad)   277         self._to_string(out)   278         out.end_heading(self.level, self.end_pad, self.end_extra)   279    280 class List(Container):   281    282     "A list."   283    284     def __init__(self, nodes, indent, marker, num):   285         Container.__init__(self, nodes)   286         self.indent = indent   287         self.marker = marker   288         self.num = num   289    290     def __repr__(self):   291         return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num)   292    293     def prettyprint(self, indent=""):   294         l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)]   295         return self._prettyprint(l, indent)   296    297     def to_string(self, out):   298         out.start_list(self.indent, self.marker, self.num)   299         self._to_string(out)   300         out.end_list(self.indent, self.marker, self.num)   301    302 class ListItem(Container):   303    304     "A list item."   305    306     def __init__(self, nodes, indent, marker, space, num):   307         Container.__init__(self, nodes)   308         self.indent = indent   309         self.marker = marker   310         self.space = space   311         self.num = num   312    313         # Forbid blocks within list items for simpler structure.   314    315         self.allow_blocks = False   316    317     def __repr__(self):   318         return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num)   319    320     def prettyprint(self, indent=""):   321         l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)]   322         return self._prettyprint(l, indent)   323    324     def to_string(self, out):   325         out.start_listitem(self.indent, self.marker, self.space, self.num)   326         self._to_string(out)   327         out.end_listitem(self.indent, self.marker, self.space, self.num)   328    329 class TableAttrs(Container):   330    331     "A collection of table attributes."   332    333     def __repr__(self):   334         return "TableAttrs(%r)" % self.nodes   335    336     def prettyprint(self, indent=""):   337         l = ["%sTableAttrs:" % indent]   338         return self._prettyprint(l, indent)   339    340     def to_string(self, out):   341         out.start_table_attrs()   342         self._to_string(out)   343         out.end_table_attrs()   344    345 class Table(Container):   346    347     "A table."   348    349     def __repr__(self):   350         return "Table(%r)" % self.nodes   351    352     def prettyprint(self, indent=""):   353         l = ["%sTable:" % indent]   354         return self._prettyprint(l, indent)   355    356     def to_string(self, out):   357         out.start_table()   358         self._to_string(out)   359         out.end_table()   360    361 class TableCell(Container):   362    363     "A table cell."   364    365     def __init__(self, nodes, attrs=None):   366         Container.__init__(self, nodes)   367         self.attrs = attrs   368    369     def __repr__(self):   370         return "TableCell(%r, %r)" % (self.nodes, self.attrs)   371    372     def prettyprint(self, indent=""):   373         l = ["%sTableCell:" % indent]   374         return self._prettyprint(l, indent)   375    376     def to_string(self, out):   377         out.start_table_cell(self.attrs)   378         for node in self.nodes:   379             if node is not self.attrs:   380                 node.to_string(out)   381         out.end_table_cell()   382    383 class TableRow(Container):   384    385     "A table row."   386    387     def __init__(self, nodes, trailing=""):   388         Container.__init__(self, nodes)   389         self.trailing = trailing   390    391     def __repr__(self):   392         return "TableRow(%r, %r)" % (self.nodes, self.trailing)   393    394     def prettyprint(self, indent=""):   395         l = ["%sTableRow: trailing=%r" % (indent, self.trailing)]   396         return self._prettyprint(l, indent)   397    398     def to_string(self, out):   399         out.start_table_row()   400         self._to_string(out)   401         out.end_table_row(self.trailing)   402    403    404    405 # Inline nodes.   406    407 class Inline(Container):   408    409     "Generic inline formatting."   410    411     def __repr__(self):   412         return "%s(%r)" % (self.__class__.__name__, self.nodes)   413    414     def prettyprint(self, indent=""):   415         l = ["%s%s" % (indent, self.__class__.__name__)]   416         return self._prettyprint(l, indent)   417    418 class Larger(Inline):   419    420     "Larger text."   421    422     def to_string(self, out):   423         out.start_larger()   424         self._to_string(out)   425         out.end_larger()   426    427 class Link(Container):   428    429     "Link details."   430    431     def __init__(self, nodes, target):   432         Container.__init__(self, nodes)   433         self.target = target   434    435     def __repr__(self):   436         return "Link(%r, %r)" % (self.nodes, self.target)   437    438     def prettyprint(self, indent=""):   439         l = ["%sLink: target=%r" % (indent, self.target)]   440         return self._prettyprint(l, indent)   441    442     def to_string(self, out):   443         out.start_link(self.target)   444         if self.nodes:   445             out.start_linktext()   446             self._to_string(out)   447             out.end_linktext()   448         out.end_link()   449    450 class Monospace(Inline):   451    452     "Monospaced text."   453    454     def to_string(self, out):   455         out.start_monospace()   456         self._to_string(out)   457         out.end_monospace()   458    459 class Smaller(Inline):   460    461     "Smaller text."   462    463     def to_string(self, out):   464         out.start_smaller()   465         self._to_string(out)   466         out.end_smaller()   467    468 class Strikethrough(Inline):   469    470     "Crossed-out text."   471    472     def to_string(self, out):   473         out.start_strikethrough()   474         self._to_string(out)   475         out.end_strikethrough()   476    477 class Subscript(Inline):   478    479     "Subscripted text."   480    481     def to_string(self, out):   482         out.start_subscript()   483         self._to_string(out)   484         out.end_subscript()   485    486 class Superscript(Inline):   487    488     "Superscripted text."   489    490     def to_string(self, out):   491         out.start_superscript()   492         self._to_string(out)   493         out.end_superscript()   494    495 class Underline(Inline):   496    497     "Underlined text."   498    499     def to_string(self, out):   500         out.start_underline()   501         self._to_string(out)   502         out.end_underline()   503    504    505    506 # Nodes without children.   507    508 class Node:   509    510     "A document node without children."   511    512     def empty(self):   513         return False   514    515 class Break(Node):   516    517     "A paragraph break."   518    519     def __repr__(self):   520         return "Break()"   521    522     def prettyprint(self, indent=""):   523         return "%sBreak" % indent   524    525     def to_string(self, out):   526         out.break_()   527    528 class Continuation(Node):   529    530     "Continuation padding for table content."   531    532     def __init__(self, text):   533         self.text = text   534    535     def __repr__(self):   536         return "Continuation(%r)" % self.text   537    538     def prettyprint(self, indent=""):   539         return "%sContinuation: %r" % (indent, self.text)   540    541     def to_string(self, out):   542         out.continuation(self.text)   543    544 class Rule(Node):   545    546     "A horizontal rule."   547    548     def __init__(self, length):   549         self.length = length   550    551     def __repr__(self):   552         return "Rule(%d)" % self.length   553    554     def prettyprint(self, indent=""):   555         return "%sRule: length=%d" % (indent, self.length)   556    557     def to_string(self, out):   558         out.rule(self.length)   559    560 class TableAttr(Node):   561    562     "A table attribute."   563    564     def __init__(self, name, value=None, concise=False, quote=None):   565         self.name = name   566         self.value = value   567         self.concise = concise   568         self.quote = quote   569    570     def __repr__(self):   571         return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote)   572    573     def prettyprint(self, indent=""):   574         return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote)   575    576     def to_string(self, out):   577         out.table_attr(self.name, self.value, self.concise, self.quote)   578    579 class Text(Node):   580    581     "A text node."   582    583     def __init__(self, s):   584         self.s = s   585    586     def empty(self):   587         return not self.s   588    589     def multiline(self):   590         return "\n" in self.s   591    592     def merge(self, text):   593         self.s += text.s   594    595     def __repr__(self):   596         return "Text(%r)" % self.s   597    598     def prettyprint(self, indent=""):   599         return "%sText: %r" % (indent, self.s)   600    601     def to_string(self, out):   602         out.text(self.s)   603    604 # vim: tabstop=4 expandtab shiftwidth=4