Bazy danych oparte na własnych formatach

Wolverine

Wstęp

Wielu z nas zapewne zastanawiało się nad stworzeniem prostej bazy danych, czy to opartej na plikach tekstowych czy typowanych. Ostatecznie rzucaliśmy projekt i sięgaliśmy pomocy ze strony popularnych systemów bazodanowych (np. MySQL), lecz to rozwiązanie ma jedną podstawową wadę - wymagania ze strony komercyjnego Delphi.

W tym artykule pragne przybliżyć tworzenie baz danych opartych na dwóch wyżej wymienionych sposobach: pliki tekstowe i pliki typowane. Obie metody mają swoje zalety i wady.

Proste pliki tekstowe

Główna zaletą plików tekstowych jest ich prosta konstrukcja, najprostrza zawartość pliku takiej bazy może wyglądać tak:

Jan|Kowalski|Kasztanowa|8|1|Warszawa

Jedynym zadaniem programu odczytującego taki plik bedzie podzielenie linijki względem znaku |.

procedure Explode(s: String; Dot: Char; var Buffer: TStringList);
begin
  Buffer.Clear;
  while Pos(Dot, s) <> 0 do begin
    Buffer.Add(Copy(s, 1, Pos(Dot, s) - 1));
    s := Copy(s, Pos(Dot, s) + 1, Length(s) - Pos(Dot, s));
  end;
  if Length(s) > 0 then begin
    Buffer.Add(s);
  end;
end;

Procedura dzieli linie, podawaną w parametrze s (String) na liste łańcuchów względem znaku Dot (Char) i zapisuję ją w parametrze Buffer (TStringList). Teraz mamy wszystko, aby stworzyć pierwszy, prosty program wykorzystujący tekstowe bazy danych.

Przykładowy odczyt może wyglądać tak:

var
  Rec, Baza: TStringList;
  i: Integer;
begin
  Baza := TStringList.Create;
  Rec := TStringList.Create;

  Baza.Lines.LoadFromFile('plik z baza');

  for i := 0 to Baza.Lines.Count -1 do begin
    Explode(Baza.Lines[i], '|', Rec);
  
    Memo1.Lines.Add('Imie: ' + Rec[0]);
    Memo1.Lines.Add('Nazwisko: ' + Rec[1]);
  end;
end;

Hyper Objects Language

Drugim formatem bazy w plikach tekstowych, ktory zamierzam opisać w tym artykule jest plik HOL (Hyper Objects Language). Format pliku jest zbliżony do formatu pliku ini, lecz opiera się na pseudo-obiektach. Przykładowy plik HOL wygląda tak:
hol;version=100;Author=Wolverine 

user;imie=Jan;nazwisko=Kowalski;ulica=Kasztanowa;numer domu=8;numer mieszkania=1;miasto=Warszawa

Druga linijka informuje program przetwarzający o wersji skryptu, w tym przypadku 1.0.0 (100). Mimo pozorów odczytanie informacji z takiego pliku będzie łatwiejsze i wygodniejsze niż w poprzednim przypadku. Jedną z zalet skryptów HOL jest ich odporność na położenie danych, np:

user;imie=Jan;nazwisko=Kowalski

da ten sam efekt co:

user;nazwisko=Kowalski;imie=Jan

Do odczytania pliku posłuży nam kasa THolFile (załączona do artykułu). Po rozpakowaniu archiwum do naszego programu możemy dodać odpowiedni modów do naszego programu

uses HolFile;

i zadeklarować zmienna parsera

var
  HolFile: THolFile;

Jak każdą klase THolFile trzeba przed użyciem stworzyć

HolFile := THolFile.Create;

Następnie można załadować plik z bazą danych

HolFile.Parse('Nazwa pliku');

Od tego momentu mamy do dyspozycji zawartość naszej bazy, jest ona przechowywana w tablicy HolObjects (array of THolObject). Pojedyńczy obiekt ma właściwość name (nazwa) i kolejną tablice - params (array of THolParam). Gdy znamy już podstawowe elementy budowy klasy THolFile, możemy użyć jej do wczytania pliku z danymi

var
  HolFile: THolFile;
  i: Integer;
begin
  HolFile := THolFile.Create;
  HolFile.Parse('nazwa pliku');
  for i := 0 to HolFile.ObjectCount -1 do begin
    Memo1.Lines.Add('Imie: ' + GetParamValue(HolFile.HolObjects[i], 'imie');
    Memo1.Lines.Add('Nazwisko: ' + GetParamValue(HolFile.HolObjects[i], 'nazwisko');
  end;
  HolFile.Free;
end;

Kod może wydawać się trudniejszy, ale jest bardziej elastyczny i za pomocą specjalnych metod klasy THolFile można np zdefiniować ze zmiennej, jakie dane mają zostać wczytane, podczas gdy w poprzednim rozwiązaniu mieliśmy dostępne dane w postaci rygorystycznie ustawionej listy.

Pliki typowane

Jest to najtrudniejsze i najmniej elastyczne rozwiązanie z trzech przedstawionych, ale równie często używane przez programistów. Plików typowanych nie da się edytować w edytorze tekstowym, co sprawia, że ten rodzaj bazy jest najbezpieczniejszy. Ich zawartość tworzą rekordy definiowane na stałe w programie. Przykładowy rekord może wyglądać tak:
TUser = record
  Imie: String[20];
  Nazwisko: String[30];
  Ulica: String[20];
end;

Jak można zauważyć w rekordzie możemy zdefiniować zmienne, lecz z pewną zasadą - każdy rekord musi mieć jednakową wielkość. A więc plik typowany jest zbiorem rekordów, ktore są zbiorami ze ściśle określonymi zmiennymi, ale jak ich używać?

Nie ma uniwersalnej klasy do obsługi każdego pliku typowanego, jest za to obiekt file (plik), który przypomina po części tablice. Definiuje się go tak samo

Baza: file of TUser;

Po zdefiniowaniu typu należy przypisać mu fizyczny plik

AssignFile(Baza, 'nazwa pliku');

Aby zacząć korzystanie plik należy zainicjować

Reset(Baza);

Gdy chcemy edytować plik

ReWrite(Baza);

Gdy chcemy go stworzyć od początku

Do odczytu rekordu z pliku służy procedura Read, która za parametry obiera odczytywany plik i zmienną do zapisu

type
  TUser = record
    Imie: String[20];
    Nazwisko: String[30];
    Ulica: String[20];
  end;
var
  User = TUser;
  Baza: file of TUser;
begin
  AssignFile(Baza, 'nazwa pliku');
  ReWrite(Baza);
  while not Eof(Baza) do begin
    Read(Baza, User);
    Memo1.Lines.Add('Imie: ' + User.Imie);
    Memo1.Lines.Add('Nazwisko: ' + User.Nazwisko);
  end;
  Close(Baza);
end;

Zapis do pliku wygląda analogicznie, używając procedury Write, przybierającej jako parametry plik i rekord do zapisu. Wadą (?) plików typowanych jest programowanie niskopoziomowe, które dla niedoświadczonych programistów może okazać się niewygodne.

Na zakończenie

Wszystkie trzy metody nadają się wspaniale do obsługi małych aplikacji bazodanowych, lecz należy zapamiętać, że każdy z nich ma pewne wady: mała elastyczność przy prostych plikach tekstowych, brak wbudowanych metod szyfrujących w klasie THolFile i konieczność programowania niskopoziomowego przy plikach typowanych. Mam nadzieję, że każdy znajdzie w tym artykule coś dla siebie.

Pliki

Pliki do artkułu: [HOL_Parser_v100.zip](//4programmers.net/Download/372/937)

Dodatkowe informacje:
4p - Pliki typowane
Delphi.About.com - My own database

14 komentarzy

Jest jeszcze jedna bardzo duża niedogodność. Szukanie odpowiedniego rekordu ;>
Na szczęście istnieje coś takiego jak SQLite, dzięki czemu już się nikt nie musi bawić we własne formaty baz danych i może używać SQLa :)

B.d artykuł :)

a jak załadować to do DBGrida?

Ja podchodzę do zagadnienia trochę inaczej. Na początku tworzę odpowiednie rekordy, np:

const
 ItemsCount = 50;

type
 TRec = record
  Imie     :string[10];
  Nazwisko :string[20];
  Wiek     :Byte;
 end; // TRec

 PBaza = ^TBaza;
 TBaza = array [1..ItemsCount] of TRec;

var
 Baza :PBaza;
 F    :file;

procedure LoadFromFile(FileName :string);
var i :Byte;
begin
 AssignFile(F, FileName);
 try
  Reset(F, 1);
  BlockRead(F, Baza^, SizeOf(TBaza));
  for i := 1 to ItemCount do ComboBox.Items.Add(Baza^[i].Nazwisko);
  CloseFile(F);
 except
  ShowMessage('Błąd otwarcia');
 end; // try..except
end; // LoadFromFile

Odczyt/zapis poprzez BlockRead/BlockWrite. Tworzenie i niszczenie przez New i Dispose.

Skad pobrac ten HOL Parser, bo ten link nie dziala?

*Juz mam http:*4programmers.net/file.php?id=1506

Rookie One ~ przez wieki uzytkowania nie zdarzylo mi sie cos takiego ...

Ta procedurka Explode jest delikatnie mowiac zryta (wywoluje naruszenie dostepu). Dajcie sobie z nia spokoj i uzyjcie tej.

terz ostatnio się w tym babram i co do plików typowanych to niemasz całkiem racji prawdą jest, że otworzenie takiego pliku w notatniku ukaże nam ciąg niezrozumiałuch znaczków jednak po dłuższym siedzeniu w tym można cokolwiek rozgryźć ale zapis dokonanych zmian to jednak nadal ryzykowna decyzja

ja zrobilem baze danych oparta o pliki ini, prosty program, robiony przeze mnie i oczywiscie nie skonczony

? a co wlasciwie zmieniles? bo ja roznicy nie widze :/

Mam Delphi6 i umnie to nie chodzi, trzeba odrobinę kod zmodyfikować:
procedure pobierzdane(s: String);
var
Rec, Baza: TStringList;
i: Integer;
begin
Baza := TStringList.Create;
Rec := TStringList.Create;

Baza.LoadFromFile(s);

for i := 0 to Baza.Count -1 do begin
Explode(Baza.Strings[i], '|', Rec);

Form1.Memo1.Lines.Add('Imie: ' + Rec[0]);
Form1.Memo1.Lines.Add('Nazwisko: ' + Rec[1]);

end;
end;
Teraz jest ok!

racja, poprawione.

var
 record, Baza: TStringList;
 i: Integer;
begin
 Baza := TStringList.Create;
 record := TStringList.Create;

Czemu używasz słowa kluczowego do nazwy zmiennej.. to nie ma prawa działać.