filippg
Goto Top

Skript für Template-Filegenerierung

Suche Programm, dass aus einem Template und einer .csv für jede Zeile ein ausgefülltes Template erzeugt

Hallo,

ich suche etwas, das bestimmt irgendwer schon irgendwo im Internet gepostet hat, aber ich finde es nicht:
Ich habe ein Programm, das z.B. Nutzeranlagen als einzelne XML-Files entgegennimmt. Wenn ich nun eine Liste mit 20 Nutzeranlagen habe, muss ich 20 XML-Files erstellen, in denen ich jeweils nur den Name veränder. Das ist irgendwie umständlich, und muss doch auch einfacher gehen.
Ich suche also ein Skript mit folgender Funktionalität:
Übergeben werden zwei Dateien: Eine .csv mit beliebiger Spalten- und Zeilenzahl und ein Template. In dem Template sind Platzhalter entsprechend den Spaltennamen enthalten. Für jede Zeile erzeugt das Skript nun eine neue Datei, in der die Platzhalter mit den entsprechenden Spaltenwerten ersetzt werden.

Kann mir jemand bei der Suche weiterhelfen?

Danke

Filipp

Content-Key: 150165

Url: https://administrator.de/contentid/150165

Printed on: February 25, 2024 at 12:02 o'clock

Member: bastla
bastla Sep 01, 2010 at 19:53:44 (UTC)
Goto Top
Hallo filippg!

Wozu suchen - selber schreiben face-wink ...

... als VBS schematisch (und ungetestet) etwa so:
CSV = "D:\Die.csv"  
Delim = ";"  
Template = "D:\Template.xml"  
Pfad = "D:\Ausgabepfad XML-Dateien"  

Set fso = CreateObject("Scripting.FileSystemObject")  
TemplateText = fso.OpenTextFile(Template).ReadAll 'Templateinhalt einlesen  
Daten = Split(fso.OpenTextFile(CSV).ReadAll, vbCrLf) 'Daten aus CSV in Array einlesen  
Felder = Split(Daten(0), Delim) 'Feldnamen = Platzhalter aus erster Zeile der CSV in  Array einlesen  

For i = 1 To UBound(Daten) 'alle Datensätze durchgehen  
    Info = Split(Daten(i), Delim) 'Informationen aus den einzelnen Feldern des Datensatzes in Array einlesen  
    XML = Pfad & "\" & Info(0) & ".xml" 'Dateiname aus erstem Feld übernehmen  
    XMLText = TemplateText 'Originaltemplate als Ausgangspunkt  
    For j = 0 To UBound(Info) 'alle Felder durchgehen  
        XMLText = Replace(XMLText, Felder(j), Info(j)) 'anhand der Feldnamen die Platzhalter durch die eingelesenen Informationen erstezen  
    Next
    fso.CreateTextFile(XML).Write XMLText 'Datei schreiben  
Next
Vorausgesetzt wird, dass die Platzhalter als Feldnamen in der der ersten Zeile der CSV-Datei in der exakt gleichen Schreibweise stehen und es keine Fehler in der Struktur der Daten gibt.
Da ich keine Informationen über die Namen der Zieldateien hatte, wird einfach der Inhalt des jeweils ersten Feldes als Name verwendet ...

[Edit] Inzwischen oberflächlich getestet und einige Fehler korrigiert. [/Edit]

Grüße
bastla
Member: filippg
filippg Sep 03, 2010 at 17:28:43 (UTC)
Goto Top
Hallo Bastla,

vielen Dank für dein Skript.
Es sind halt immer die Feinheiten, die das Skripten anstrengend machen - und ich hatte gehofft ein Skript zu finden, das diese Feinheiten schon enthält, so dass ich mich darum nicht selber kümmern muss. Z.B. wird das Skript nicht damit klarkommen, wenn das Delimiterzeichen in einem Feld enthalten ist. Hübsch wäre auch ein Skript, dass "vernünftig" reagiert, wenn die Ausgabedatei schon vorhanden ist, etc...
Aber scheinbar werde ich mir da wirklich gelegentlich mal ewtas selbst basteln müssen.... mal schauen, wann ich dazu komme, poste es dann hier.

Gruß

Filipp
Member: bastla
bastla Sep 04, 2010 at 20:06:30 (UTC)
Goto Top
Hallo Filipp!

Mein Ansatz war ja nur als Grundgerüst gedacht - eine "vernünftige" Reaktion auf bereits vorhandene Ausgabedateien kann ja, wenn sie einmal definiert ist face-wink, nicht so schwer zu realisieren sein ...

Etwas kniffliger:
Z.B. wird das Skript nicht damit klarkommen, wenn das Delimiterzeichen in einem Feld enthalten ist.
Abgesehen davon, dass ja Du die CSV erstellst oder zumindest Einfluss darauf haben solltest und Dir wohl nicht selbst Knüppel zwischen die Beine werfen wirst, ließe sich auch das (vermutlich noch eleganter) lösen - zB als mit "cscript" zu startende Demo für eine Zeile:
Zeile = """;Feld1"";Feld2;""Feld3;"";"""";""Feld 5"";""Feld 6 mit ; im Wert"";;;Feld 9"  
Delim = ";"  

Auf = "{"   'beliebiges nicht in den Daten enthaltenes Zeichen  
Zu = "}"    'anderes nicht in den Daten enthaltenes Zeichen  
Dummy = "@" 'noch ein nicht in den Daten enthaltenes Zeichen  

WScript.Echo "Original   :" & Zeile  

'Unmittelbar aufeinander folgende Anführungszeichen kennzeichnen ein leeres Feld und ...  
'... können daher vorweg durch "Nichts" ersetzt werden  
Z = Replace(Zeile, """""", "")  
WScript.Echo "Leer ohne "":" & Z  

Set rE = New RegExp
rE.Global = True
'Suche nach paarweisen Anführungszeichen ...  
rE.Pattern = "("")([^""]*)("")"  
'... und Ersetzen durch öffnende und schließende Klammern  
Z = rE.Replace(Z, Auf & "$2" & Zu)  
WScript.Echo "Geklammert :" & Z  

'Suche nach Vorkommen von Trennzeichen innerhalb der Klammern, zB {abc;xyz} und ...  

'Aufteilung:  (          $1         )     (   $2    )     (         $3        )  
rE.Pattern = "(" &Auf&"[^"&Delim&"]*)" & "("&Delim&")" & "([^"&Delim&"]*"&Zu&")"  
'Ergebnis:             $1={abc               $2=;                 $3=xyz}  

'... Ersetzen des im Feld enthaltenen Trennzeichens = $2 durch einen "Dummy"-Wert ...  
Z = rE.Replace(Z, "$1" & Dummy & "$3")  
'... sowie Entfernung der umschließenden Klammern "{" und "}"  
Z = Replace(Replace(Z, Auf, ""), Zu, "")  
WScript.Echo "mit Dummy  :" & Z & vbCrLf  

'Zerlegung anhand des Trennzeichens in Felder  
Felder = Split(Z, Delim)
For i = 0 To UBound(Felder) 'Durchlaufen der einzelnen Felder  
    'Demo-Ausgabe mit Rückwandlung des "Dummy"-Wertes in das Trennzeichen  
    WScript.Echo "Feld " & i + 1 & ": |" & Replace(Felder(i), Dummy, Delim) & "|"  
Next
Vorausgesetzt habe ich, dass Felder, welche das Trennzeichen enthalten, auf jeden Fall unter Anführungszeichen stehen (ansonsten wäre ja eine Unterscheidung, noch dazu bei der gewünschten variablen Feldanzahl, gar nicht möglich). Zerlegt wird in dieser Demo die (mit ein paar "Spezialfällen" angereicherte) Zeile
";Feld1";Feld2;"Feld3;";"";"Feld 5";"Feld 6 mit ; im Wert";;;Feld 9
Grüße
bastla
Member: filippg
filippg Sep 05, 2010 at 01:35:21 (UTC)
Goto Top
Hallo Bastla,

entschuldige, das waren beides nur als Beispiele gedacht. Das "Delimiter im Text"-Problem ergibt sich bei Powershell (die ich wohl nehmen werde) nicht, da die ein entsprechend sorgfältig implementiert ist. Und zur schon existierenden Ausgabedatei: klar, das lässt sich leicht abfangen. Aber das ist es eben: es sind zig Kleinigkeiten, an die man denken muss, wenn es wirklich gut sein soll. Und Kleinvieh macht auch Mist... Deswegen habe ich auch für ein solches eigentlich triviales Problem nach einer guten Fertiglösung gesucht...

Gruß

Filipp
Member: bastla
bastla Sep 05, 2010 at 09:18:21 (UTC)
Goto Top
Hallo Filipp!
Deswegen habe ich auch für ein solches eigentlich triviales Problem nach einer guten Fertiglösung gesucht...
Dagegen habe ich ja auch gar nix einzuwenden - mir macht einfach nur das Scripten Spaß ... face-wink

Grüße
bastla
Member: filippg
filippg Oct 26, 2010 at 17:41:43 (UTC)
Goto Top
Hallo,

ich hatte ganz vergessen hier zu posten, was ich jetzt verwende. Voilá:
param(
[string]$Template,
[string]$csv
)
$Description = "  
Erwartet als ersten Parameter eine Template-Datei (beliebiges 
Dateiformat), als zweiten eine .csv. Für jeden Datensatz in der
.csv wird das Template einmal ausgefüllt. Platzhalter, die in
## eingeschlossen sind werden durch den jeweiligen Spaltenwert
ersetzt.
Liegt im aktuellen Verzeichnis nur eine .csv-Datei, so muss
diese nicht mit angegeben werden.
Gibt es eine Spalte 'OutFile', so wird ihr Wert dem Ausgabe-  
Dateiname angehängt.
(C) Filipp Geyer, 2010
"  

if($Template -eq "") {  
        Write-Host $Description
        break
}
if($csv -eq ""){  
        $csvsInDir = Get-ChildItem "." -Filter "*.csv"  
        if(($csvsInDir | Measure-Object).Count -eq 1)
                { $csv = $csvsInDir.FullName }
        else 
                { Write-Host -ForegroundColor Red Es wurde keine Eingabedatei angegegeben, und es wurde keine oder mehr als eine .csv-Datei im Arbeitsverzeichnis gefunden; break}
}

if(-not(Test-Path -PathType Leaf $template))
        {Write-Host -ForegroundColor Red Templatedatei $template konnte nicht gefunden werden; break }
$templateObj = Get-Item $Template
$templateBaseName = $templateObj.Name.Remove($templateObj.Name.Length - $templateObj.Extension.Length)

if(-not(Test-Path -PathType Leaf $csv))
        {Write-Host -ForegroundColor Red Eingabedatei $csv konnte nicht gefunden werden; break }

$inFileObj = Get-Item $csv
if($inFileObj.Extension -ne ".csv")  
        {Write-Host -ForegroundColor Red Eingabedatei $csv hat nicht die Endung .csv; break }

function getNextFileName([string]$path, [string]$fileName, [string]$fileExt){
        if(-not (Test-Path -PathType Container $path )){
                Write-Host -ForegroundColor Red Ausgepfad $path existiert nicht!
                break
                }
        $currFileName = $path + "\" + $fileName  + $fileExt  
        
        for($i = 2; $i -lt 999; $i++){
                if(-not (Test-Path -PathType Leaf $currFileName)){
                        return $currFileName}
                $currFileName = $path + "\" + $fileName + "_" + $i + $fileExt     
        }
        Write-Host -ForegroundColor Red Konnte keinen Dateinamen fuer die Ausgabe finden.
        break;
}

$dataset = Import-Csv $csv
$cols = $dataset | Get-Member -MemberType NoteProperty
$contTemplate = Get-Content $template
foreach($row in $dataset){
        $currCont = $contTemplate
        
        $outFile = getNextFileName $inFileObj.Directory.FullName ($templateBaseName + $row.OutFile) $templateObj.Extension
        foreach($col in $cols){ 
                $currCont = $currCont -replace ("##"+$col.Name+"##"), ($row.($col.Name))  
        }
        
        $currCont > $outFile    
}

Grüße

Filipp