natural language toolkit ascii visualization of nltk trees c 20012023 nltk project andreas van cranenburgh a w vancranenburghuva nl peter ljunglf peter ljunglofgu se url https www nltk org for license information see license txt prettyprinting of discontinuous trees adapted from the discodop project by andreas van cranenburgh https github comandreasvcdiscodop interesting reference not used for this code t eschbach et al orth hypergraph drawing journal of graph algorithms and applications 102 141157 2006149 https jgaa infoaccepted2006eschbachguentherbecker2006 10 2 pdf prettyprint a tree in text format either as ascii or unicode the tree can be a normal tree or discontinuous treeprettyprintertree sentencenone highlight creates an object from which different visualizations can be created param tree a tree object param sentence a list of words strings if sentence is given tree must contain integers as leaves which are taken as indices in sentence using this you can display a discontinuous tree param highlight optionally a sequence of tree objects in tree which should be highlighted has the effect of only applying colors to nodes in this sequence nodes should be given as tree objects terminals as indices from nltk tree import tree tree tree fromstring s np mary vp walks printtreeprettyprintertree text doctest normalizewhitespace s np vp mary walks this deals with empty nodes frontier nonterminals and multiplemixed terminals under nonterminals produce coordinates of nodes on a grid objective produce coordinates for a nonoverlapping placement of nodes and horizontal lines order edges so that crossing edges cross a minimal number of previous horizontal lines never vertical lines approach bottom up level order traversal start at terminals at each level identify nodes which cannot be on the same row identify nodes which cannot be in the same column place nodes into a grid at row column order childparent edges with crossing edges last coordinates are row column the origin 0 0 is at the top left the root node is on row 0 coordinates do not consider the size of a node which depends on font c so the width of a column of the grid should be automatically determined by the element with the greatest width in that column alternatively the integer coordinates could be converted to coordinates in which the distances between adjacent nodes are nonuniform produces tuple nodes coords edges highlighted where nodesid tree object for the node with this integer id coordsid n m coordinate where to draw node with id in the grid edgesid parent id of node with this id ordered dictionary highlighted set of ids that should be highlighted find vacant row column index for node m iterate over current rows for this level try lowest first and look for cell between first and last child of this node add new row to level if no free row available find free column dump matrix contents for debugging purposes return n join 2d s n join2r i 2 for i in row for n row in enumeratematrix leaves tree leaves if not allisinstancen int for n in leaves raise valueerrorall leaves must be integer indices if lenleaves lensetleaves raise valueerrorindices must occur at most once if not all0 n lensentence for n in leaves raise valueerror all leaves must be in the interval 0 n with nlensentencentokens d indices rnsentence s lensentence tree leaves sentence vertline corner 1 2 constants tree tree copytrue for a in tree subtrees a sortkeylambda n minn leaves if isinstancen tree else n scale 2 crossed set internal nodes and lexical nodes no frontiers positions tree treepositions maxdepth maxmaplen positions 1 childcols defaultdictset matrix none lensentence scale nodes ids a n for n a in enumeratepositions highlightednodes n for a n in ids items if not highlight or treea in highlight levels n for n in rangemaxdepth 1 terminals for a in positions node treea if isinstancenode tree levelsmaxdepth node height appenda else terminals appenda for n in levels levelsn sortkeylambda n maxtreen leaves mintreen leaves terminals sort positions setpositions for m in terminals i inttreem scale assert matrix0i is none matrix0i m i matrix0i idsm nodesidsm sentencetreem if nodesidsm is none nodesidsm highlightednodes discardidsm positions removem childcolsm 1 add0 i add other nodes centered on their children if the center is already taken back off to the left and right alternately until an empty cell is found for n in sortedlevels reversetrue nodesatdepth levelsn startoflevel lenmatrix matrix append vertline if a not in corner none else none for a in matrix1 for m in nodesatdepth 1 if n maxdepth 1 and childcolsm pivot minchildcolsm keyitemgetter1 if a 1 for row in matrix 1 for a in row pivot if isinstancea tuple a 1 for row in matrix 1 for a in rowpivot if isinstancea tuple crossed addm rowidx i findcellm matrix startoflevel childcols positions removem block positions where children of this node branch out for x in childcolsm matrixrowidxx corner assert m or matrixrowidxi in none corner matrixrowidxi m strtree joinsentence node itself matrixrowidxi idsm nodesidsm treem add column to the set of children for its parent if lenm 0 childcolsm 1 addrowidx i assert lenpositions 0 remove unused columns right to left for m in rangescale lensentence 1 1 1 if not anyisinstancerowm tree int for row in matrix for row in matrix del rowm remove unused rows reverse matrix row for row in reversedmatrix if not alla is none or a vertline for a in row collect coordinates of nodes coords for n in enumeratematrix for m i in enumeratematrixn if isinstancei int and i 0 coordsi n m move crossed edges last positions sorted a for level in levels values for a in level keylambda a a 1 in crossed collect edges from node to node edges ordereddict for i in reversedpositions for j in enumeratetreei edgesidsi j idsi return nodes coords edges highlightednodes def text self nodedist1 unicodelinesfalse htmlfalse ansifalse nodecolorblue leafcolorred funccolorgreen abbreviatenone maxwidth16 if abbreviate true abbreviate 5 if unicodelines horzline u2500 leftcorner u250c rightcorner u2510 vertline u2502 tee horzline u252c horzline bottom horzline u2534 horzline cross horzline u253c horzline ellipsis u2026 else horzline leftcorner rightcorner vertline tee 3 horzline cross bottom ellipsis def crosscellcur xvertline collect labels and coordinates bottom up level order traversal draw horizontal branch towards children for this node draw vertical lines in partially filled multiline node labels but only if it s not a frontier node for each column if there is a node below us which has a parent above us draw a vertical branch in that column return svg representation of a tree horizontal branches from nodes to children vertical branches from children to parents write nodes with coordinates do some tree drawing tests def printtreen tree sentencenone ansitrue xargs print print formatn joinsentence or tree leaves printtree print drawtree treeprettyprintertree sentence try printdrawtree textunicodelinesansi ansiansi xargs except unicodedecodeerror unicodeencodeerror printdrawtree textunicodelinesfalse ansifalse xargs from nltk corpus import treebank for n in 0 1440 1591 2771 2170 tree treebank parsedsentsn printtreen tree nodedist2 maxwidth8 print printascii version printtreeprettyprintertree textnodedist2 tree tree fromstring top punct 8 smain noun 0 verb 1 inf verb 5 inf verb 6 conj inf pp prep 2 np det 3 noun 4 verb 7 inf verb 9 vg 10 inf verb 11 punct 12 readleafint sentence ze had met haar moeder kunnen gaan winkelen zwemmen of terrassen split printtreediscontinuous tree tree sentence nodedist2 all treeprettyprinter if name main test natural language toolkit ascii visualization of nltk trees c 2001 2023 nltk project andreas van cranenburgh a w vancranenburgh uva nl peter ljunglöf peter ljunglof gu se url https www nltk org for license information see license txt pretty printing of discontinuous trees adapted from the disco dop project by andreas van cranenburgh https github com andreasvc disco dop interesting reference not used for this code t eschbach et al orth hypergraph drawing journal of graph algorithms and applications 10 2 141 157 2006 149 https jgaa info accepted 2006 eschbachguentherbecker2006 10 2 pdf pretty print a tree in text format either as ascii or unicode the tree can be a normal tree or discontinuous treeprettyprinter tree sentence none highlight creates an object from which different visualizations can be created param tree a tree object param sentence a list of words strings if sentence is given tree must contain integers as leaves which are taken as indices in sentence using this you can display a discontinuous tree param highlight optionally a sequence of tree objects in tree which should be highlighted has the effect of only applying colors to nodes in this sequence nodes should be given as tree objects terminals as indices from nltk tree import tree tree tree fromstring s np mary vp walks print treeprettyprinter tree text doctest normalize_whitespace s ____ ____ np vp mary walks this deals with empty nodes frontier non terminals and multiple mixed terminals under non terminals produce coordinates of nodes on a grid objective produce coordinates for a non overlapping placement of nodes and horizontal lines order edges so that crossing edges cross a minimal number of previous horizontal lines never vertical lines approach bottom up level order traversal start at terminals at each level identify nodes which cannot be on the same row identify nodes which cannot be in the same column place nodes into a grid at row column order child parent edges with crossing edges last coordinates are row column the origin 0 0 is at the top left the root node is on row 0 coordinates do not consider the size of a node which depends on font c so the width of a column of the grid should be automatically determined by the element with the greatest width in that column alternatively the integer coordinates could be converted to coordinates in which the distances between adjacent nodes are non uniform produces tuple nodes coords edges highlighted where nodes id tree object for the node with this integer id coords id n m coordinate where to draw node with id in the grid edges id parent id of node with this id ordered dictionary highlighted set of ids that should be highlighted find vacant row column index for node m iterate over current rows for this level try lowest first and look for cell between first and last child of this node add new row to level if no free row available center of gravity round to unscaled coordinate need to add a new row place unaries directly above child find free column dump matrix contents for debugging purposes constants internal nodes and lexical nodes no frontiers add other nodes centered on their children if the center is already taken back off to the left and right alternately until an empty cell is found 1 block positions where children of this node branch out assert m or matrix rowidx i in none corner matrix rowidx i m str tree join sentence node itself add column to the set of children for its parent remove unused columns right to left remove unused rows reverse collect coordinates of nodes move crossed edges last collect edges from node to node return ascii art for a discontinuous tree param unicodelines whether to use unicode line drawing characters instead of plain 7 bit ascii param html whether to wrap output in html code default plain text param ansi whether to produce colors with ansi escape sequences only effective when html false param leafcolor nodecolor specify colors of leaves and phrasal nodes effective when either html or ansi is true param abbreviate if true abbreviate labels longer than 5 characters if integer abbreviate labels longer than abbr characters param maxwidth maximum number of characters before a label starts to wrap pass none to disable overwrite center of this cell with a vertical branch collect labels and coordinates e g root bottom up level order traversal draw horizontal branch towards children for this node if n and n in minchildcol draw vertical lines in partially filled multiline node labels but only if it s not a frontier node for each column if there is a node below us which has a parent above us draw a vertical branch in that column return svg representation of a tree horizontal branches from nodes to children vertical branches from children to parents write nodes with coordinates do some tree drawing tests
import re

try:
    from html import escape
except ImportError:
    from cgi import escape

from collections import defaultdict
from operator import itemgetter

from nltk.tree.tree import Tree
from nltk.util import OrderedDict

ANSICOLOR = {
    "black": 30,
    "red": 31,
    "green": 32,
    "yellow": 33,
    "blue": 34,
    "magenta": 35,
    "cyan": 36,
    "white": 37,
}


class TreePrettyPrinter:
    

    def __init__(self, tree, sentence=None, highlight=()):
        if sentence is None:
            leaves = tree.leaves()
            if (
                leaves
                and all(len(a) > 0 for a in tree.subtrees())
                and all(isinstance(a, int) for a in leaves)
            ):
                sentence = [str(a) for a in leaves]
            else:
                
                
                tree = tree.copy(True)
                sentence = []
                for a in tree.subtrees():
                    if len(a) == 0:
                        a.append(len(sentence))
                        sentence.append(None)
                    elif any(not isinstance(b, Tree) for b in a):
                        for n, b in enumerate(a):
                            if not isinstance(b, Tree):
                                a[n] = len(sentence)
                                if type(b) == tuple:
                                    b = "/".join(b)
                                sentence.append("%s" % b)
        self.nodes, self.coords, self.edges, self.highlight = self.nodecoords(
            tree, sentence, highlight
        )

    def __str__(self):
        return self.text()

    def __repr__(self):
        return "<TreePrettyPrinter with %d nodes>" % len(self.nodes)

    @staticmethod
    def nodecoords(tree, sentence, highlight):
        

        def findcell(m, matrix, startoflevel, children):
            
            candidates = [a for _, a in children[m]]
            minidx, maxidx = min(candidates), max(candidates)
            leaves = tree[m].leaves()
            center = scale * sum(leaves) // len(leaves)  
            if minidx < maxidx and not minidx < center < maxidx:
                center = sum(candidates) // len(candidates)
            if max(candidates) - min(candidates) > 2 * scale:
                center -= center % scale  
                if minidx < maxidx and not minidx < center < maxidx:
                    center += scale
            if ids[m] == 0:
                startoflevel = len(matrix)
            for rowidx in range(startoflevel, len(matrix) + 1):
                if rowidx == len(matrix):  
                    matrix.append(
                        [
                            vertline if a not in (corner, None) else None
                            for a in matrix[-1]
                        ]
                    )
                row = matrix[rowidx]
                if len(children[m]) == 1:  
                    return rowidx, next(iter(children[m]))[1]
                elif all(
                    a is None or a == vertline
                    for a in row[min(candidates) : max(candidates) + 1]
                ):
                    
                    for n in range(scale):
                        i = j = center + n
                        while j > minidx or i < maxidx:
                            if i < maxidx and (
                                matrix[rowidx][i] is None or i in candidates
                            ):
                                return rowidx, i
                            elif j > minidx and (
                                matrix[rowidx][j] is None or j in candidates
                            ):
                                return rowidx, j
                            i += scale
                            j -= scale
            raise ValueError(
                "could not find a free cell for:\n%s\n%s"
                "min=%d; max=%d" % (tree[m], minidx, maxidx, dumpmatrix())
            )

        def dumpmatrix():
            
            return "\n".join(
                "%2d: %s" % (n, " ".join(("%2r" % i)[:2] for i in row))
                for n, row in enumerate(matrix)
            )

        leaves = tree.leaves()
        if not all(isinstance(n, int) for n in leaves):
            raise ValueError("All leaves must be integer indices.")
        if len(leaves) != len(set(leaves)):
            raise ValueError("Indices must occur at most once.")
        if not all(0 <= n < len(sentence) for n in leaves):
            raise ValueError(
                "All leaves must be in the interval 0..n "
                "with n=len(sentence)\ntokens: %d indices: "
                "%r\nsentence: %s" % (len(sentence), tree.leaves(), sentence)
            )
        vertline, corner = -1, -2  
        tree = tree.copy(True)
        for a in tree.subtrees():
            a.sort(key=lambda n: min(n.leaves()) if isinstance(n, Tree) else n)
        scale = 2
        crossed = set()
        
        positions = tree.treepositions()
        maxdepth = max(map(len, positions)) + 1
        childcols = defaultdict(set)
        matrix = [[None] * (len(sentence) * scale)]
        nodes = {}
        ids = {a: n for n, a in enumerate(positions)}
        highlighted_nodes = {
            n for a, n in ids.items() if not highlight or tree[a] in highlight
        }
        levels = {n: [] for n in range(maxdepth - 1)}
        terminals = []
        for a in positions:
            node = tree[a]
            if isinstance(node, Tree):
                levels[maxdepth - node.height()].append(a)
            else:
                terminals.append(a)

        for n in levels:
            levels[n].sort(key=lambda n: max(tree[n].leaves()) - min(tree[n].leaves()))
        terminals.sort()
        positions = set(positions)

        for m in terminals:
            i = int(tree[m]) * scale
            assert matrix[0][i] is None, (matrix[0][i], m, i)
            matrix[0][i] = ids[m]
            nodes[ids[m]] = sentence[tree[m]]
            if nodes[ids[m]] is None:
                nodes[ids[m]] = "..."
                highlighted_nodes.discard(ids[m])
            positions.remove(m)
            childcols[m[:-1]].add((0, i))

        
        
        
        for n in sorted(levels, reverse=True):
            nodesatdepth = levels[n]
            startoflevel = len(matrix)
            matrix.append(
                [vertline if a not in (corner, None) else None for a in matrix[-1]]
            )
            for m in nodesatdepth:  
                if n < maxdepth - 1 and childcols[m]:
                    _, pivot = min(childcols[m], key=itemgetter(1))
                    if {
                        a[:-1]
                        for row in matrix[:-1]
                        for a in row[:pivot]
                        if isinstance(a, tuple)
                    } & {
                        a[:-1]
                        for row in matrix[:-1]
                        for a in row[pivot:]
                        if isinstance(a, tuple)
                    }:
                        crossed.add(m)

                rowidx, i = findcell(m, matrix, startoflevel, childcols)
                positions.remove(m)

                
                for _, x in childcols[m]:
                    matrix[rowidx][x] = corner
                
                
                
                matrix[rowidx][i] = ids[m]
                nodes[ids[m]] = tree[m]
                
                if len(m) > 0:
                    childcols[m[:-1]].add((rowidx, i))
        assert len(positions) == 0

        
        for m in range(scale * len(sentence) - 1, -1, -1):
            if not any(isinstance(row[m], (Tree, int)) for row in matrix):
                for row in matrix:
                    del row[m]

        
        matrix = [
            row
            for row in reversed(matrix)
            if not all(a is None or a == vertline for a in row)
        ]

        
        coords = {}
        for n, _ in enumerate(matrix):
            for m, i in enumerate(matrix[n]):
                if isinstance(i, int) and i >= 0:
                    coords[i] = n, m

        
        positions = sorted(
            (a for level in levels.values() for a in level),
            key=lambda a: a[:-1] in crossed,
        )

        
        edges = OrderedDict()
        for i in reversed(positions):
            for j, _ in enumerate(tree[i]):
                edges[ids[i + (j,)]] = ids[i]

        return nodes, coords, edges, highlighted_nodes

    def text(
        self,
        nodedist=1,
        unicodelines=False,
        html=False,
        ansi=False,
        nodecolor="blue",
        leafcolor="red",
        funccolor="green",
        abbreviate=None,
        maxwidth=16,
    ):
        
        if abbreviate == True:
            abbreviate = 5
        if unicodelines:
            horzline = "\u2500"
            leftcorner = "\u250c"
            rightcorner = "\u2510"
            vertline = " \u2502 "
            tee = horzline + "\u252C" + horzline
            bottom = horzline + "\u2534" + horzline
            cross = horzline + "\u253c" + horzline
            ellipsis = "\u2026"
        else:
            horzline = "_"
            leftcorner = rightcorner = " "
            vertline = " | "
            tee = 3 * horzline
            cross = bottom = "_|_"
            ellipsis = "."

        def crosscell(cur, x=vertline):
            
            splitl = len(cur) - len(cur) // 2 - len(x) // 2 - 1
            lst = list(cur)
            lst[splitl : splitl + len(x)] = list(x)
            return "".join(lst)

        result = []
        matrix = defaultdict(dict)
        maxnodewith = defaultdict(lambda: 3)
        maxnodeheight = defaultdict(lambda: 1)
        maxcol = 0
        minchildcol = {}
        maxchildcol = {}
        childcols = defaultdict(set)
        labels = {}
        wrapre = re.compile(
            "(.{%d,%d}\\b\\W*|.{%d})" % (maxwidth - 4, maxwidth, maxwidth)
        )
        
        for a in self.nodes:
            row, column = self.coords[a]
            matrix[row][column] = a
            maxcol = max(maxcol, column)
            label = (
                self.nodes[a].label()
                if isinstance(self.nodes[a], Tree)
                else self.nodes[a]
            )
            if abbreviate and len(label) > abbreviate:
                label = label[:abbreviate] + ellipsis
            if maxwidth and len(label) > maxwidth:
                label = wrapre.sub(r"\1\n", label).strip()
            label = label.split("\n")
            maxnodeheight[row] = max(maxnodeheight[row], len(label))
            maxnodewith[column] = max(maxnodewith[column], max(map(len, label)))
            labels[a] = label
            if a not in self.edges:
                continue  
            parent = self.edges[a]
            childcols[parent].add((row, column))
            minchildcol[parent] = min(minchildcol.get(parent, column), column)
            maxchildcol[parent] = max(maxchildcol.get(parent, column), column)
        
        for row in sorted(matrix, reverse=True):
            noderows = [
                ["".center(maxnodewith[col]) for col in range(maxcol + 1)]
                for _ in range(maxnodeheight[row])
            ]
            branchrow = ["".center(maxnodewith[col]) for col in range(maxcol + 1)]
            for col in matrix[row]:
                n = matrix[row][col]
                node = self.nodes[n]
                text = labels[n]
                if isinstance(node, Tree):
                    
                    if n in minchildcol and minchildcol[n] < maxchildcol[n]:
                        i, j = minchildcol[n], maxchildcol[n]
                        a, b = (maxnodewith[i] + 1) // 2 - 1, maxnodewith[j] // 2
                        branchrow[i] = ((" " * a) + leftcorner).ljust(
                            maxnodewith[i], horzline
                        )
                        branchrow[j] = (rightcorner + (" " * b)).rjust(
                            maxnodewith[j], horzline
                        )
                        for i in range(minchildcol[n] + 1, maxchildcol[n]):
                            if i == col and any(a == i for _, a in childcols[n]):
                                line = cross
                            elif i == col:
                                line = bottom
                            elif any(a == i for _, a in childcols[n]):
                                line = tee
                            else:
                                line = horzline
                            branchrow[i] = line.center(maxnodewith[i], horzline)
                    else:  
                        branchrow[col] = crosscell(branchrow[col])
                text = [a.center(maxnodewith[col]) for a in text]
                color = nodecolor if isinstance(node, Tree) else leafcolor
                if isinstance(node, Tree) and node.label().startswith("-"):
                    color = funccolor
                if html:
                    text = [escape(a, quote=False) for a in text]
                    if n in self.highlight:
                        text = [f"<font color={color}>{a}</font>" for a in text]
                elif ansi and n in self.highlight:
                    text = ["\x1b[%d;1m%s\x1b[0m" % (ANSICOLOR[color], a) for a in text]
                for x in range(maxnodeheight[row]):
                    
                    
                    noderows[x][col] = (
                        text[x]
                        if x < len(text)
                        else (vertline if childcols[n] else " ").center(
                            maxnodewith[col], " "
                        )
                    )
            
            
            if row != max(matrix):
                for n, (childrow, col) in self.coords.items():
                    if n > 0 and self.coords[self.edges[n]][0] < row < childrow:
                        branchrow[col] = crosscell(branchrow[col])
                        if col not in matrix[row]:
                            for noderow in noderows:
                                noderow[col] = crosscell(noderow[col])
                branchrow = [
                    a + ((a[-1] if a[-1] != " " else b[0]) * nodedist)
                    for a, b in zip(branchrow, branchrow[1:] + [" "])
                ]
                result.append("".join(branchrow))
            result.extend(
                (" " * nodedist).join(noderow) for noderow in reversed(noderows)
            )
        return "\n".join(reversed(result)) + "\n"

    def svg(self, nodecolor="blue", leafcolor="red", funccolor="green"):
        
        fontsize = 12
        hscale = 40
        vscale = 25
        hstart = vstart = 20
        width = max(col for _, col in self.coords.values())
        height = max(row for row, _ in self.coords.values())
        result = [
            '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" '
            'width="%dem" height="%dem" viewBox="%d %d %d %d">'
            % (
                width * 3,
                height * 2.5,
                -hstart,
                -vstart,
                width * hscale + 3 * hstart,
                height * vscale + 3 * vstart,
            )
        ]

        children = defaultdict(set)
        for n in self.nodes:
            if n:
                children[self.edges[n]].add(n)

        
        for node in self.nodes:
            if not children[node]:
                continue
            y, x = self.coords[node]
            x *= hscale
            y *= vscale
            x += hstart
            y += vstart + fontsize // 2
            childx = [self.coords[c][1] for c in children[node]]
            xmin = hstart + hscale * min(childx)
            xmax = hstart + hscale * max(childx)
            result.append(
                '\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
                'points="%g,%g %g,%g" />' % (xmin, y, xmax, y)
            )
            result.append(
                '\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
                'points="%g,%g %g,%g" />' % (x, y, x, y - fontsize // 3)
            )

        
        for child, parent in self.edges.items():
            y, _ = self.coords[parent]
            y *= vscale
            y += vstart + fontsize // 2
            childy, childx = self.coords[child]
            childx *= hscale
            childy *= vscale
            childx += hstart
            childy += vstart - fontsize
            result += [
                '\t<polyline style="stroke:white; stroke-width:10; fill:none;"'
                ' points="%g,%g %g,%g" />' % (childx, childy, childx, y + 5),
                '\t<polyline style="stroke:black; stroke-width:1; fill:none;"'
                ' points="%g,%g %g,%g" />' % (childx, childy, childx, y),
            ]

        
        for n, (row, column) in self.coords.items():
            node = self.nodes[n]
            x = column * hscale + hstart
            y = row * vscale + vstart
            if n in self.highlight:
                color = nodecolor if isinstance(node, Tree) else leafcolor
                if isinstance(node, Tree) and node.label().startswith("-"):
                    color = funccolor
            else:
                color = "black"
            result += [
                '\t<text style="text-anchor: middle; fill: %s; '
                'font-size: %dpx;" x="%g" y="%g">%s</text>'
                % (
                    color,
                    fontsize,
                    x,
                    y,
                    escape(
                        node.label() if isinstance(node, Tree) else node, quote=False
                    ),
                )
            ]

        result += ["</svg>"]
        return "\n".join(result)


def test():
    

    def print_tree(n, tree, sentence=None, ansi=True, **xargs):
        print()
        print('{}: "{}"'.format(n, " ".join(sentence or tree.leaves())))
        print(tree)
        print()
        drawtree = TreePrettyPrinter(tree, sentence)
        try:
            print(drawtree.text(unicodelines=ansi, ansi=ansi, **xargs))
        except (UnicodeDecodeError, UnicodeEncodeError):
            print(drawtree.text(unicodelines=False, ansi=False, **xargs))

    from nltk.corpus import treebank

    for n in [0, 1440, 1591, 2771, 2170]:
        tree = treebank.parsed_sents()[n]
        print_tree(n, tree, nodedist=2, maxwidth=8)
    print()
    print("ASCII version:")
    print(TreePrettyPrinter(tree).text(nodedist=2))

    tree = Tree.fromstring(
        "(top (punct 8) (smain (noun 0) (verb 1) (inf (verb 5) (inf (verb 6) "
        "(conj (inf (pp (prep 2) (np (det 3) (noun 4))) (verb 7)) (inf (verb 9)) "
        "(vg 10) (inf (verb 11)))))) (punct 12))",
        read_leaf=int,
    )
    sentence = (
        "Ze had met haar moeder kunnen gaan winkelen ,"
        " zwemmen of terrassen .".split()
    )
    print_tree("Discontinuous tree", tree, sentence, nodedist=2)


__all__ = ["TreePrettyPrinter"]

if __name__ == "__main__":
    test()