/* Excel-CSV-Export-Erfassungsliste einlesen und bearbeiten
Eingabedaten liegen in CP1252 vor, nicht in UTF-8.
Diese Datei ist in CP1252, bei UTF-8 funktionieren die Umlaute auf der Konsole nicht richtig.
Eine BOM wird von cscript.exe nicht verdaut.
Aufruf: cscript /Nologo Erfassungsliste.js < Erfassungsliste.csv > Erfassungsliste4.csv
Wenn als Unicode-Text gespeichert (in Wahrheit Tab-separierte Werte in UTF-16):
Aufruf: cscript /u /nologo Erfassungsliste.js < Erfassungsliste.txt > Erfassungsliste4.csv
220720 erstellt
+220725 Tab statt Semikolon als weiterer Feldtrenner, für Excels Export-als-Text
+220725 3 neue Tabellenspalten: Typ, Messplatz,Fremdfirma, generiert aus Zwischenüberschriften
-220726 System-Listentrennzeichen (besserer Begriff: Feld- oder Zelltrenner) statt hartkodiertes ";" verwenden
Einfach beide Feldtrenner ("," und ";") zu akzeptieren ist nicht sicher,
weil Excel das jeweils andere Zeichen nicht escaped; man müsste eine umständliche
Häufigkeits- oder Plausibilitätsanalyse machen
Überraschenderweise escaped Excel den Feldtrenner (';') auch beim „Textexport“ = TSV-Export.
Was Excel nicht tun /müsste/. Sowie den Tabulator beim CSV-Export, ebenfalls unlogisch.
Hier ist dieses Verhalten von Vorteil, weil '\t' und ';' gleich behandelt werden können.
+220726 OneSAP-Nummer aus „sap.tsv“ in linke Spalte mergen
+220727 Variable Spaltenzuordnung beim Erkennen der Zeilen
+220727 Titelzeile ohne Schlusspunkte zur Ladbarkeit in MS Access
+220730 Offensichtlich verrutschte Folgezeileneinträge in vorhergehende Zeile mergen
*/
var forExcel=true;
// Feldtrennzeichen von Registry lesen
var sh = new ActiveXObject("WScript.Shell");
var FS = sh.RegRead("HKEY_CURRENT_USER\\Control Panel\\International\\sList")||',';
var sDecimal = sh.RegRead("HKEY_CURRENT_USER\\Control Panel\\International\\sDecimal")||'.'; // Punkt oder Komma?
// Feldtrenner wie in awk, Semikolon bei deutschem Windows, Komma bei englischem Windows
//var ExcelApp = new ActiveXObject("Excel.Application");
//var ExcelSheet = new ActiveXObject("Excel.Sheet");
//ExcelSheet.Application.Visible = true;
//WScript.StdErr.WriteLine(ExcelSheet.ActiveSheet.Cells(1,1).Value);
//ExcelSheet.Application.Quit();
// Dateinamen von Kommandozeile lesen
var argv = WScript.Arguments.Unnamed; // Arrayzugriff mit runden(!!) Klammern
var txt, stdout = WScript.StdOut;
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (argv.length>=1) { // Inputfilename
WScript.StdErr.WriteLine("Eingabedatei: "+argv(0));
var f = fso.OpenTextFile(argv(0),1,false,-1); // -1 == TristateUseDefault, scheint die BOM zu prüfen
txt = f.ReadAll();
f.close();
if (argv.length>=2) { // Outputfilename
stdout = fso.OpenTextFile(argv(1),2,true,-1); // -1 schreibt UTF-16 mit BOM
}
}else txt = WScript.StdIn.ReadAll();
// Zuordnungsliste OneSAP-Nummer ↔ Inventarnummer laden
// Diese zweispaltige TSV-Tabelle kann recht einfach aus den Mockup-Listen extrahiert werden
// und ist in ASCII (nicht in Unicode, ohne Umlaute, ohne Spaltentitel) vorzuhalten.
// Ist keine derartige Datei vorhanden, wird keine Spalte links in den Ausgabedaten hinzugefügt.
// TODO: Alle Dateien (*.sap.*) laden, Eineindeutigkeit prüfen und verwenden, falls erforderlich.
var filename="sap.tsv";
var onesap = fso.OpenTextFile(filename,1,false);
if (onesap) {
var map={size:0}; // JScript3 hat kein eingebautes „new Map()“, Behelf über Objekt = assoziatives Array
while (!onesap.AtEndOfStream) {
var z=onesap.ReadLine().split('\t',2); // links: OneSAP-Nummer, rechts: Inventarnummer
if (map[z[1]]) {
WScript.StdErr.WriteLine("Fehler in "+onesap.Name+": Doppelte Inventarnummer bei Zeile "+map[z[1]].line+" und "+onesap.Line+"!");
}else{
map[z[1]]={sap:z[0],file:filename,line:onesap.Line-1};
map.size++; // "size" kommt nicht als Inventarnummer vor
}
}
onesap.close();
onesap=map;
WScript.StdErr.WriteLine("OneSAP-Nummern werden eingefügt: Vorgabe hat "+map.size+" Einträge.");
}
var len = txt.length; // Anzahl Zeichen = Bytes
var quote='', eat='', col=0, field="";
var a=[], b; // a = Zeilenvektor, b = (aktueller) Spaltenvektor
//var start=0, step=1;
//if (len>=2 && !(len&1) && txt.charCodeAt(0)==0xFF && txt.charCodeAt(1)==0xFE) {
// ReadAll macht die UTF-Erkennung nicht selbst. JScript verwendet intern ohnehin UTF-16 für seine Strings.
// Es sieht so aus, als ob JScript mit Nullen Probleme hat!
// WScript.StdErr.WriteLine("Dateiformat: UTF-16LE");
// start=2; step=2;
//}
WScript.StdErr.Write("Gelesen: "+len+" Zeichen, jetzt läuft der Parser ...");
for (var i=0; i<len; i++) {
var c=txt.charAt(i); // Möglicherweise sind reguläre Ausdrücke schneller, aber einzelzeichenweise geht's auch noch akzeptabel schnell: ca. 1000 Datenzeilen pro Sekunde
if (c==eat) {eat=''; continue;}
eat='';
if (quote) { // Wenn innerhalb „quoted string“
if (c==quote) { // Mögliches Ende
if (i+1<len && txt[i+1]==quote) { // doppelt = einfach literal (derzeit kein Testfall)
++i;
field+=c;
}else{ // einfach = Feldende
quote='';
}
}else{
field+=c; // kann auch FS und '\n' und '\t' sein!
}
}else if (c=='\r' || c=='\n' || c==FS || c=='\t') { // Zeilenende oder Feldende?
field=field.replace(/^\s+|\s+$/g,""); // Vor- und nachlaufenden Weißraum tilgen
if (field) { // Wenn dann noch etwas übrig geblieben ist …
if (!b) b=[]; // Neues Zeilenarray erzeugen, wenn noch nichts gespeichert
b[col]=field; // JScript füllt nichtzusammenhängende Indizes mit undefined auf und aktualisiert b.length
}
field="";
if (c=='\r' || c=='\n') {
if (c=='\r') eat='\n'; // dieses Zeichen beim nächsten getchar() wegignorieren
else if (c=='\n') eat='\r';
a.push(b); // Referenz kopieren = Array retten
col=0;
b=undefined; // Diese Referenz tilgen
}else ++col;
}else if (c=='"') {
quote=c; // Flag = Endezeichen setzen und nicht übernehmen
}else{
field+=c; // Jedes andere Zeichen literal nehmen
}
}
if (b) WScript.StdErr.WriteLine("Am Ende der Daten fehlt Zeilenschaltung!");
WScript.StdErr.WriteLine(" fertig, Zeilenzahl: "+a.length);
function countArray(b) { // zählt Nicht-Null-Elemente eines Arrays, hier: nicht-leere Strings
var c = 0;
for (var i=0; i<b.length; i++) if (b[i]) ++c;
return c;
}
function firstIndex(b) { // Index des ersten Nicht-Null-Elements eines Arrays, -1 wenn keins
for (var i=0; i<b.length; i++) if (b[i]) return i;
return -1;
}
// Filtern: Leere Spalten der Datenzeilen auffinden und Eineindeutigkeit der Inventarnummer prüfen
function isKopfzeile(b) {
if (countArray(b)<4) return false;
return b[firstIndex(b)].match(/^Invent/i); // Erste nicht-leere Spalte enthält "Inventar" oder "inventory" oder so
}
// WAS eine Datenzeile ist lässt sich nicht ganz sicher finden!
// Davon ausgehend, dass die Kopfzeile bereits detektiert wurde, eine Zeile mit mindestens 4 besetzten Spalten.
var Extraspalten={"Typ":undefined,"Messplatz":undefined,"Fremdfirma":undefined,Datenzeilen:0};
function isDatenzeile(b) {
var ret = countArray(b)>=4;
if (ret) ++Extraspalten.Datenzeilen; // wird benötigt um das Umschalten von Datenzeilen auf Zwischenüberschriften zu erkennen
return ret;
}
// Eine Zwischenüberschrift sei etwas mit genau einer gefüllten Spalte
function isZwischenüberschrift(b) {
var ret = countArray(b) == 1;
if (ret && Extraspalten.Datenzeilen) {
Extraspalten.Typ=undefined;
Extraspalten.Messplatz=undefined;
Extraspalten.Fremdfirma=undefined;
Extraspalten.Datenzeilen=0;
}
return ret;
}
function findOneSap(invnr) {
}
var invnr={}; // Normalerweise Map, aber das gibt's nicht bei JS3. Hier wird die Zeilennumer der Definition zugewiesen.
var spaltenr=[]; // Zähler für Spalteneinträge
var okay=true;
for (var i=0; i<a.length; i++) {
var b=a[i];
if (b && !isKopfzeile(b) && isDatenzeile(b)) {
for (var j=0; j<b.length; j++) if (b[j]) spaltenr[j] = (spaltenr[j]||0)+1; // Einträge zählen (true zuweisen würde es auch tun)
var k=b[firstIndex(b)];
if (invnr[k]) {
WScript.StdErr.WriteLine("Mehrfache Inventarnummer "+k+" aus Zeile "+invnr[k]+" in Zeile "+(i+1)+"!");
okay=false;
}else invnr[k]=i+1; // Zeilennummer für Fehlermeldung zuweisen
}
}
if (okay) WScript.StdErr.WriteLine("Okay: Keine mehrfachen Inventarnummern gefunden.");
WScript.StdErr.WriteLine("Spalten-Nutzung der (vermeintlichen) Datenzeilen:");
for (var i=0; i<spaltenr.length; i++) {
WScript.StdErr.Write((spaltenr[i]||0)+" ");
}
WScript.StdErr.WriteLine();
// Aufeinanderfolgende Zeilen auf verrutschte Elemente prüfen und ggf. auf eine Zeile bringen
for (var i=0; i<a.length-1; i++) {
var b=a[i], bb=a[i+1];
if (b && bb) { // Zwei aufeinanderfolgende, nicht-leere Zeilen?
var l=Math.max(b.length,bb.length), candidate=true;
for (j=0; j<l; j++) if (b[j] && bb[j]) {candidate=false; break;} // Komplett disjunkte Spalten?
if (candidate) {
for (j=0; j<l; j++) if (bb[j]) b[j]=bb[j]; // Folgespaltenelemente in aktuelle übertragen
a[++i]=undefined; // Folgezeile löschen
}
}
}
// Datenzeilen zusammenschieben. Danach sind Datenzeilen leider nicht mehr als solche erkennbar.
var killed=0;
for (var i=0; i<a.length; i++) {
var b=a[i];
if (b) {
if (isKopfzeile(b)) { // Tabellenkopf zusammenschieben und aufhübschen
var c=[];
if (onesap) c.push("OneSAP");
for (var j=0; j<b.length; j++) if (b[j]) c.push(b[j].replace(/\s+/g," ").replace(/- /g,"-").replace(/-(?=[a-z])/g,"")); // Nicht-leere Spalten kopieren
// Das zweite replace() verbindet "Inventar- Nr." zu "Inventar-Nr.", das dritte "Prüf-turn." zu "Prüfturn.".
// Könnte man auch global für die Datenzeilen machen.
Extraspalten.SpalteStart=onesap?1:0;
for (var j=0; j<spaltenr.length; j++) if (spaltenr[j]) ++Extraspalten.SpalteStart;
if (c.length==Extraspalten.SpalteStart) WScript.StdErr.WriteLine("Okay: Kopfzeile hat gleiche Spaltenzahl ("+c.length+") wie Datenzeilen");
else WScript.StdErr.WriteLine("Problem: Kopfzeile hat Spaltenzahl "+c.length+", Datenzeilen Spaltenzahl "+Extraspalten.SpalteStart+"!");
if (Extraspalten.SpalteStart<c.length) Extraspalten.SpalteStart=c.length; // Maximum beider
c[Extraspalten.SpalteStart]="Typ";
c[Extraspalten.SpalteStart+1]="Messplatz";
c[Extraspalten.SpalteStart+2]="Fremdfirma";
a[i]=c; // Zeilenvektor ersetzen
}else if (isDatenzeile(b)) { // Datenzeile zusammenschieben
var c=[];
if (onesap) {
var k=b[firstIndex(b)];
var entry=onesap[k]; // Map-Zugriff
if (entry) {
c.push(entry.sap);
entry.used=i+1; // Zeilennummer vermerken
}else c.push(undefined);
}
for (var j=0; j<b.length; j++)
if (spaltenr[j])
c.push(b[j]?b[j].replace(/\s+/g," "):undefined); // Nicht-leere Spalten kopieren, dabei Mehrfachleerzeichen tilgen, '\n' durch ' ' ersetzen
c[Extraspalten.SpalteStart]=Extraspalten["Typ"];
c[Extraspalten.SpalteStart+1]=Extraspalten["Messplatz"];
c[Extraspalten.SpalteStart+2]=Extraspalten["Fremdfirma"];
a[i]=c; // Zeilenvektor ersetzen
}else if (isZwischenüberschrift(b)) {
var ü = b[firstIndex(b)];
if (ü.match(/^Messplatz/)) Extraspalten["Messplatz"]=ü.replace(/^Messplatz\s?:?\s*/,"");
else if (ü.match(/^Fremdfirma/)) Extraspalten["Fremdfirma"]=ü.replace(/^Fremdfirma\s?:?\s*/,"");
else{
if (Extraspalten["Typ"]) {
WScript.StdErr.WriteLine("Zwischenüberschrift ohne Datenzeile in Zeile "+(i+1)+", vorherige Definition „"+Extraspalten["Typ"]+"“ verfällt!");
killed++;
}
Extraspalten["Typ"]=ü;
}
a[i]=undefined;
}else{
WScript.StdErr.WriteLine("Zeile "+(i+1)+" gekillt, 1. Spalte = "+a[i][0]);
a[i]=undefined;
killed++;
}
}
}
if (Extraspalten["Typ"] && !Extraspalten.Datenzeilen) {
WScript.StdErr.WriteLine("Zwischenüberschrift ohne Datenzeile am Ende, Definition „"+Extraspalten["Typ"]+"“ verfällt!");
killed++;
}
if (killed) WScript.StdErr.WriteLine(killed+" Zeilen gekillt.");
// Ungenutzte SAP-Listeneinträge zählen
if (onesap) {
var unusedSap=0;
for (var e in onesap) if (e!="size" && !onesap[e].used) unusedSap++;
if (unusedSap) {
WScript.StdErr.WriteLine("Von der OneSAP-Zuordnungsliste "+(unusedSap==1?"wurde "+unusedSap+" Eintrag":"wurden "+unusedSap+" Einträge")+" nicht genutzt!"
+(unusedSap<=10?" Liste:":""));
// Liste der ungenutzten Einträge ausgeben, wenn <=10. Im MSC-Stil (Zeilennummern in Klammern), GNU wäre Zeilennummer nach Doppelpunkt
// Die Ausgabe erfolgt unsortiert, je nach innerer Verarbeitungslogik von JScript.
if (unusedSap<=10) for (var e in onesap) if (e!="size" && !onesap[e].used) {
WScript.StdErr.WriteLine("\t"+onesap[e].file+"("+onesap[e].line+"): "+onesap[e].sap+"\t"+e);
}
}
}
// Ausgeben
WScript.StdErr.WriteLine("Ausgabedatei wird geschrieben ...");
var zahl=new RegExp("^-?\\d+("+sDecimal+"\\d+)?(e-?\\d+)?$","i");
for (var Kopf=true, i=0; i<a.length; i++) {
var b=a[i];
if (b) { // Gekillte und Leerzeilen nicht ausgeben = kompakte Liste
for (var j=0;;) {
var k=b[j];
if (k) {
k=k.replace(/"/g,'""'); // Quotes verdoppeln (derzeit kein Testfall)
if (forExcel) {
k='"'+(k.match(zahl)?'\t':'')+k+'"'; // Mit dem Tabulator bei Ziffernfolge String-Import erzwingen
}else{ // for Access
if (Kopf) k=k.replace(/\.$/,''); // Schlusspunkt am Spaltentitel entfernen
k='"'+k+'"';
}
stdout.Write(k);
}
if (++j>=b.length) break;
stdout.Write(FS);
}
stdout.WriteLine();
Kopf=false;
}
}
Detected encoding: ASCII (7 bit) | 9
|