| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | __title__ = "FreeCAD Spreadsheet Workbench - XLSX importer" |
| | __author__ = "Ulrich Brammer <ulrich1a@users.sourceforge.net>" |
| | __url__ = ["https://www.freecad.org"] |
| |
|
| | """ |
| | This library imports an Excel-XLSX-file into FreeCAD. |
| | |
| | Version 1.1, Nov. 2016: |
| | Changed parser, adds rad-unit to trigonometric functions in order |
| | to give the same result in FreeCAD. |
| | Added factor to arcus-function in order to give the same result in FreeCAD |
| | Added support for celltype "inlineStr" |
| | |
| | Version 1.0: |
| | It uses a minimal parser, in order to translate the IF-function into |
| | the different FreeCAD version. |
| | The other function-names are translated by search and replace. |
| | Features: |
| | - Imports tables defined inside Excel-document |
| | - Set alias definitions |
| | - Translate formulas known by FreeCAD. (see tokenDic as by version 1.1) |
| | - set cross table references |
| | - strings are imported |
| | - references to cells with strings are working |
| | |
| | known issues: |
| | - units are not imported |
| | - string support is minimal, the same as in FreeCAD |
| | """ |
| |
|
| |
|
| | import zipfile |
| | import xml.dom.minidom |
| | import FreeCAD as App |
| |
|
| | try: |
| | import FreeCADGui |
| | except ValueError: |
| | gui = False |
| | else: |
| | gui = True |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | sepToken = { |
| | "(": None, |
| | "=": None, |
| | "<": "branchLower", |
| | ">": "branchHigher", |
| | ")": None, |
| | |
| | |
| | " ": None, |
| | ",": None, |
| | "!": None, |
| | "+": None, |
| | "-": None, |
| | "*": None, |
| | "/": None, |
| | "^": None, |
| | } |
| |
|
| | branchLower = {">": None, "=": None} |
| |
|
| | branchHigher = {"=": None} |
| |
|
| |
|
| | |
| | treeDict = {"branchLower": branchLower, "branchHigher": branchHigher} |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | tokenDic = { |
| | "(": (1, "(", 0), |
| | "=": (0, "==", 0), |
| | "<>": (0, "!=", 0), |
| | ">=": (0, ">=", 0), |
| | "<=": (0, "<=", 0), |
| | "<": (0, "<", 0), |
| | ">": (0, ">", 0), |
| | ",": (0, ",", 0), |
| | ")": (-1, ")", 0), |
| | "!": (0, ".", 0), |
| | |
| | "+": (0, "+", 0), |
| | "-": (0, "-", 0), |
| | "*": (0, "*", 0), |
| | "/": (0, "/", 0), |
| | "^": (0, "^", 0), |
| | "IF": (0, "", 3), |
| | "ABS": (0, "abs", 0), |
| | "ACOS": (0, "pi/180deg*acos", 0), |
| | "ASIN": (0, "pi/180deg*asin", 0), |
| | "ATAN": (0, "pi/180deg*atan", 0), |
| | "ATAN2": (0, "pi/180deg*atan2", 0), |
| | "COS": (0, "cos", 2), |
| | "COSH": (0, "cosh", 2), |
| | "EXP": (0, "exp", 0), |
| | "LOG": (0, "log", 0), |
| | "LOG10": (0, "log10", 0), |
| | "MOD": (0, "mod", 0), |
| | "POWER": (0, "pow", 0), |
| | "SIN": (0, "sin", 2), |
| | "SINH": (0, "sinh", 2), |
| | "SQRT": (0, "sqrt", 0), |
| | "TAN": (0, "tan", 2), |
| | "TANH": (0, "tanh", 2), |
| | "AVERAGE": (0, "average", 0), |
| | "COUNT": (0, "count", 0), |
| | "MAX": (0, "max", 0), |
| | "MIN": (0, "min", 0), |
| | "STDEVA": (0, "stddev", 0), |
| | "SUM": (0, "sum", 0), |
| | "PI": (0, "pi", 1), |
| | "_xlfn.CEILING.MATH": (0, "ceil", 0), |
| | "_xlfn.FLOOR.MATH": (0, "floor", 0), |
| | } |
| |
|
| |
|
| | class exprNode(object): |
| | """This defines a tree class for expression parsing. |
| | A tree is built, to step down into the levels of the expression.""" |
| |
|
| | def __init__(self, parent, state, actIndex): |
| | self.state = state |
| | self.parent = parent |
| | self.lIndex = actIndex |
| | self.result = "" |
| |
|
| |
|
| | class FormulaTranslator(object): |
| | """This class translates a cell-formula from Excel to FreeCAD.""" |
| |
|
| | def __init__(self): |
| | self.tokenList = ["="] |
| |
|
| | def translateForm(self, actExpr): |
| | self.getNextToken(actExpr) |
| | |
| | self.resultTree = exprNode(None, 0, 1) |
| | self.resultTree.result = self.tokenList[0] |
| | self.parseExpr(self.resultTree) |
| | |
| | return self.resultTree.result |
| |
|
| | def getNextToken(self, theExpr): |
| | """This is the recursive tokenizer for an excel formula. |
| | It appends all identified tokens to self.tokenList.""" |
| | |
| | |
| | tokenComplete = False |
| | keyToken = False |
| | if len(theExpr) > 0: |
| | theTok = theExpr[0] |
| | theExpr = theExpr[1:] |
| | if theTok in sepToken: |
| | keyToken = True |
| | branch = sepToken[theTok] |
| | while branch: |
| | |
| | if theExpr[0] in treeDict[branch]: |
| | branch = treeDict[branch][theExpr[0]] |
| | theTok = theTok + theExpr[0] |
| | theExpr = theExpr[1:] |
| | else: |
| | branch = None |
| | tokenComplete = True |
| | self.tokenList.append(theTok) |
| | self.getNextToken(theExpr) |
| | else: |
| | if len(theExpr) > 0: |
| | while not tokenComplete: |
| | if not self.isKey(theExpr): |
| | theTok = theTok + theExpr[0] |
| | theExpr = theExpr[1:] |
| | if len(theExpr) == 0: |
| | tokenComplete = True |
| | else: |
| | tokenComplete = True |
| | self.tokenList.append(theTok) |
| | self.getNextToken(theExpr) |
| |
|
| | def isKey(self, theExpr): |
| | |
| | keyToken = False |
| | lenExpr = len(theExpr) |
| | if theExpr[0] in sepToken: |
| | branch = sepToken[theExpr[0]] |
| |
|
| | if branch is None: |
| | keyToken = True |
| | else: |
| | |
| | if (lenExpr > 1) and (theExpr[1] in treeDict[branch]): |
| | branch = treeDict[branch][theExpr[1]] |
| | if branch is None: |
| | keyToken = True |
| | else: |
| | if (lenExpr > 2) and (theExpr[2] in treeDict[branch]): |
| | keyToken = True |
| | else: |
| | keyToken = True |
| | return keyToken |
| |
|
| | def parseExpr(self, treeNode): |
| | token = self.tokenList[treeNode.lIndex] |
| | treeNode.lIndex += 1 |
| | if token in tokenDic: |
| | lChange, newToken, funcState = tokenDic[token] |
| | else: |
| | lChange = 0 |
| | newToken = token |
| | funcState = 0 |
| | |
| |
|
| | if token == ",": |
| | if treeNode.state == 4: |
| | newToken = ":" |
| | treeNode.state = 6 |
| | if treeNode.state == 3: |
| | newToken = "?" |
| | treeNode.state = 4 |
| |
|
| | if funcState == 3: |
| | funcState = 0 |
| | newNode = exprNode(treeNode, 3, treeNode.lIndex) |
| | self.parseIF(newNode) |
| | else: |
| | treeNode.result = treeNode.result + newToken |
| |
|
| | if funcState == 2: |
| | funcState = 0 |
| | newNode = exprNode(treeNode, 2, treeNode.lIndex) |
| | self.parseAngle(newNode) |
| | treeNode.result = treeNode.result + ")" |
| | elif funcState == 1: |
| | treeNode.lIndex += 2 |
| |
|
| | if lChange == -1: |
| | |
| | treeNode.parent.result = treeNode.parent.result + treeNode.result |
| | treeNode.parent.lIndex = treeNode.lIndex |
| | |
| | if treeNode.state < 2: |
| | |
| | if treeNode.lIndex < len(self.tokenList): |
| | self.parseExpr(treeNode.parent) |
| |
|
| | elif lChange == 1: |
| | |
| | newNode = exprNode(treeNode, 1, treeNode.lIndex) |
| | self.parseExpr(newNode) |
| | treeNode.lIndex = newNode.lIndex |
| | else: |
| | if treeNode.lIndex < len(self.tokenList): |
| | |
| | self.parseExpr(treeNode) |
| |
|
| | def parseIF(self, treeNode): |
| | |
| | treeNode.result = treeNode.result + "(" |
| | treeNode.lIndex += 1 |
| | self.parseExpr(treeNode) |
| | |
| | return |
| |
|
| | def parseAngle(self, treeNode): |
| | |
| | treeNode.result = treeNode.result + "(1rad*(" |
| | treeNode.lIndex += 1 |
| | self.parseExpr(treeNode) |
| | |
| |
|
| |
|
| | def getText(nodelist): |
| | rc = [] |
| | for node in nodelist: |
| | if node.nodeType == node.TEXT_NODE: |
| | rc.append(node.data) |
| | return "".join(rc) |
| |
|
| |
|
| | def handleWorkSheet(theDom, actSheet, strList): |
| | rows = theDom.getElementsByTagName("row") |
| | for row in rows: |
| | handleCells(row.getElementsByTagName("c"), actSheet, strList) |
| |
|
| |
|
| | def handleCells(cellList, actCellSheet, sList): |
| | for cell in cellList: |
| | cellAtts = cell.attributes |
| | refRef = cellAtts.getNamedItem("r") |
| | ref = getText(refRef.childNodes) |
| |
|
| | refType = cellAtts.getNamedItem("t") |
| | if refType: |
| | cellType = getText(refType.childNodes) |
| | else: |
| | cellType = "n" |
| |
|
| | |
| |
|
| | if cellType == "inlineStr": |
| | iStringList = cell.getElementsByTagName("is") |
| | |
| | for stringEle in iStringList: |
| | tElement = stringEle.getElementsByTagName("t")[0] |
| | theString = getText(tElement.childNodes) |
| |
|
| | |
| | actCellSheet.set(ref, theString) |
| |
|
| | formulaRef = cell.getElementsByTagName("f") |
| | if len(formulaRef) == 1: |
| | theFormula = getText(formulaRef[0].childNodes) |
| | if theFormula: |
| | |
| | fTrans = FormulaTranslator() |
| | actCellSheet.set(ref, fTrans.translateForm(theFormula)) |
| | else: |
| | attrs = formulaRef[0].attributes |
| | attrRef = attrs.getNamedItem("t") |
| | attrName = getText(attrRef.childNodes) |
| | indexRef = attrs.getNamedItem("si") |
| | indexName = getText(indexRef.childNodes) |
| | content = "<f t='{}' si='{}'/>".format(attrName, indexName) |
| | print(f"Unsupported formula in cell {ref}: {content}") |
| |
|
| | else: |
| | valueRef = cell.getElementsByTagName("v") |
| | |
| | if len(valueRef) == 1: |
| | valueRef = cell.getElementsByTagName("v")[0] |
| | if valueRef: |
| | theValue = getText(valueRef.childNodes) |
| | |
| | if cellType == "n": |
| | actCellSheet.set(ref, theValue) |
| | if cellType == "s": |
| | actCellSheet.set(ref, (sList[int(theValue)])) |
| |
|
| |
|
| | def handleWorkBookRels(theBookRels): |
| | theRels = theBookRels.getElementsByTagName("Relationship") |
| | idTarget = {} |
| | for rel in theRels: |
| | relAtts = rel.attributes |
| | idRef = relAtts.getNamedItem("Id") |
| | relRef = getText(idRef.childNodes) |
| | targetRef = relAtts.getNamedItem("Target") |
| | relTarget = getText(targetRef.childNodes) |
| | idTarget[relRef] = relTarget |
| | return idTarget |
| |
|
| |
|
| | def handleWorkBook(theBook, theBookRels, sheetDict, Doc): |
| | theSheets = theBook.getElementsByTagName("sheet") |
| | theIdTargetMap = handleWorkBookRels(theBookRels) |
| | |
| | for sheet in theSheets: |
| | sheetAtts = sheet.attributes |
| | nameRef = sheetAtts.getNamedItem("name") |
| | sheetName = getText(nameRef.childNodes) |
| | |
| | idRef = sheetAtts.getNamedItem("r:id") |
| | sheetFile = theIdTargetMap[getText(idRef.childNodes)] |
| | |
| | |
| | sheetDict[sheetName] = (Doc.addObject("Spreadsheet::Sheet", sheetName), sheetFile) |
| |
|
| | theAliases = theBook.getElementsByTagName("definedName") |
| | for theAlias in theAliases: |
| | aliAtts = theAlias.attributes |
| | nameRef = aliAtts.getNamedItem("name") |
| | aliasName = getText(nameRef.childNodes) |
| | |
| |
|
| | aliasRef = getText(theAlias.childNodes) |
| | if aliasRef and "$" in aliasRef: |
| | refList = aliasRef.split("!$") |
| | addressList = refList[1].split("$") |
| | |
| | |
| | |
| | actSheet, sheetFile = sheetDict[refList[0]] |
| | actSheet.setAlias(addressList[0] + addressList[1], aliasName) |
| |
|
| |
|
| | def handleStrings(theStr, sList): |
| | |
| | stringElements = theStr.getElementsByTagName("t") |
| | for sElem in stringElements: |
| | |
| | sList.append(getText(sElem.childNodes)) |
| |
|
| |
|
| | def open(nameXLSX): |
| |
|
| | if len(nameXLSX) > 0: |
| | z = zipfile.ZipFile(nameXLSX) |
| |
|
| | theDoc = App.newDocument() |
| |
|
| | sheetDict = dict() |
| | stringList = [] |
| |
|
| | theBookFile = z.open("xl/workbook.xml") |
| | theBook = xml.dom.minidom.parse(theBookFile) |
| | theBookRelsFile = z.open("xl/_rels/workbook.xml.rels") |
| | theBookRels = xml.dom.minidom.parse(theBookRelsFile) |
| | handleWorkBook(theBook, theBookRels, sheetDict, theDoc) |
| | theBook.unlink() |
| | theBookRels.unlink() |
| |
|
| | if "xl/sharedStrings.xml" in z.namelist(): |
| | theStringFile = z.open("xl/sharedStrings.xml") |
| | theStrings = xml.dom.minidom.parse(theStringFile) |
| | handleStrings(theStrings, stringList) |
| | theStrings.unlink() |
| |
|
| | for sheetSpec in sheetDict: |
| | |
| | theSheet, sheetFile = sheetDict[sheetSpec] |
| | f = z.open("xl/" + sheetFile) |
| | myDom = xml.dom.minidom.parse(f) |
| |
|
| | handleWorkSheet(myDom, theSheet, stringList) |
| | myDom.unlink() |
| |
|
| | z.close() |
| | |
| | theDoc.recompute() |
| | theDoc.recompute() |
| | theDoc.recompute() |
| | return theDoc |
| |
|
| |
|
| | def insert(nameXLSX, docname): |
| | try: |
| | theDoc = App.getDocument(docname) |
| | except NameError: |
| | theDoc = App.newDocument(docname) |
| | App.ActiveDocument = theDoc |
| |
|
| | sheetDict = dict() |
| | stringList = [] |
| |
|
| | z = zipfile.ZipFile(nameXLSX) |
| | theBookFile = z.open("xl/workbook.xml") |
| | theBook = xml.dom.minidom.parse(theBookFile) |
| | theBookRelsFile = z.open("xl/_rels/workbook.xml.rels") |
| | theBookRels = xml.dom.minidom.parse(theBookRelsFile) |
| | handleWorkBook(theBook, theBookRels, sheetDict, theDoc) |
| | theBook.unlink() |
| | theBookRels.unlink() |
| |
|
| | if "xl/sharedStrings.xml" in z.namelist(): |
| | theStringFile = z.open("xl/sharedStrings.xml") |
| | theStrings = xml.dom.minidom.parse(theStringFile) |
| | handleStrings(theStrings, stringList) |
| | theStrings.unlink() |
| |
|
| | for sheetSpec in sheetDict: |
| | |
| | theSheet, sheetFile = sheetDict[sheetSpec] |
| | f = z.open("xl/" + sheetFile) |
| | myDom = xml.dom.minidom.parse(f) |
| |
|
| | handleWorkSheet(myDom, theSheet, stringList) |
| | myDom.unlink() |
| |
|
| | z.close() |
| | |
| | theDoc.recompute() |
| | theDoc.recompute() |
| | theDoc.recompute() |
| |
|