# -*- coding: utf-8 -*-
# FileConvert
#
# Copyright 2009 Erik Fløisbonn
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
%s
" % ("".join(tmp)));tmp=[] tmp.append("%s
" % ("".join(tmp[:-1]))) out.append("%s
" % ("".join(tmp)));tmp=[] else: tmp.append(line) return "\n".join(out) # Classes defining readers and writers class Widget(): ''' Widget class that represents the widgets string, cstring, check ''' def __init__(self, name, type, value, label=None, checked=None, description=None, priority=99): self.type = type self.name = name self.label = label self.priority = priority if type == "string": self.widget = QtGui.QLineEdit(value) if label: self.label = QtGui.QLabel(self.label) else: self.label = QtGui.QLabel(self.name) if type == "cstring": self.widget = QtGui.QLineEdit(value) if label: self.check = QtGui.QCheckBox(self.label) else: self.check = QtFGui.QCheckBox(self.name) if type == "check": if label: self.check = QtGui.QCheckBox(self.label) else: self.check = QtGui.QCheckBox(self.name) # Description if description: self.button = QtGui.QPushButton('?') self.description = description # Checked if checked != None and hasattr(self, "check"): if (checked == "True" or checked == True): checked = True elif (checked == "False" or checked == False): checked = False else: return # Change widgets self.check.setChecked(checked) self.check_action(checked) # Widget actions def check_action(self, value): if hasattr(self, "widget"): if value: self.widget.setEnabled(True) else: self.widget.setEnabled(False) def button_action(self): if hasattr(self, "description"): # QtGui.QMessageBox.information(None, self.name, self.description) textedit(title=self.name, text=self.description) class Variables(): ''' Variables class, one level above the widgets. Has the responsibility of getting stored values of the widgets, storing the values, showing the widgets and deleting them. It is also the interface towards the user when accessing variables, like get_var and is_checked. ''' def __init__(self, config): self.config = config self.var = {} variables = [] # Add all the variables defined in 'in_variables()' if self.job == "reader" and hasattr(self, "reader_variables") and self.reader_variables(): if type(self.reader_variables()) == list: variables.extend(self.reader_variables()) # Add all the variables defined in 'out_variables()' if self.job == "writer" and hasattr(self, "writer_variables") and self.writer_variables(): if type(self.writer_variables()) == list: variables.extend(self.writer_variables()) # Add widgets for var in variables: if type(var) != dict: continue # name, type, value, label, description # m, m, m, o, o description = None label = None priority = 99 if "name" in var.keys(): name = var["name"] else: QtGui.QMessageBox.warning(None, 'Format error', "One of " + unicode(self.__class__.__name__) + "'s " + self.job + " variables does not have a name.") if "type" in var.keys(): typei = var["type"] else: QtGui.QMessageBox.warning(None, 'Format error', unicode(self.__class__.__name__) + "'s " + self.job + " variable " + name + " does not have a type.") if "value" in var.keys(): value = var["value"] else: QtGui.QMessageBox.warning(None, 'Format error', unicode(self.__class__.__name__) + "'s " + self.job + " variable " + name + " does not have a default value.") if "label" in var.keys(): label = var["label"] if "description" in var.keys(): description = var["description"] if "priority" in var.keys(): priority = var["priority"] # Add variable self.add_var(name, typei, value, label=label, description=description, priority=priority) def add_var(self, name, type, default, label=None, description=None, priority=99): # Check to see if the value is in the config # if so, add it instead of the default # Look in config if not self.config: return None # Get widget value value = self.config.get(self.__class__.__name__, name + ".widget") if not value: value = default # Get check value, default is None checked = self.config.get(self.__class__.__name__, name + ".check") if not checked: checked = default # Add variable self.var[name] = Widget(name, type, value, label=label, checked=checked, description=description, priority=priority) def get_var(self, name): if name not in self.var.keys(): return None if self.var[name].type == "check": # Get the value return self.var[name].check.isChecked() else: return self.var[name].widget.text() def is_checked(self, name): widget = self.var[name] if hasattr(widget, "check"): return widget.check.isChecked() return None def addWidgets(self, box, window=None): # Sort by variable name # keys = self.var.keys() #keys.sort() # Sort the variables based on their priority keys = self.var.keys() keys.sort(lambda x,y:cmp(self.var[x].priority,self.var[y].priority)) x, y = 0, 0 for name in keys: widget = self.var[name] y = 0 if widget.type == "cstring": box.addWidget(self.var[name].check, x, y) y+=1 box.addWidget(self.var[name].widget, x, y) y+=1 # Add check action box.parentWidget().connect(self.var[name].check, QtCore.SIGNAL('stateChanged(int)'), self.var[name].check_action) if widget.type == "string": box.addWidget(self.var[name].label, x, y) x+=1; box.addWidget(self.var[name].widget, x, y) y+=1 if widget.type == "check": box.addWidget(self.var[name].check, x, y) y+=1 if hasattr(self.var[name], "button"): # Resize button if window: # Same size as in_file button = window.out_file_button size = button.width() self.var[name].button.setMaximumWidth(size) box.addWidget(self.var[name].button, x, 2) box.parentWidget().connect(self.var[name].button, QtCore.SIGNAL('pressed()'), self.var[name].button_action) x +=1 def deleteWidgets(self, box): x, y = 0, 0 for name in self.var: widget = self.var[name] # Add value to config if hasattr(widget, "widget"): self.config.set(self.__class__.__name__, name + ".widget", self.get_var(name)) if hasattr(widget, "check"): self.config.set(self.__class__.__name__, name + ".check", str(self.is_checked(name))) if widget.type == "cstring": box.removeWidget(self.var[name].check) box.removeWidget(self.var[name].widget) self.var[name].check.setParent(None) self.var[name].widget.setParent(None) if widget.type == "string": box.removeWidget(self.var[name].label) self.var[name].label.setParent(None) box.removeWidget(self.var[name].widget) self.var[name].widget.setParent(None) if widget.type == "check": box.removeWidget(self.var[name].check) self.var[name].check.setParent(None) if hasattr(self.var[name], "button"): box.removeWidget(self.var[name].button) self.var[name].button.setParent(None) x +=1 self.var = {} class io(Variables): ''' For each userformat this holds the file, and statistics about io ''' def __init__(self, file, options=None, config=None, job=None): self.file = file self.options = options self.job = job self.points = 0 self.lines = 0 Variables.__init__(self, config=config) # Methods called before and after a conversion # self.job holds the job, either reader or writer def before_(self): self.stats = {} self.before() def after_(self): self.after() def before(self): pass def after(self): pass # Stop the conversion def stop(self, message): raise ConvertError(message) # Give a warning. Best to do in after() or before() # as if used in next() or read() you will get spammed. def warning(self, message): QtGui.QMessageBox.warning(None, "Warning!", message) class writer(io): ''' Writer class, holds the functions that can be overloaded by usercreated formats To create a custom writer, overload write(self, dict) and use self.write_point() to write to the output.''' def write_(self, dict): if not dict.has_key("@@TYPE"): dict["@@TYPE"] = "Custom" if not hasattr(self, "stats"): self.stats = {} if not self.stats.has_key(dict["@@TYPE"]): self.stats[dict["@@TYPE"]] = 0 self.stats[dict["@@TYPE"]] += 1 #if self.stats[self.job].has_key(dict["@@TYPE"]): # self.stats[self.job][dict["@@TYPE"]] += 1 #else: # self.stats[self.job][dict["@@TYPE"]] = 1 '''if dict["@@TYPE"] not in self.writer_formats(): self.stop("Writer got data in a format that it does not understand.\nIt does not support:\n\n'" + dict["@@TYPE"] + "'") else: self.write(dict)''' self.write(dict) def writer_formats(self): return ["Point"] def write(self, dict): return None # Write point to the file def write_point(self, string): if hasattr(self, "points"): self.points+=1 self.file.write(string) def writer_variables(self): return None def writer_report(self): return "" class reader(io): ''' Reader class, holds the functions that can be overloaded by usercreated formats To create a custom reader, overload next(self) and use self.return_point(self, dict) to return point, and read from input with self.file.next().''' def __iter__(self): return self def next(self): self.file.next() return None # Return point to the writer def return_point(self, dict): if hasattr(self, "points"): self.points+=1 return dict def reader_formats(self): return ["Point"] def reader_variables(self): return None def reader_report(self): return "" # Classes implementing formats class Parser: ''' Parse a string with $(variables) and $functions(arg1,arg2,...). dict holds the variable values. ''' def __init__(self, dict): # Variables, add whitespace self.var = dict.copy() self.var['space'] = " " self.var['tab'] = "\t" self.var['newline'] = "\n" self.var['comma'] = "," # Functions self.functions = ["+", "-", "*", "/"] def parse(self, format): # format variables for key, item in self.var.iteritems(): if key and item: format = format.replace("$(" + key + ")", unicode(item)) # Functions functions = self.functions pos = format.find("$") # While there are dollars left while pos != -1: fparan = format.find("(", pos) nextDollar = format.find("$", pos+1) if nextDollar == -1: nextDollar = fparan+1 # Found a right paranthesis. Does it belong to this dollar? if fparan != -1 and fparan < nextDollar: # Get name fname = format[pos+1:fparan] if fname in functions: # Get end end = format.find(")", fparan) if end != -1: # Get arguments arg_string = format[fparan+1:end].split(",") res = "" if len(arg_string[0]) > 0: try: res = eval(fname.join(arg_string), {}, {}) except Exception, e: raise Exception("Interpretation of " + fname.join(arg_string) + ":\n\n" + unicode(e)) # Replace the function call with the result format = format.replace(format[pos:end+1], unicode(res)) # Go to next dollar pos = format.find("$", pos+1) return format class txt(reader, writer): ''' .txt reader/writer. Reads line by line from a file, and tries to match the line to a given format with variables. Writers lines on the usergiven format ''' def before(self): self.ignored = 0 def reader_report(self): return "Ignored " + str(self.ignored) + " line(s)
" def writer_variables(self): return [{ "name": "out_format", "type": "string", "value": "$(x),$(y),$(z)", "label": "Use format:", "description": reSmall('''txt writer ---------- The program supports several file formats which in all cases define x, y and z coordinates of each point. Since some formats also include other information like pointcode, certain formats also define format specific variables. This is a list of the variables and functions one can use with the .txt writer class, which enables the user to specify a custom writer format. Variables +++++++++ - $(pointcode) = pointcode from a .kof file. - $(x), $(y), $(z) = x, y, z coordinates for each point - $(tab), $(space), $(newline), $(comma) = Tab, space, newline, comma - Functions: $+, $/, $-, $* on the form $+(arg1,arg2,...) => arg1 + arg2 + ... Note: Valid arguments to functions are numbers and variables, as they are evaluated the first pass. Calls to functions are invalid arguments. Example +++++++ - To write "x,y,z" for all points, use the format "$(x), $(y), $(z)". - To write "x/2, y-1, z+1" for all points, use the format "$/($(x),2),$-($(y),1),$+($(z),1)"''')}] def reader_variables(self): return [{ "name": "in_format", "type": "string", "value": "$(x),$(y),$(z)", "label": "Uses format:", "description": reSmall('''txt reader ---------- This reader enables the user to read files with a custom userdefined format. The reader tries to match the format to each line of the infile, by matching and setting the variable values as it goes from left to right. The reader learns as it moves along, so if you are looking for and matching $(x), the next $(x) will only match it's allready set value. Unknown delimiter +++++++++++++++++ If you do not know the delimiter, or if it varies from line to line, you can match each delimiter to it's own variable. The reader then looks for numbers, and uses anything inbetween as delimiter. For example: - If you have a file with "x,y,z" and "x-y_z", you can use the format "$(x)$(s1)$(y)$(s2)$(z)". This will match both lines and it will set s1,s2 accordingly. If you had used $(s) instead of s1,s2, the second line would not match as "-" does not equal "_". Example ++++++++ - If you have a file with points on the format "x,y;z", you can extract the values by using the format "$(x),$(y);$(z)".''')}, { "name": "stop_mismatch", "type": "check", "value": True, "label": "Stop on mismatch", "description": reSmall('''txt reader stop on mismatch ----------------------------- If you check "stop on mismatch" the program will stop when it encounters a line that does not match the format given. If it is not checked, the program will ignore all lines that do not match.''')}] def write(self, dict): format = unicode(self.get_var("out_format")) parser = Parser(dict) self.write_point(parser.parse(format) + "\n") def get_next(self): if len(self.match) > 0: if self.match[0:2] == "$(": end = self.match[0:].find(")") # Could not find an end, it's just text if end == -1: tmp = self.match[0] self.match = self.match[1:] return tmp tmp = self.match[0:end+1] self.match = self.match[end+1:] # Match the variable with constants # and variables found so far if len(tmp) >=3: var = tmp[2:len(tmp)-1] if var in self.vars.keys(): return self.vars[var] return tmp else: tmp = self.match[0] self.match = self.match[1:] return tmp return "" def next(self): # Match format line = self.file.next().strip("\n") self.match = unicode(self.get_var("in_format")) self.vars = {} self.vars['space'] = " " self.vars['tab'] = "\t" self.vars['comma'] = "," while len(self.match) > 0: pattern = self.get_next() ahead = self.get_next() if pattern[0] != "$": if pattern[0] == line[0]: line = line[1:] self.match = ahead + self.match continue else: if self.is_checked("stop_mismatch"): self.stop("Can not map:\n\n'" + pattern + ahead + "' to " + line + "\n\nThe character '" + pattern[0] + "' does not match '" + line[0] + "'.") self.ignored +=1 return self.return_point(None) # Variable pattern else: # Two variables of unknow size not possible if not looking # for something specific if len(ahead) > 0 and ahead[0] == "$": # Find number innumber = ["1","2","3","4","5","6","7","8","9","0",".", "-"] number = [] while len(line) > 0 and line[0] in innumber: number.append(line[0]) line = line[1:] # Did not find a number if number == []: # The left is not a number # Go forward until you encounter a number. nonumber = [] while len(line) > 0 and line[0] not in innumber: nonumber.append(line[0]) line = line[1:] # Assign the value of the variable to the number. # Is only set on the first encounter of an variable, # as the rest is evaluated to the value. if len(nonumber) > 0: self.vars[pattern[2:len(pattern)-1]] = "".join(nonumber) self.match = ahead + self.match continue else: # Found a number # Assign the value to the variable. self.vars[pattern[2:len(pattern)-1]] = "".join(number) self.match = ahead + self.match continue # Find the ahead if ahead == "": self.vars[pattern[2:len(pattern)-1]] = line line = "" else: pos = line.find(ahead) if pos != -1: self.vars[pattern[2:len(pattern)-1]] = line[:pos] line = line[pos+1:] else: if self.is_checked("stop_mismatch"): self.stop("Can not map:\n\n'" + pattern + ahead + "' to '" + line + "'\n\nas the character '" + ahead + "' is not in the string '" + line + "'.") self.ignored +=1 return self.return_point(None) return self.return_point(self.vars) class pxy(reader): ''' Reader class for .pxy files ''' def __iter__(self): return self def getNextTokenize(self): line = self.file.readline() if line == "": raise StopIteration tokens = line.split(" ") tokens = [token for token in tokens if token != ""] return (tokens, line) def next(self): #line = self.file.next() #tokens = line.split(" ") #tokens = [token for token in tokens if token != ""] tokens, origline = self.getNextTokenize() # Get token 1, 2, 3 dict = {'pre': "", "coord": "", "post":""} # Only interrested in lines on the format: #| " + text + " |
Read " + str(k.reader.file.tell()) + " byte(s) from " + k.reader.file.name + "
", str(k.reader.reader_report()), "Wrote " + str(k.writer.file.tell()) + " byte(s) to " + k.writer.file.name + "
", str(k.writer.writer_report()), "