|
Antwort |
Registriert seit: 27. Feb 2007 Ort: Emmendingen 221 Beiträge Delphi 2007 Professional |
#1
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 |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#2
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.
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.
Möchtest Du die Position von einem ? in einer bestimmten Zeile ermitteln, so kannst Du dies mit der Funktion Pos tun.
Delphi-Quellcode:
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.
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; Hoffe das hilft weiter, sonst einfach fragen! Gruß Der Unwissende |
Zitat |
Registriert seit: 27. Feb 2007 Ort: Emmendingen 221 Beiträge Delphi 2007 Professional |
#3
Hallo Unwissender,
Danke erstmal für die schnelle Hilfe. Ich will ein Dat File einlesen welches ähnlich u.g. aussieht:
Delphi-Quellcode:
Jetzt soll z.B. der Wert, der an der neunten Stelle steht aggeändert werden.
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 Im Bedarfsfall soll erkannt werden, ob es sich um eine Dezimalzahl handelt oder nicht. Gruss |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#4
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. 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:
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).
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]; 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:
und
Ein String mit\n\fZeilenumbruch
Code:
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.
Ein String mit
Zeilenumbruch 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:
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.
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; 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:
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 |
Zitat |
Registriert seit: 27. Feb 2007 Ort: Emmendingen 221 Beiträge Delphi 2007 Professional |
#5
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:
In obengenannter Schleife sollte dann anstatt der Eliminierung der dritten Zeile die Modifikation stattfinden.
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; Mir fiel bisher nichts besseres ein! |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#6
Zitat von Cosamia:
Delphi-Quellcode:
begin
if FindFirst(dir+'*',faAnyFile,search)=0 then begin
Zitat von Cosamia:
Delphi-Quellcode:
if (((search.Attr and faDirectory)>0) and (not(leftStr(search.Name,1)='.'))) then getFiles(dir+search.Name+'\')
else Besser wäre hier also
Delphi-Quellcode:
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).
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 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. |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |