diff --git a/vi/widgets/csvexport.py b/vi/widgets/csvexport.py index 69d6e84d..50a68a38 100644 --- a/vi/widgets/csvexport.py +++ b/vi/widgets/csvexport.py @@ -10,11 +10,15 @@ from flare.viur import BoneSelector from flare.i18n import translate from flare.button import Button +from flare.viur.bones.relational import RelationalBone +from io import BytesIO, StringIO +import csv +import js, pyodide class ExportCsv(html5.Progress): def __init__(self, widget, selection, encoding = None, language = None, - separator = None, lineSeparator = None, *args, **kwargs): + separator = None, lineSeparator = None, expand=False, *args, **kwargs): super(ExportCsv, self).__init__() if encoding is None or encoding not in ["utf-8", "iso-8859-15"]: @@ -23,6 +27,7 @@ def __init__(self, widget, selection, encoding = None, language = None, if language is None or language not in conf["server"].keys(): language = conf["currentLanguage"] + self.expand = expand self.widget = widget self.module = widget.module self.params = self.widget.getFilter().copy() @@ -64,7 +69,10 @@ def nextChunkComplete(self, req): return self.data.extend(answ["skellist"]) - self.nextChunk(answ["cursor"]) + if answ["cursor"]: + self.nextChunk(answ["cursor"]) + else: + self.exportToFile() def exportToFile(self): if not self.data: @@ -73,7 +81,6 @@ def exportToFile(self): assert self.structure - defaultLanguage = conf["currentLanguage"] conf["currentLanguage"] = self.lang # Visualize progress @@ -82,79 +89,78 @@ def exportToFile(self): cellRenderer = {} struct = {k: v for k, v in self.structure} - fields = {} + fields = [] titles = [] + outFileCSV = StringIO() + writer = csv.writer(outFileCSV) - idx = 0 + subFields = {} for key, bone in self.structure: print(key, bone) - #if bone["visible"] and ("params" not in bone or bone["params"] is None or "ignoreForCsvExport" not in bone[ - # "params"] or not bone["params"]["ignoreForCsvExport"]): + if bone["visible"]: cellRenderer[key] = BoneSelector.select(self.module, key, struct) if cellRenderer[key]: cellRenderer[key] = cellRenderer[key](self.module, key, struct) - fields[key] = idx - idx += 1 - + fields.append(key) titles.append(bone.get("descr", key) or key) - + if self.expand and cellRenderer[key] and isinstance(cellRenderer[key], RelationalBone): + # Check for subfields + subFields[key] = {"dest": [], "rel": []} + for entry in self.data: + data = entry.get(key) + if isinstance(data, dict): + for subKey in (data.get("dest") or {}).keys(): + if subKey not in subFields[key]["dest"]: + subFields[key]["dest"].append(subKey) + for subKey in (data.get("rel") or {}).keys(): + if subKey not in subFields[key]["rel"]: + subFields[key]["rel"].append(subKey) + boneName = bone.get("descr", key) or key + for subKey in subFields[key]["dest"]: + titles.append("%s.dest.%s" % (boneName, subKey)) + fields.append("%s.dest.%s" % (key, subKey)) + for subKey in subFields[key]["rel"]: + titles.append("%s.rel.%s" % (boneName, subKey)) + fields.append("%s.rel.%s" % (key, subKey)) + + + writer.writerow(titles) # Export content= self.separator.join(titles) + self.lineSeparator for entry in self.data: - row = [str(None) for _ in range(len(fields.keys()))] + row = [] - for key, value in entry.items(): + for field in fields: #print(key, value) - - if key not in fields or value is None or str(value).lower() == "none": - continue - - try: - if cellRenderer[key] is not None: - try: - row[fields[key]] = cellRenderer[key].toString(value) - except: - row[fields[key]] = str(value) + value = entry + parts = field.split(".") + for part in parts: + if isinstance(value, dict): + value = value.get(part) else: - row[fields[key]] = str(value) - - except ValueError: - pass - content += self.separator.join(row) + self.lineSeparator - self["value"] += 1 - - # Virtual File - conf["currentLanguage"] = defaultLanguage - - a = html5.A() - a.hide() - self.appendChild(a) + value = "" - def replacer(obj): - '''Replace an Singel Character - to int then to to hex and the to string - after all the last to Character retrun an "%" is added - - ! => to 33 => 0x21 => %21 - ''' - return "%" + str(hex(ord(obj.group(0))))[2:] - content=re.sub("[-_.!~*'()]",replacer,content) #relpace Character for "encodeURIComponent" and "escape" - - if self.encoding == "utf-8": - - a["href"] = "data:text/csv;charset=utf-8," +html5.jseval('encodeURIComponent("%r")'%(content)) - elif self.encoding == "iso-8859-15": - a["href"] = "data:text/csv;charset=ISO-8859-15," +html5.jseval('escape("%r")'%(content)) - else: - raise ValueError("unknown encoding: %s" % self.encoding) + row.append(str(value)) + writer.writerow(row) + outFileCSV.flush() + outFileCSV.seek(0) + data = outFileCSV.read().encode(self.encoding) filename = "export-%s-%s-%s-%s.csv" % (self.module, self.lang, self.encoding, - datetime.datetime.now().strftime("%Y-%m-%d")) + datetime.datetime.now().strftime("%Y-%m-%d")) + blob = js.Blob.new(pyodide.to_js([data], dict_converter=js.Map.new), + pyodide.to_js({"type": "octet/stream"}, + dict_converter=js.Map.new)) + url = js.window.URL.createObjectURL(blob) + a = html5.A() + a["href"] = url a["download"] = filename + self.appendChild(a) a.element.click() + js.window.URL.revokeObjectURL(url) self.replaceWithMessage(translate("{count} datasets exported\nas {filename}", count=len(self.data), filename=filename)) @@ -237,6 +243,14 @@ def __init__(self, widget, *args, **kwargs ): opt.appendChild(html5.TextNode(v)) self.encodingSelect.appendChild(opt) + div = html5.Div() + div.appendChild("""""") + div.addClass("input-group") + self.expandCB = div.expandCB + + self.popupBody.appendChild(div) + + self.cancelBtn = Button(translate("Cancel"), self.close, icon="icon-cancel") self.popupFoot.appendChild(self.cancelBtn) @@ -246,11 +260,12 @@ def __init__(self, widget, *args, **kwargs ): def onExportBtnClick(self, *args, **kwargs): encoding = self.encodingSelect["options"].item(self.encodingSelect["selectedIndex"]).value + expand = self.expandCB.element.checked or True if self.langSelect: language = self.langSelect["options"].item(self.langSelect["selectedIndex"]).value else: language = None - ExportCsv(self.widget, self.widget.getCurrentSelection(), encoding=encoding, language=language) + ExportCsv(self.widget, self.widget.getCurrentSelection(), encoding=encoding, language=language, expand=expand) self.close()