AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Konverter

Ein Thema von Cosamia · begonnen am 27. Feb 2007 · letzter Beitrag vom 28. Feb 2007
Antwort Antwort
Benutzerbild von Cosamia
Cosamia

Registriert seit: 27. Feb 2007
Ort: Emmendingen
221 Beiträge
 
Delphi 2007 Professional
 
#1

Konverter

  Alt 27. Feb 2007, 15:15
Hallo zusammen,

ich bin neu hier und habe gleich eine wichtige Frage an euch.

Zielsetzung ist ein Textfile (.dat) auszulesen, den Inhalt einer bestimmen Zeile abzuändern, und dann das File in einem andern Verzeichnis abzuspeichern..
Das Problem ist, dass das File als "Platzhalter" immer ein ? nutzt.
Dieses Fragezeichen scheint aber den Item.Index der anzeigenden Listbox zu verwirren.
Ich nutze bisher die Listbox zur Visualisierung und als Zwischenschritt vor der Modifikation der Daten.

Oder wie könnte man die Sache anders angehen?

Danke schonmal im voraus.

cosamia
  Mit Zitat antworten Zitat
Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#2

Re: Konverter

  Alt 27. Feb 2007, 15:25
Hi und erstmal Herzlich Willkommen in der DP

Zitat von Cosamia:
Zielsetzung ist ein Textfile (.dat) auszulesen, den Inhalt einer bestimmen Zeile abzuändern, und dann das File in einem andern Verzeichnis abzuspeichern.
Sicher kein Problem!


Zitat von Cosamia:
Das Problem ist, dass das File als "Platzhalter" immer ein ? nutzt. Dieses Fragezeichen scheint aber den Item.Index der anzeigenden Listbox zu verwirren.
Was genau meinst Du denn damit? Der Item.Index ist mir so unbekannt, es gibt nur die Eigenschaft Items (der Inhalt der ListBox) und den itemIndex (ohne Punkt). Letzterer sollte Dir nur sagen, welche Zeile ausgewählt ist bzw. -1 (oder so) sein, wenn kein Eintrag ausgewählt wurde.
Möchtest Du die Position von einem ? in einer bestimmten Zeile ermitteln, so kannst Du dies mit der Funktion Pos tun.

Delphi-Quellcode:
var buffer: String;
begin
  // speichern der entsprechenden Zeile in der Variable buffer
  buffer := ListBox.Items[ZeilenNr];
  
  // ermitteln der Position des ? im String
  result := Pos('?', buffer);
end;
So könntest Du z.B. jetzt den Index zurück geben lassen. Natürlich brauchst Du den Puffer nicht, dient hier vorallem der Übersichtlichkeit! Pos ist < 1, wenn der SubString (das ?) nicht im String (buffer) vorhanden ist.

Hoffe das hilft weiter, sonst einfach fragen!

Gruß Der Unwissende
  Mit Zitat antworten Zitat
Benutzerbild von Cosamia
Cosamia

Registriert seit: 27. Feb 2007
Ort: Emmendingen
221 Beiträge
 
Delphi 2007 Professional
 
#3

Re: Konverter

  Alt 28. Feb 2007, 13:55
Hallo Unwissender,

Danke erstmal für die schnelle Hilfe. Ich will ein Dat File einlesen welches ähnlich u.g. aussieht:

Delphi-Quellcode:
197
Dominges Angela
?
?
?
?
?
?
?
06.04.06
?
?
?
?
?
?
3
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
Trivial
Basiscamp
0.9
?
?
?
?
13
19
?
?
?
?
?
?
?
?
?
?
?
4.43
3.97
?
?
?
?
?
?
?
?
?
4.00
?
?
?
?
?
?
?
239.6
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
0.4
?
1
121.25
02.30
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
39.93
Jetzt soll z.B. der Wert, der an der neunten Stelle steht aggeändert werden.
Im Bedarfsfall soll erkannt werden, ob es sich um eine Dezimalzahl handelt oder nicht.

Gruss
  Mit Zitat antworten Zitat
Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#4

Re: Konverter

  Alt 28. Feb 2007, 14:47
Zitat von Cosamia:
Jetzt soll z.B. der Wert, der an der neunten Stelle steht aggeändert werden.
Im Bedarfsfall soll erkannt werden, ob es sich um eine Dezimalzahl handelt oder nicht.
Was ist denn hier die neunte Stelle? Da steht doch im Moment ein ?, oder sehe ich das falsch?
An sich hast Du zwei Möglichkeiten:
1) Die Datei (wie hier) als Textfile zu betrachten. Dabei dient dann ein CR (Carriage-Return) und ggf. auch ein LF (Line-Feed) als Zeilenumbruch. Zudem wird alles was sich in der Datei befindet immer als Character interpretiert. Die ? sind dann mit relativ hoher Wahrscheinlichkeit keine echten ? sondern einfach Zeichen, deren Ordinaler Wert keine echte Entsprechung für ein sichtbares Zeichen hat (z.B. Steuerzeichen wie EOT, ACK, NAK, ...).
In diesem Fall solltest Du dann sagen, welche Zeile Du auf welche Art und Weise ändern möchtest. Für die Arbeit mit Textdateien (die nicht all zu groß sind) eignet sich die TStringList ganz gut. Nicht all zu groß heißt dann, dass die nur ein paar hunder KByte hat. Ich glaube die wird sonst recht langsam, aber schau einfach mal.
Mit der Funktion LoadFromFile kannst Du eine Datei laden, die dann in der TStringList landet. Dabei kannst Du über ihre Standard-Array-Eigenschaft gezielt auf Zeilen zugreifen:

Delphi-Quellcode:
var list: TStringList;
    line: String;
begin
  list := TStringList.Create;
  list.LoadFromFile(...);
   
  // speichert Zeile 11 in line
  // Achtung, normalerweise sollte man natürlich prüfen,
  // dass es überhaupt so viele Zeilen gibt!
  // list.Count gibt Dir die Gesamtzahl zurück!
  line := list[10];
Hier kannst Du mittels list[i] einfach auf die i-te Zeile zugreifen (lesend und schreibend). Änderungen landen aber erst in der Datei, wenn Du diese wieder abspeicherst (SaveToFile).

2) Alternativ zu Textdateien kannst Du eine Datei auch immer "Roh" betrachten. Das ist die Form, die sie tatsächlich auf der Festplatte hat. Eine Datei besteht eigentlich nur aus einer bestimmten Anzahl von Bytes, die Du beliebig interpretieren kannst. Ein Texteditor sieht eine Datei, die Du öffnest genau so. Er wandelt dann einfach alle Bytes in Characters um, wobei Character 10 und 13 dann eben gerade CR und LF entsprechen und als Zeilenumbruch interpretiert werden. Character 9 wäre z.B. der Tabulator, hier kann dann der Texteditor entscheiden was geschieht, wenn dieses Zeichen gefunden wird.
Eine andere Möglichkeit ist, die Bytes Sedezimal anzeigen zu lassen, bekannter ist das unter dem Namen Hex-Editor (Se ist imho das korrekte Präfix, nicht hex). Jedenfalls wird hier einfach die Datei Byteweise angezeigt, so wie sie eben auch auf der Platte liegt. Um die Lesbarkeit für einen Menschen zu erhöhen, werden aber die Bytes noch schön formatiert (in einer Art Tabelle angeordnet und ihre Positionen dazu geschrieben).
Möchtest Du eine Datei in dieser Form lesen/schreiben, so kannst Du das mit Hilfe von Streams. Streams betrachten einfach Datenströme. Sie haben einen Zeiger, der auf die aktuelle Position im Stream zeigt und können Lesen oder Schreiben. Eine Datei stellt eine mögliche Quelle für einen solchen Strom da. Eine Datei ist nur ein einfacher Strom von Bytes.
In anderen Programmiersprachen ist es durchaus üblich, dass man einen Zeilenumbruch durch das einfügen von \n erreicht. Die Zeilen:
Code:
Ein String mit\n\fZeilenumbruch
und
Code:
Ein String mit
Zeilenumbruch
sind also eigentlich das selbe. Die obere Form entspricht aber dem, wie Du es in einer Datei (oder einem Stream) vorfinden würdest. Statt \n würde man in Delphi eher #10#13 schreiben, aber das Prinzip ist das Gleiche.
Bei der Arbeit mit einem Stream greifst Du also direkt auf ein Zeichen an einer bestimmten Stelle zu, Zeilen gibt es nicht (die sind reine Interpreationssache). Das erste Zeichen ist E, das 15te ist \n, usw.
Einen solchen Stream werden auch immer Daten als Bytes (uninterpretiert) hinzugefügt oder entnommen, es ist Sache des Benutzers diese geeignet zu interpretieren.

Delphi-Quellcode:
var s: TFileStream;
    buffer: String;
begin
  // Stream erzeugen, der auf die Datei FileName zugreift und diese
  // zum lesen öffnet und vor Veränderungen schützt (andere dürfen
  // natürlich gleichzeitig lesen!)
  s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);

  // immer wichtig bei der Arbeit mit Dateien oder anderen Ressourcen
  // ist ein Ressourcen-Schutzblock. Dieser sollte sicherstellen, dass
  // eine Ressource auch im Fehlerfall nicht blockiert wird
  try
    // die Größe des Puffers setzen
    setLength(buffer, 10);
    // den Positionszeiger setzen
    s.Position := 10;
    // 10 Zeichen von der aktuellen Position aus der Datei in den Puffer lesen
    s.read(buffer[1], 10);
  finally
    // hier die Freigabe des Streams auch im Fehlerfall
    // und damit die garantierte Aufhebung der Schreibsperre
    s.Free;
  end;
end;
Wichtig sind hier ein paar Punkte, als erstes der Ressourcen-Schutzblock. Dieser ist nötig, da hier ein fmShareDenyWrite (hoffe es heißt so, einfach in der OH nachschlagen) als Schreibsperre gesetzt wird. Diese Sperre macht Sinn, da keiner Schreiben sollte während Du liest (sonst wäre das was Du liest eventuell total undefiniert). Ressourcen sind immer begrenzt, also sollte man sie auch immer frei geben, egal was passiert.
Der nächste schritt ist die Puffergröße. Ein String ist eigentlich nur ein statisches Array. Schreibst Du s := 'XYZ', dann wird für den String s automatisch Speicher von 3 Byte alloziert und mit den Werten X, Y, Z belegt. Würdest Du jetzt s := s + 'A' schreiben, dann würde an einer anderen Stelle im Speicher neuer Platz von 4 Byte alloziert, die alte Belegung von s dort hinkopiert und an die vierte Stelle ein A geschrieben werden. Hast Du einem String keinen Wert zugewiesen, so belegt dieser auch noch keinen Platz im Speicher. Möchtest Du also wie im Beispiel einen String der Länge 10 lesen, musst Du erst dafür sorgen, dass der String auch 10 Zeichen aufnehmen kann. Das tust Du mit setLength(string, größe).
Als nächstes folgt das Setzen des Positionszeigers. Dies ist immer die Stelle, von der an gelesen oder geschrieben wird. Natürlich solltest Du immer Prüfen, dass die nächste Position > -1 und < s.Size (s der Stream) ist. Alles andere würde die Schranken des Streams verletzen und zu einem Fehler führen. Hier wird der Zeiger (ungeprüft) auf das 10te Zeichen gesetzt. Nun werden von dieser Position (wo auch immer der Zeiger gerade steht) 10 Zeichen in den Puffer gelesen. Der Puffer ist immer die Adresse der ersten Zelle, in die geschrieben wird. Du hättest auch ein Array (beliebigen Typs) oder ein Integer übergeben können. Die Daten werden wirklich roh an diese Stelle im Speicher geschrieben. Ob es sich bei den gelesenen Daten überhaupt um den entsprechenden Typen handelt kann der Stream nicht feststellen, er schiebt einfach die Bytes dort in den Speicher. Auch auf die Grenzen des übergebenen Datums wird der Stream nicht achten. Liest Du 8 Byte in ein Word ein, so wirst Du natürlich die Grenzen des Word (2 Byte) verletzen, für den Stream gibt es aber nur eine Adresse, an die alle 8 Byte geschrieben werden sollen.
Ein Stream erfordert also etwas mehr Kontrolle auf Deiner Seite, ist dafür aber auch sau-schnell. Das liegt daran, dass ein Stream wirklich einfache Speicheroperationen ohne jegliche Prüfung ausführt und nicht mehr (aber auch nicht weniger).
Insbesondere solltest Du immer beachten:
  • Den Positionszeiger setzen. Hast Du den auf Position 10 gesetzt und liest 20 Zeichen, steht dieser danach auf 20
  • Bei Zuweisung der Position diese prüfen (> -1 und < Stream.Size).
  • Vor dem Lesen prüfen ob so viele Bytes im Stream stehen (Stream.Position + BytesToRead < Stream.Size)
  • Schauen wieviele Bytes wirklich gelesen/geschrieben wurden (Rückgabewert der Funktion read bzw. write)
  • Ressourcenschutzblock verwenden

Sind weniger Regeln als Du jetzt vielleicht denkst und man merkt sie sich schnell! Die sind wirklich einfach und die Arbeit mit Streams sehr angenehm (gerade bei großen Dateien).

Ja, ich hoffe ich konnte Dir grob weiterhelfen. Genaueres kann ich leider nicht sagen, da mir nicht klar ist, welche Stelle Du gerade ändern/betrachten möchtest. Sowohl Zeichen 9 als auch Zeile 9 sind hier keine Werte, die wie eine Zahl wirken. Zudem wäre es wichtig, ob Du hier den String ändern oder ein bestimmtes Byte setzen möchtest. Du kannst immerhin die Zahl 1 als String 1 (ordinaler Wert = 49) oder als Integer (0x00 0x00 0x00 0x01) abspeichern (0xAB = Hex-Darstellung des Bytes, Byte FF = 255, Byte 00 = 0).

Gruß Der Unwissende
  Mit Zitat antworten Zitat
Benutzerbild von Cosamia
Cosamia

Registriert seit: 27. Feb 2007
Ort: Emmendingen
221 Beiträge
 
Delphi 2007 Professional
 
#5

Re: Konverter

  Alt 28. Feb 2007, 15:23
Gehen wir davon aus, dass ich den Wert "06.04.06" in dem Dat File ändern will.
Ich muss überprüfen ob es ein "Datum" ist, oder nicht. Falls es keines ist muss ich das aktulle Datum einsetzen.

Zum einlesen der Datein nutze ich bisher folgenden Code:


Delphi-Quellcode:
procedure getFiles(dir: string);

begin
   if FindFirst(dir+'*',faAnyFile,search)=0 then
      begin
         repeat
            if (((search.Attr and faDirectory)>0) and (not(leftStr(search.Name,1)='.'))) then getFiles(dir+search.Name+'\')
            else
               begin
                  if ExtractFileExt(search.Name)='.'+Convert.Ext.Text then
                     begin
                        Convert.Content.Items.LoadFromFile(dir+search.name); //Listbox Content
                        Convert.Results.AddItem(search.Name,Application); //Schleife
                        Convert.Caption:='Anzahl ['+intToStr(convert.Results.Count)+']';
                        Convert.Results.Items.SaveToFile('.\listbox.dat'); //Inhalt von Listbox Results wird abgespeichert
                        Convert.Content.Items.Delete(2); //Nur als kleine Übung
                        Convert.Content.Items.SaveToFile(Convert.Ziel.Text+search.name); //Inhalt von Content wird in neue Dateien gespeichert
                     end;
               end;
         until not(FindNext(search)=0);
      end;
   FindClose(search);
end;

procedure TConvert.BtnGoClick(Sender: TObject);
begin
   convert.Caption:='Anzahl [0]';
   Results.Clear;
   if not(rightStr(Path.Text,1)='\') then Path.Text:=Path.Text+'\';
   if DirectoryExists(Path.Text) then getFiles(Path.Text);
end;
In obengenannter Schleife sollte dann anstatt der Eliminierung der dritten Zeile die Modifikation stattfinden.

Mir fiel bisher nichts besseres ein!
  Mit Zitat antworten Zitat
Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#6

Re: Konverter

  Alt 28. Feb 2007, 16:05
Zitat von Cosamia:
Delphi-Quellcode:
begin
   if FindFirst(dir+'*',faAnyFile,search)=0 then
      begin
An dieser Stelle würde ich an deiner Stelle schon mit einem Schutzblock anfangen. Dieser stellt dann sicher, dass FindClose auf jeden Fall aufgerufen wird.

Zitat von Cosamia:
Delphi-Quellcode:
            if (((search.Attr and faDirectory)>0) and (not(leftStr(search.Name,1)='.'))) then getFiles(dir+search.Name+'\')
            else
Sorry, aber ganz schlecht! Einerseits würde ich hier völlig überlesen, was dort im Falle des if's passiert. Viel Besser ist es, wenn Du für jede Struktur immer ein begin-end-Block verwendest. Der ist natürlich nicht nötig für die korrekte Semantik, erleichtert aber die Lesbarkeit immens und senkt eventuelle Folgefehler beim einfügen weiterer Anweisungen.
Besser wäre hier also
Delphi-Quellcode:
if (((search.Attr and faDirectory)>0) and (not(leftStr(search.Name,1)='.'))) then
begin
  getFiles(dir+search.Name+'\')
end // if (((search.Attr and faDirectory)>0) and (not(leftStr(search.Name,1)='.')))
         
else
So ist viel deutlicher, dass es sich hier um zwei verschiedene Fälle handelt und was wann getan wird. Das andere, was mich hier stören würde, ist die Rekursion. Natürlich ist es nicht falsch oder so, aber bei einer hohen Verschachtelungstiefe hast Du ein Problem, die Ressourcen. Jedes FindFirst belegt ein Handle, dass an anderer Stelle fehlt. Du gehst hier erst in die Tiefe eines Ordners, bevor Du das Handle des aktuellen Ordners wieder frei gibst. Wie gesagt, tief genug und es gibt Probleme (vorallem wird das System langsam!). Besser wäre es, wenn Du Dir hier nur die Ordner merkst, die Du als nächstes besuchen möchtest. Hier würde ich Dir (wieder) zu einer TStringList raten. Leg einfach eine an, füge mit add den Pfad der neuen Ordner hinzu und mach erstmal mit dem Rest weiter. Natürlich kannst Du so auch die Dateien in eine eigene Liste schreiben. Damit hast Du dann erstmal die Suche für einen Ordner abgeschlossen und die zu untersuchenden Dateien und Ordner in je einem Puffer stehen. Nun kannst Du schon FindClose aufrufen (und damit erstmal nicht benötigte Ressourcen frei geben).
Dann kannst Du selbst entscheiden, wie Du weitermachst (erst Ordner oder Dateien).

Statt dem dir+search.Name+'\' solltest Du lieber die Funktion IncludeTrailingPathDelimiter verwenden. Diese stellt sicher, dass das korrekte Pfad Trennzeichen angefügt wird, wenn es noch nicht vorhanden ist.

Ja, hast Du nun die zwei Listen, nenen wir sie Dateien und Ordner, dann würde ich Dir empfehlen diese nun in je einer eigenen Routine weiter zu verarbeiten. Auch hier geht es nur um die bessere Lesbarkeit. Je mehr kleine Routinen Du verwendest, desto leichter kannst Du überblicken, was eine solche Routine macht. Das senkt die Anzahl der Fehler und erhöht die Lesbarkeit (und ggf. natürlich auch die Wiederverwendbarkeit).

Bei den Ordnern ist die Vorgehensweise klar, Du kannst einfach die Methode getFiles für jeden gespeicherten Pfad aufrufen. Bleiben also noch die Dateien. Hier kannst Du wie bereits gesagt jede Datei einfach mit einer TStringList laden (LoadFromFile).
Wenn Du die Zeile kennst, auf die Du zugreifen möchtest, dann kannst Du dem schon geposteten Beispiel entnehmen, wie Du diese Zeile extrahierst.
Hier ist es nur noch wichtig, welches Datum gespeichert wurde und ob dieses gültig ist. Dazu steht Dir die Funktion TryStrToDate zur Verfügung. Diese probiert die Umwandlung eines Strings in ein Datum aus und gibt zurück, ob dies möglich war oder nicht.
Die Umwandlung hängt allerdings immer vom verwendeten Datumsformat ab. Dies kann mit jeder Windowsversion und natürlich eingestellter Lokalisierung variieren (z.B. je nach Land). Da Du ein bestimmtes Format erwartest, solltest Du dies auch zurück geben. Dazu kannst Du einfach mit der Funktion getLocaleFormatSettings die lokalen Einstellungen als TFormatSettings-Record zurückgeben lassen.
In diesen FormatSettings änderst Du dann ShortDateFormat auf 'dd.mm.YYYY' und übergibst diese veränderten LocalSettings dann mit an die TryStrToDate-Funktion. Bekommst Du dann ein False zurück, kannst Du das aktuelle Datum mit FormatDate als String erzeugen und für diesen String in die TStringList einfügen. Dann einfach wieder speichern und fertig.
  Mit Zitat antworten Zitat
Benutzerbild von Cosamia
Cosamia

Registriert seit: 27. Feb 2007
Ort: Emmendingen
221 Beiträge
 
Delphi 2007 Professional
 
#7

Re: Konverter

  Alt 28. Feb 2007, 16:45
Danke für die Tips.
Ich werde das morgen mal umsetzen. Besser gesagt, ich werds versuchen.

Ich werde das Ergebnis dann präsentieren.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:34 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz