Chciałbym zrobić w HTML/JS coś takiego, że pobiera się pliki od użytkownika (jeden lub wiele plików), dla każdego wykonuje się pewne przetwarzanie lub zapamiętanie, dla którego danymi wejściowymi jest nazwa i treść pliku w formie BASE64. Na potrzeby tego tematu, owe "przetwarzanie" to wypisanie komunikatu z nazwą i fragmentem treści. Dodatkowo, jeżeli jest plik ZIP, to przetworzony ma być nie ten plik ZIP, tylko jego zawartość. Do tego celu znalazłem to: https://stuk.github.io/jszip/
Zrobiłem kod, który działa, robi to, co ma robić, ale jest przekombinowany, okrężny, stąd zakładam temat.
Tak naprawdę, są dwa sposoby przetwarzania ZIP, z których pierwszy FileZipProcess
wykorzystuje zmienne globalne i robi to prawidłowo, a drugi sposób FileZipProcess2Test
nie korzysta ze zmiennych globalnych, ale źle przypisuje nazwy plików, tylko same treści plików przetwarza zgodnie z oczekiwaniem.
Pytania są następujące:
- Funkcja
FileUploadEvent
- W jaki sposób prawidłowo zrobić przetworzenie listy plików pobranych od użytkownika? We funkcji ja dorabiam poleX
doFileReader
po to, żeby jakoś zachować nazwę i korzystam z tego, że w zdarzeniuonload
mam dostęp do tego samego obiektuFileReader
. Działa to poprawnie, ale w jaki sposób to się powinno zrobić, żeby w zdarzeniuonload
mieć i treść pliku i nazwę pliku? - Funkcja
FileZipProcess2Test
- W tym przypadku nie jest możliwe zrobienie tego samego, co w funkcjiFileUploadEvent
, bo w argumencie wywołaniathen
mam samą treść pliku, nic więcej. Jak skojarzyć obiekt, który wywołał daną obietnicę? Przekazanie nazwy pliku z poza wywołania zdarzenia nie daje oczekiwanego efektu, dla każdej pozycji jest przekazywana nazwa ostatniego pliku. - Rozumiem, że taki asynchroniczny styl programowania ma swoje zalety widoczne, jak operacje są długotrwałe, ale skąd taka duża popularność i wręcz przymus takiego programowania, bez możliwości wyboru? W tym przypadku, jak w 95% przypadków, wywołania asynchroniczne trwają niemierzalny ułamek sekundy, a przez to zaprogramowanie tego samego jest trudniejsze, dłuższy kod trzeba wyprodukować, kod jest mniej czytelny, działanie programu jest bardziej skomplikowane, podczas, gdy to samo np. w C++ zrobiłbym w pół godziny za pomocą zwykłej pętli
for
i rekurencji, bez zadawania żadnych pytań. - Ostatnie, ale najważniejsze. W jaki sposób skrócić kod, żeby robił to samo, ale bez zmiennych globalnych i prawidłowo?
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test</title>
</head>
<body>
<script type='text/javascript' src="jszip.js"></script>
<script type='text/javascript'>
// Lista nazw plików wewnątrz ZIP
let FileZipName = [];
// Lista treści plików wewnątrz ZIP
let FileZipData = [];
// Obiekt listy obiektów-plików z biblioteki JSZip
let FileZipObj;
// Funkcja wykonujaca jakies przetwarzanie, która przyjmuje nazwe pliku i treść pliku w formie BASE64
function FileProcess(Name, Data)
{
let DataView = Data.length > 100 ? Data.substr(0, 100) : Data;
console.log("Jakaś czynność z plikiem o nazwie \"" + Name + "\" i treści " + DataView);
}
// Przetwarzanie pliku ZIP poprzez wyciąganie plików składowych i przetwarzanie tych plików
function FileZipProcess()
{
// Zmienna FileZipData zawiera nazwy wszystkich plików składowych
// Przy pierwszym wywołaniu tej funkcji, zmienna FileZipData jest pusta tablicą,
// przy kolejnych wywołaniach FileZipData zawiera treści plików uzyskacne przy poprzednich wywołaniach
let I = FileZipData.length;
if (FileZipName.length > I)
{
// Jeżeli tablica treści (FileZipData) jest krótsza niż tablica nazw (FileZipName),
// to należy odczytać i zapamiętać treść pliku o nazwie będącej
// na pozycji kolejnej niż ostatnia pozycja tablicy treści plików.
// Odczytanie treści w formie BASE64 i wywołanie zdarzenia po odczytaniu
FileZipObj[FileZipName[I]].async("base64").then(function (XData) {
// Wstawienie treści jako kolejny element tablicy treści
FileZipData.push(XData);
// Ponowne wywołanie funkcji przetwarzania pliku ZIP
FileZipProcess();
});
}
else
{
// Jeżeli tablice FileZipName i FileZipData są tej samej długości, to znaczy,
// że zostały odczytane treści wszystkich plików i przyporzadkowane do nazw,
// w tym momencie można wykonać docelowe przetwarzanie nazw i treści plików
for (I = 0; I < FileZipName.length; I++)
{
FileProcess(FileZipName[I], FileZipData[I]);
}
}
}
// Inny sposób przetwarzania pliku ZIP w celach testowych
function FileZipProcess2Test(FileList)
{
console.log(FileList);
// Przetwarzane są tylko nazwy plików, katalogi sa pomijane
for (F in FileList)
{
if (F.length > 0)
{
if (F.charAt(F.length - 1) != '/')
{
console.log("Nazwa pliku \"" + F + "\"");
// Odczytanie treści w formie BASE64 i przetworzenie treści
FileList[F].async("base64").then(function (XData) {
// W tym miejscu nie wiadomo, jaką nazwę ma przetwarzana pozycja,
// nazwa przekazywana do funkcji przetwarzającej w tym miejscu jest błędna
FileProcess(F, XData);
});
}
}
}
}
// Ładowanie pliku - czynność wykonywana na jednej parze składającej się z nazwy i treści pliku
function FileLoad(Name, Data) {
// Sprawdzanie, czy jest to ZIP po rozszerzeniu nazwy
let IsZIP = false;
if (Name.length > 4) {
if (Name.substr(Name.length - 4).toLowerCase() == ".zip") { IsZIP = true; }
}
console.log("Ładowanie pliku " + Name + " - plik ZIP: " + IsZIP);
if (IsZIP)
{
// Tworzenie obiektu JSZip z biblioteki
var zip = new JSZip();
// Czyszczenie zmiennych globalnych związanych z plikiem ZIP
ZipFileObj = [];
FileZipName = [];
FileZipData = [];
// Ładowanie treści pliku ZIP do obiektu biblioteki
zip.loadAsync(FileTextToBlob(Data)).then(function (zip) {
// Po załadowaniu treści - zachowanie listy plików składowych
FileZipObj = zip.files;
// Zachowanie globalne listy nazw plików wewnątrz plików ZIP
// Katalogi nie są zapamiętywane, zapamiętane sa nazwy plików ze ścieżkami
for (F in FileZipObj)
{
if (F.length > 0)
{
if (F.charAt(F.length - 1) != '/') { FileZipName.push(F); }
}
}
// Rozpoczęcie przetwarzania globalnej listy plików
if (confirm("Przetworzyć ZIP w sposób 1 - zmienne globalne?"))
{
FileZipProcess();
}
// Inny sposób przetwarzania plików ZIP w cealach testowych
if (confirm("Przetworzyć ZIP w sposób 2 test - tylko zmienne logalne?"))
{
FileZipProcess2Test(zip.files);
}
});
}
else
{
// Przetworzenie pliku, który nie jest plikiem ZIP
FileProcess(Name, Data);
}
}
// Zamiana tekstu BASE64 na Blob - potrzebne do załadowania treści pliku ZIP do biblioteki
function FileTextToBlob(Data) {
const DataStr = atob(Data);
const DataNum = new Array(DataStr.length);
for (let i = 0; i < DataStr.length; i++)
{
DataNum[i] = DataStr.charCodeAt(i);
}
return new Blob([new Uint8Array(DataNum)], {type: "application/octet-stream"});
}
// Zdarzenie po wybraniu plików w polu typu "file"
function FileUploadEvent()
{
// Pole typu "file"
let Fld = document.getElementById("TempUpload");
// Odczytanie i przetworzenie treści dla każdego wskazanego pliku,
// możliwe jest jednoczesne wskazanie wielu plików
for (var I = 0; I < Fld.files.length; I++)
{
let FR = new FileReader();
// Dorobienie pola "X", żeby przenieść nazwę pliku
FR.X = Fld.files[I].name;
// Zdarzenie po pobraniu treści pliku
FR.onload = function(e) {
// Parametrami funkcji FileLoad jest nazwa i treść pliku w formie BASE64
// Nazwa jest przekazywana dzięki dodatkowemu polu "X"
// i możliwości dotarcia do obiektu FilleReader,
// do którego jest dorobione pole "X"
FileLoad(e.target.X, e.target.result.substr(e.target.result.indexOf(',') + 1));
};
// Wywołanie pobrania treści pliku
FR.readAsDataURL(Fld.files[I]);
}
}
</script>
<input type="file" id="TempUpload" onchange="FileUploadEvent()" multiple="multiple">
</body>
</html>