| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| __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() |
|
|