
Tömeges állományok létrehozása táblázatból
Valós eset Fájl műveletek Excel NodeJS
2023. junius 07.
Korábban írtam egy esetről, amikor tömeges állományokat kellett létrehozni. Az állománynevek következetesek és könnyen előállíthatóak voltak Excelben. Ezért készült egy program, ami ezt a forrást használja és így generálja le az állományokat. Most egy ilyen program elkészítését be is mutatom, hogy ne csak egy esetleírás legyen belőle. Így bárki bármikor tud egy ilyet vagy ehhez hasonlót készíteni, vagy csak szimplán használni az általam már elkészítettet.
A kész program itt megtalálható: https://www.npmjs.com/package/@dovicsin/bulk-file-creator
Felkészülés
Az első lépésünk, hogy megnézzük milyen kész csomagok állnak rendelkezésre. Tudjuk, hogy táblázatot kell beolvasni és azt feldolgozni. Ehhez a convert-excel-to-json
nevű csomagot találtam, ami a táblázatból JSON
formátumot csinál. A döntés még pluszba azért is erre esett, mert minden munkalapot beolvas. Tudja kezelni, ha van fejléc, amit kulcsként a JSON-be be is tesz, de ez csak opcionális. Nekünk viszont pont erre van szükségünk.
Ezen túl fontos, hogy át kell gondolni már előre, hogy mik azok a paraméterek, amiket szeretnénk, ha változók lennének. Ami a legfontosabb, hogy a forrásfájl átadható legyen, hiszen szükségünk van arra, hogy bármilyen táblázatot meg tudjunk adni a programnak anélkül, hogy a kódhoz hozzá kelljen nyúlni. Ezen túl megadható legyen a célkönyvtár és még pár opcionális paraméter.
Lássuk mik is ezek pontosan:
Megnevezés | Paraméter | Paraméter röviden | Alapértelmezett érték | Kötelező mező | Leírás |
---|---|---|---|---|---|
Forrásállomány | —source | -s | nincs | kötelező | Forrás fájl elérése |
Célkönyvtár | —target | -t | dist/ | nem kötelező | Célkönyvtár, ahová menteni szeretnénk |
Könyvtárak létrehozás | —dirs | -d | true | nem kötelező | Minden állomány saját könyvtárat kapjon |
Fejléc hozzáadása | —addheaderkey | -a | true | nem kötelező | A fejléc értékének hozzáadása a tartalomhoz. |
Elválasztó | —join | -j | = | nem kötelező | A célfájlban a fejléc és az érékének az elválaszója |
Szuper, ha ezt már összeszedük, akkor nincs más teendőnk, mit bekérni őket.
Értékek beolvasása
Ahhoz, hogy az átadott értékeket beolvassuk a terminálból a process.arg
változót szoktuk használni, ami visszaadja egy tömbben az összes paramétert, amit megadtunk. Azonban most én egy külső csomagot használtam, aminek a neve yargs
. Ennek előnye, hogy csak fel kell paraméterezni, hogy milyen értékeket várunk és minden mást lekezel. Kezeli, ha egy paraméter kötelező, de nem adtuk meg, vagy adhatunk alapértelmezett értékeket és akkor azt kapjuk vissza abban az esetben, ha nem írta be a felhasználó. Mindenféle többi bonyolult paraméterezést is lehet adni neki, de nekünk nagyjából ennyire van szükségünk. A cél, hogy ha valami tényleg jól megvan már írva és működik, akkor ne írjuk meg újra csak használjuk. Lássuk, hogy is működik.
Először is telepítsük a csomagot.
$ npm i yargs
Majd hívjuk be a scriptünkbe
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
A következő és nagyon fontos lépés, hogy a konfigurációt hozzuk létre, hogy meg tudjuk neki mondani, hogy mit várunk pontosan.
const argsOptions = {
'source': {
alias: 's',
description: 'Source file (supported: xlsx, xls, ods)',
demandOption: true,
},
'target': {
alias: 't',
description: 'Target directory',
demandOption: true,
default: 'dist/'
},
'dirs': {
alias: 'd',
description: 'Create directory every files',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'header': {
alias: 'h',
description: 'The source incluse is header',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'addheaderkey': {
alias: 'a',
description: 'Add header key in content with join separator',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'join': {
alias: 'j',
description: 'The target file content line and header separator',
demandOption: true,
default: '='
}
}
Láthatjuk, hogy csak simán egy objektumban fel kell sorolni a paramétereket. Meg kell adni kulcsnak a paraméter nevét, amiben egy objektum lesz, aminek az értékei pedig nagyon beszédesek. Az alias
a rövid név, description
a leírás, demandOption
hogy mező kötelező vagy sem és a default
értékként lehet megadni, hogy mi az alapértelmezett értéke, abban az esetben, ha a felhasználó nem adta meg. Ezzel fel is van készítve rá a programunk a paraméter bekérésre. Már csak meg kell hívni a függvényt és átadni neki az objektumot.
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 -t [string]')
.option(argsOptions)
.help()
.argv;
Ezzel már biztosítva is van a paraméterek beolvasása. Abban az esetben, ha minden kötelező paraméter ki van töltve, akkor azt argv
nevű változóban lesz az összes bejövő változó. Itt megtalálunk mindent, amire szükségünk van. Ezeket vegyük külön változókba és alakítsuk át úgy, hogy nekünk később csak használni kelljen.
const source = argv['source'];
const target = argv['target'] + (!argv['target'].endsWith("/") ? "/" : "");
const createDirs = argv['dirs'] ? argv['dirs'] === "true" : false;
const addkey = argv['addheaderkey'] ? argv['addheaderkey'] === "true" : false;
const join = argv['join'] ? argv['join'] : false;
A forrás csak vegyük ki az úgy jó ahogy van. A cél könyvtár végére tegyünk egy /
jelet, ha nincs, hiszen ez egy könyvtár. Így késöbb csak használni kell és az állomány nevét is csak utána kell tenni. A következő pedig, hogy a könyvtárakat létre kell hozni vagy sem. Itt egy eldöntendő boolean
igaz/hamis (true/false
) értéket várunk, azonban a bejövő érték az egy szöveg és ezért ezt át kell alakítani. Így ha az értéke szöveges true
tehát igaz, akkor igaz minden más esetben hamis. Ugyanezt a metodikát használjuk a fejléc hozzáadásnál is. Aztán a végén az elválasztót is csak simán kitesszük egy változóba, mert azzal fűzzük össze, amit kaptunk.
Célkönyvtár ellenőrzés
Ellenőrizni kell, hogy e a célkönyvtár létezik vagy sem, mert ha nem akkor garantált a hiba, ha dolgozni szeretnénk benne. Tehát ha nem létezik, akkor ebben az esetben létre is kell hozni.
if (!fs.existsSync(target)) {
fs.mkdirSync(target);
}
Táblázat beolvasása
Ha ezzel megvagyunk, akkor be kell olvasni a táblázatot, hogy az adatokat fel tudjuk dolgozni. Itt nincs más teendőnk, mint megmondani melyik táblázatot olvassa be a függvény.
const result = excelToJson({
sourceFile: source,
header:{
rows: 1
},
columnToKey: {
'*': '{{columnHeader}}'
}
});
console.info("Táblázat beolvasva.")
A sikeres beolvasás után kapunk egy result
változót benne egy objektummal, amiben további objektumok vannak. Ezek kulcsai a munkalapok nevei és benne a sorok vannak. Valahogy így.
{
"sheet-1": [
{
"title": "Cím...",
"description": "Leírás...."
},
{
"title": "Második cím...",
"description": "Leírás...."
}
],
"sheet-2": [...]
{
Végén csak vissza kell jelezni, hogy ez megtörtént és mehetünk tovább.
Adatok feldolgozása
Az utolsó ponton már csak a beolvasott adatokat kell feldolgozni.
Első lépésként a munkalapok nevét olvassuk és azokon fogunk végig menni. Illetve elhelyezünk egy számlálót, hogy tudjuk hány állományt hoztunk létre.
const sheetNames = Object.keys(result);
let count = 0;
A következő lépés, hogy végig megyünk ezen a tömbön és egyesével feldolgozzuk. Itt most egybe fogom berakni a kódot és közte a magyarázattal lesz megtörve.
sheetNames.forEach(name => {
let fileDir = `${target}${name}/`;
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir)
Tehát itt végig megyünk a fülelnevezéseken és, ha nem létezik hozzá könyvtár, akkor azt létrehozzuk. Ugyanis ezekben a könyvtárakban lesznek a sorokból generált fájlok, így tudunk több munkalapot is szépen strukturáltan legenerálni.
result[name].forEach(data => {
if (!data.name) {
console.error("Hiányzó név tulajdonság!")
return;
}
Majd végig megyünk az összes soron az aktuális munkalapon és megnézzük van-e fájlnév attribútum. Ne feledjük ez már az Excelből jön, ahol elvárjuk, hogy az első oszlop a name
legyen, a második pedig a kiterjesztés extension
. Minden más érték megy a cél állományba.
Tehát ha nincs név tulajdonság, akkor a fájlt nem tudjuk létrehozni. Ezt jelezni kell és lépni tovább a következő sorra.
let filePath = fileDir;
if (createDirs) {
filePath = `${fileDir}${data.name}/`;
}
if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
A következő lépés, hogy meg kell nézni, hogy szeretnénk e az összes állományt külön könyvtárba tenni. Amennyiben igen, akkor a fájllal megegyező nevű könyvtárat létre kell hozni, abban az esetben, ha az nem létezik. Ha pedig nem szeretnénk külön, hanem csak a munkalappal megegyező könyvtárban ömlesztve, akkor itt nincs teendő.
const name = data.name + (data.extension?`.${data.extension}`: "");
const writedData = Object.keys(data)
.filter(key => key !== "name" && key!=="extension" )
.map(key => `${addkey?key+join:""}${data[key]}`);
Majd összetesszük az állomány nevet és ki kérjük az adatokat az állományhoz. Ezekből az adatokból kiszűrjük a name
és az extension
paramétereket, hiszen ezeket csak az állomány nevéhez használjuk fel.
fs.writeFileSync(`${filePath}${name}`, writedData.join("\n"));
count++;
})
});
Az utolsó része pedig, hogy ki írjuk az adatokat, majd növeljük a számlálót. Fontos, hogy ezek most tömbként vannak meg a writeData
változóban, ezért a join(\n)
függvénnyel összefűzzük szöveggé és így lesz minden oszlop értéke egy új sor a végeredményben.
Majd egy dolog maradt, jelezzünk vissza, ha kész és ha tudunk adjunk plusz információt is főleg, ha már számoltunk.
console.log(`Kész. Létrehozva ${sheetNames.length}db könyvtár (${sheetNames.join(", ")}) benne ${count}db állomány.`)
Lássuk egyben a kódot
A teljes szoftvert feltettem a GitHub tárolóba és az npm csomagot is bárki számára elérhető formában feltettem az npmjs.com-ra, mind a két helyen részletes használati leírással.
https://github.com/dovicsin/bulk-file-creator
https://www.npmjs.com/package/@dovicsin/bulk-file-creator
Maga a kód
const fs = require('fs');
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const excelToJson = require('convert-excel-to-json');
const argsOptions = {
'source': {
alias: 's',
description: 'Source file (supported: xlsx, xls, ods)',
demandOption: true,
},
'target': {
alias: 't',
description: 'Target directory',
demandOption: true,
default: 'dist/'
},
'dirs': {
alias: 'd',
description: 'Create directory every files',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'header': {
alias: 'h',
description: 'The source incluse is header',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'addheaderkey': {
alias: 'a',
description: 'Add header key in content with join separator',
choices: ['true', 'false'],
demandOption: true,
default: 'true'
},
'join': {
alias: 'j',
description: 'The target file content line and header separator',
demandOption: true,
default: '='
}
}
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 -t [string]')
.option(argsOptions)
.help()
.argv;
const source = argv['source'];
const target = argv['target'] + (!argv['target'].endsWith("/") ? "/" : "");
const createDirs = argv['dirs'] ? argv['dirs'] === "true" : false;
const addkey = argv['addheaderkey'] ? argv['addheaderkey'] === "true" : false;
const join = argv['join'];
if (!fs.existsSync(target)) {
fs.mkdirSync(target);
}
const result = excelToJson({
sourceFile: source,
header:{
rows: 1
},
columnToKey: {
'*': '{{columnHeader}}'
}
});
console.info("Táblázat beolvasva.")
const sheetNames = Object.keys(result);
let count = 0;
sheetNames.forEach(name => {
let fileDir = `${target}${name}/`;
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir)
result[name].forEach(data => {
if (!data.name) {
console.error("Hiányzó név tulajdonság!")
return;
}
let filePath = fileDir;
if (createDirs) {
filePath = `${fileDir}${data.name}/`;
}
if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
const name = data.name + (data.extension?`.${data.extension}`: "");
const writedData = Object.keys(data)
.filter(key => key !== "name" && key!=="extension" )
.map(key => `${addkey?key+join:""}${data[key]}`);
fs.writeFileSync(`${filePath}${name}`, writedData.join("\n"));
count++;
})
});
console.info(`Kész. Létrehozva ${sheetNames.length}db könyvtár (${sheetNames.join(", ")}) benne ${count}db állomány.`)