"List index out of bounds" przy parsowaniu pliku tekstowego

0

witam
przykładowo odczytuję dane z pliku zapisane w postaci

La;Da;ul. Kamienna 76/13;59-3xx;L;00320902587;4;UML;1;201701;27/2016
Ja;Jk;ul. Grabowa 49/1;59-3xx;L;;4;UML;1;201701;218/2016
Ea;Kk;ul. Leszczynowa 18/7;59-3xx;L;00291000561;4;UML;1;201701;37/2016

poniżej przedstawiam fragment kodu

    tekst.Clear;
    Readln(txt, arg);
    ExtractStrings([';'], [], PChar(arg), tekst);
    pimie := trim(tekst.Strings[0]);
    pnazwisko := trim(tekst.Strings[1]);
    pulica := trim(tekst.Strings[2]);
    pkodpocztowy := trim(tekst.Strings[3]);
    pmiasto := trim(tekst.Strings[4]);
    ppesel := trim(tekst.Strings[5]) + '.';
    ptyp := trim(tekst.strings[8]);
    pwaznosc := trim(tekst.Strings[9]);
    pdokument := trim(tekst.Strings[10]);

wszystko działałoby bez zarzutu ale jak w pliku który odczytuję zabraknie (w lini nr 2 przykładowo ;;) brak informacji w środku to program wywala mi błąd 'list index out of bounds(10)' i niestety nie mogę sobie poradzic by nie omijał tej częci ;;, moze znajdziecie rozwiązanie by program wstawił tam na przykład 0 i odcvzytywał dalej. Z góry dziekuję za wszelkie sugestie i pomoc.

1

Co tam w dokumentacji słychać:

System.Classes.ExtractStrings

Note: ExtractStrings does not add empty strings to the list.

Spróbuj w ten sposób:

var
  LChunks: TStringList;
begin
  LChunks.Delimiter := ';';
  LChunks.StrictDelimiter := True;
  LChunks.DelimitedText := { linia z pliku } ;

Jeśli i to nie pomoże to trzeba będzie sobie napisać funkcję dzielącą ciąg znaków i dzielić go po swojemu;

Poza tym, funkcja ExtractStrings zwraca liczbę kawałków wydobytych z łańcucha, więc na podstawie tej liczby powinieneś zabezpieczyć się przed wyjątkiem wykroczenia poza zakres indeksów;

A jeszcze poza tym, funkcja ta pobiera zbiór białych znaków do pomijania, więc jak już jej używasz to podaj te znaki (lub jeden znak), zamiast później wszystko **Trim**ować; Przy czym zawartość Twojego pliku nie potrzebuje żadnego przycinania, dlatego że żadna składowa linii nie jest objęta białymi znakami do pominięcia;


Z ciekawości sprawdziłem jak wygląda sprawa w Lazarusie;

Tu twórcy wycwanili się i dodali jeszcze jeden parametr - AddEmptyStrings typu Boolean - z domyślną wartością False; Gdybyś pisał w tym środowisku to wystarczyło by podać True w tym parametrze:

ExtractStrings([';'], [' '], PChar({ linia z pliku }), LChunks, True);

Ale nie piszesz w Lazarusie, więc to tylko ciekawostka.

0

Może to głupie ale chyba zadziała skoro problem jest z ;; to chyba nie szkodzi wcześniej StringReplace zmienić ;; na ; ; później i tak masz Trim więc ostatecznie dostaniesz pusty ciąg. Czyli po prostu przed ExtractStrings:

  if Copy(arg, 1, 1) = ';' then
    Insert(';', arg, 1);
  if Copy(arg, Length(arg), 1) = ';' then
    Insert(';', arg, Length(arg));
  while Pos(';;', arg) > 0 do
    arg:= StringReplace(arg, ';;', '; ;', [rfReplaceAll]);

EDIT: sprawdziłem teoretycznie działa ale musi być pętla bo jeżeli się okaże że jest ;;; (lub więcej) to by była kicha.... ech i jeszcze zabezpieczenie gdy brak pierwszych i ostatnich danych .

0

Po pierwsze stosuj TStringList. Ta klasa wczytuje plik od razu, czyli unikasz readLn, a każda linia pliku staje się linią w TStringList. Jeśli nie wystarczy to, co napisał @furious programming, to wpisz w google: Delphi + split string. I znajdziesz gotowe rozwiązania na dobry split.

1

Z tego z w sieci można wyczytać, ExtractStrings zawsze pomija puste ciągi i nie dodaje ich do listy wynikowej, natomiast właściwość DelimitedText (w połączeniu z Delimiter i StrictDelimiter) nie odrzuca pustych łańcuchów, więc takowe powinny znaleźć się w docelowej liście; Tak więc to co podałem powinno rozwiązać problem;


Na pewno jednym najprostszych w implementacji (szybkich i mało pamięciożernych) mechanizmów jest czytanie zawartości pliku linia po linii oraz podciąg po podciągu, za pomocą procedur Read i Readln; To w jaki sposób odczytywać podciągi zawarte w jednej linii pliku pokazałem w innym wątku - Odczyt integerów z pliku tekstowego - w nim dodatkowo było wczytywanie liczb całkowitych i zmiennoprzecinkowych, jednak chodzi o sam sposób;

Przykład takiej funkcji poniżej (dialekt FPC):

function ReadValueFromFile(var AFile: TextFile; const ADelimiter: Char): String;
var
  LChar: Char;
begin
  Result := '';

  if not EoLn(AFile) and not EoF(AFile) then
  begin
    repeat
      Read(AFile, LChar);

      if LChar <> ADelimiter then
        Result += LChar;
    until EoLn(AFile) or EoF(AFile) or (LChar = ADelimiter);

    Result := Trim(Result);
  end;
end;

Przykładowa linia w pliku:

Ja;Jk;ul. Grabowa 49/1;59-3xx;L;;4;UML;1;201701;218/2016

I przykładowe wywołanie:

type
  TLineValues = array [0 .. 10] of String;
var
  LFile: TextFile;
  LValues: TLineValues;
  LValueIdx: Integer;
begin
  AssignFile(LFile, 'C:\Input.txt');
  Reset(LFile);
  try
    for LValueIdx := Low(TLineValues) to High(TLineValues) do
      LValues[LValueIdx] := ReadValueFromFile(LFile, ';');
  finally
    CloseFile(LFile);
  end;

  for LValueIdx := Low(TLineValues) to High(TLineValues) do
    WriteLn('"', LValues[LValueIdx], '"');

  ReadLn();
end.

Na ekranie ujrzymy taki wynik:

"Ja"
"Jk"
"ul. Grabowa 49/1"
"59-3xx"
"L"
""
"4"
"UML"
"1"
"201701"
"218/2016"

Czyli funkcja działa jak najbardziej prawidłowo; Napisana jest w taki sposób, aby nie pomijała pustych podciągów, aby usuwała białe znaki z początków i końców podciągów, a także aby w razie zbyt małej liczby podciągów zwracała po prostu puste łańcuchy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1