Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Fixed infinite loop in csv-export #131

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 70 additions & 55 deletions vi/widgets/csvexport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -73,7 +81,6 @@ def exportToFile(self):

assert self.structure

defaultLanguage = conf["currentLanguage"]
conf["currentLanguage"] = self.lang

# Visualize progress
Expand All @@ -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))
Expand Down Expand Up @@ -237,6 +243,14 @@ def __init__(self, widget, *args, **kwargs ):
opt.appendChild(html5.TextNode(v))
self.encodingSelect.appendChild(opt)

div = html5.Div()
div.appendChild("""<input type="checkbox" [name]="expandCB" id="expandCB"><label for="expandCB">Expandieren</label>""")
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)

Expand All @@ -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()