#! /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 #