summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/LingTrees/trees.py
blob: 4fcbb243ef09a7d9291c0cfdccacdbe69ca58617 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#! /usr/bin/python
#
# COPYRIGHT 2005, 2006, Avery D Andrews 3rd
#
#  GPL2
#
#  changelog at end

import sys, string, os, re

#
# would be an idea to subclass 'file', but then I wouldn't
#   know how to make it work with sys.stdin
#
class Source:

    def __init__(self, source):
        self.linenum = 0
        self.file = source
        self.nextline = source.readline()

    def readline(self):
        self.linenum = self.linenum+1
        returnline = self.nextline
        self.nextline = self.file.readline()
        return returnline

class TreesError(Exception):

    def __init__(self, value):
        self.value = value+'\n'
        #
        # UGH: there must be a better way to do this
        #
        global error_occurred
        error_occurred=1

    #
    # UMM: actually this doesn't seem to do what I expect it to (be
    #   the spelling of an error instance in backquotes)
    #
    def __str__(self):
        return self.value

#
# complain and set error flag but don't stop
#
def TreesWhinge(issue):
    sys.stderr.write(issue)
    global error_occurred
    error_occurred=1


class Node:
    def __init__(self, label):
        self.tri = 0
        self.mother = None
        self.daughters = None
        self.next = None
        self.tag = ""
        self.label = label

OUTPUT_DEFAULT = 0 # write output to command-line-specified file our sysout
OUTPUT_TEMP    = 1 # write the next tree to a specified output file
OUTPUT_PERM    = 2 # write all to a specified output file

def parse_line(line, infile):
    text = string.strip(line)
    if text=='':
        raise TreesError('Blank line in tree at line %d.'%infile.linenum)
    white = line[:string.find(line,text[0])]
    return len(white), text


def get_line(infile):
    line = infile.readline()
    while infile.nextline[:2]=='.-':
       line = line + string.strip(infile.readline()[2:])
    if line=='':
        raise TreesError('File ends during tree at line %d.'%infile.linenum)
    return parse_line(line, infile)
    

#
# gets sub-trees whose first line has the same indentation as first param
#
def get_daughters(indent, text, infile, mother):
    daughter, newindent, newtext = build_tree(indent, text, infile)
    daughter.mother = mother
    daughters = [daughter]
    prevdaughter = daughter
    while newindent==indent:
        newdaughter, newindent, newtext = build_tree(newindent, newtext, infile)
        newdaughter.mother = mother
        prevdaughter.next = newdaughter
        daughters.append(newdaughter)
        prevdaughter=newdaughter
    if newindent > indent:
        raise TreesError('Indentation fault at line %d.'%infile.linenum)
    return daughters, newindent, newtext


#
#  pattern for option parsing
#
optpattern = re.compile("\s*(\S*)\s*(.*?)\s*$")

#
# text is not supposed to be '.]'
#
def build_tree(indent, text, infile):
    linenum = infile.linenum
    textlist = re.split("[|]",text)
    node = Node(textlist[0])
    dict = node.optdict = {}
    for command in textlist[1:]:
        matchcom = optpattern.match(command)
        if matchcom:
           comm = matchcom.group(1)
           if not comm in [
             'tag',     # custom tag for the line
             'tri',     # node has a triangle over it
             'none',    # node has no line going to it
             'dot',     # node line is dotted
             'dash',    # node line is dashed
             'after',   # something after the connection command
             'style',   # style commands
             'dotted',  # nice pstricks dots
             'connect', # custom connection command
             'width',   # node is of set width
             'dtwidths',# widths of daughter nodes
             ]:
              TreesWhinge("Warning: unknown option '%s' near line %d\n"%(command,linenum))
           dict[comm] = matchcom.group(2)
    if dict.has_key("tag"):
        node.tag = dict["tag"]
    else:
        global tag_count
        node.tag = "Z%d"%tag_count
        tag_count=tag_count+1
    if dict.has_key("none"):
        if dict.has_key("tri"):
            TreesWhinge("Incompatible options 'none' and 'tri' near line %d (tri wins)\n"%linenum)
        if dict.has_key("dash") or dict.has_key("dot") or dict.has_key("linestyle"):
            TreesWhinge("Incompatible options 'none' and some line style near line %d (none wins)\n"%linenum)

    newindent, newtext = get_line(infile)
    if newindent > indent:
        node.daughters, newindent, newtext = get_daughters(newindent, newtext, infile, node)
    return node, newindent, newtext


def write_tree(node, outfile, indent=0):
    dict = node.optdict
    lineoptions=""
    if node.mother:
        if dict.has_key('dash'):
            line = "\\makedash{3pt}"
        elif dict.has_key('dot'):
            line = "\\makedash{1pt}"
        else:
            line = ""
        if dict.has_key('dotted'):
            lineoptions = lineoptions+"linestyle=dotted,linewidth=\\treedotwidth"
        if dict.has_key('style'):
            if lineoptions:
                lineoptions = lineoptions+','+dict['style']
            else:
                lineoptions = dict['style']
        if lineoptions:
            lineoptions = "[%s]"%lineoptions
        if dict.has_key('after'):
            lineoptions = lineoptions+dict['after']
        if dict.has_key('tri'):
            line = line+"\\nodetriangle{%s}{%s}%s%%\n"%(node.mother.tag, node.tag, lineoptions)
        elif dict.has_key('none'):
            if dict.has_key('after'):
                line = dict['after']
        else:
            if dict.has_key('connect'):
                connect = dict['connect']
            else:
                connect = "\\nodeconnect"
            line = line+"%s{%s}{%s}%s"%(connect, node.mother.tag, node.tag, lineoptions)
    else:
        line = ''
    if dict.has_key('width'):
        width = '\\setnodewidth{%s}'%dict['width']
    elif node.mother!=None and node.mother.optdict.has_key('dtwidths'):
        width = '\\setnodewidth{%s}'%node.mother.optdict['dtwidths']
    else:
        width = ""
    if node.daughters:
        outfile.write("{%s\\ntnode{%s}{%s}{%s},\n"%(width,node.tag,node.label,line))
        for daughter in node.daughters:
            write_tree(daughter, outfile, indent+2)
        outfile.write('}')
    else:
        outfile.write("{%s\\tnode{%s}{%s}{%s}}"%(width,node.tag,node.label, line))
    if node.next:
#        if node.mother.next:
            outfile.write(",\n")


def process_tree(infile, outfile):
    indent, text = get_line(infile)
    if text[:2]=='.]':
        return
#    outfile.write('indent: %d; text: %s\n'%(indent, text))
    global tag_count
    tag_count = 0
    tree, newindent, newtext =  build_tree(indent, text, infile)
    if newindent!=0:
        raise TreesError('Indentation fault at line %d.'%infile.linenum)
    if newtext!='.]':
        raise TreesError('Bad tree end at line %d.'%infile.linenum)
    outfile.write('%%% begin tree\n\\tree')
    write_tree(tree, outfile)
    outfile.write('%%\n%%end tree\n')
    

def make_folders(path):
    folder, file = os.path.split(path)
    folder = string.strip(folder)
    if folder and not os.path.exists(folder):
        os.makedirs(folder)

def process_file(infile, outfile):
    global error_occurred
    error_occurred =0
    outfilemode = OUTPUT_DEFAULT
    while 1:
        line = infile.readline()
        if line == '':
            break;
        if line[:3]=='.>>':
            outfilemode = OUTPUT_PERM
            line = string.strip(line[3:])
            make_folders(line)
            outfile2 = outfile
            outfile = open(line,'w')
        elif line[:3] == '.<<':
            outfilemode = OUTPUT_DEFAULT
        elif line[:2] == '.>':
            if outfilemode == OUTPUT_PERM:
                sys.stderr.write("Warning: temp output directive after permanent at line %d"%infile.linenum)
            else:
                line = string.strip(line[2:])
                make_folders(line)
                outfile2 = outfile
                outfilemode = OUTPUT_TEMP
                outfile = open(line,'w')
        elif line[:2] == '.[':
            try:
                process_tree(infile, outfile)
            except TreesError, error:
                sys.stderr.write(error.value)
                errfile=open(errfilename,'w')
                errfile.write(error.value)
                outfile.write('BAD TREE:\n  %s'%error.value)
            try:
               if outfilemode == OUTPUT_TEMP and outfile2:
                   outfile = outfile2
                   del outfile2
            except UnboundLocalError:
              pass
        else:
            outfile.write(line)


if __name__=="__main__":
    argv=sys.argv
#    print 'cwd:'+os.getcwd()
    if len(argv)<2:# run as filter
        infile=sys.stdin
        outfile=sys.stdout
        errfilename = 'trees.err'
#        errfile=open("trees.err","w")
    else:
        infile=open(argv[1]+'.txp','r')
        outfile=open(argv[1]+'.tex','w')
        errfilename = argv[1]+'.err'
#        errfile=open(argv[1]+'.err','w')

    process_file(Source(infile), outfile)
    if error_occurred:
        sys.exit(1)
    sys.stderr.write("trees ran without issues\n")

#
#  changelog
#
#  v 3.1  Feb 28 2006
#    widths and dtwidths commands, for controlling width
#      of nodes.
#
#  v 3.0  Jan 30 2006
#    adjusted to write for pst-node
#    name changed to lingtrees.py
#    implement additional options for PSTricks
#
#  v 2.2, Jan 18 2006
#   implemented none, dash and dot options from Chris Manning's version
#
#  v 2.1, Dec 28 2005
#   .>, .>>, >> directives added for writing to files
#   .- directive added for continuation of lines
#