Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Software-Projekte der Mitglieder (https://www.delphipraxis.net/26-software-projekte-der-mitglieder/)
-   -   UniversalDataUnit (https://www.delphipraxis.net/184253-universaldataunit.html)

milos 12. Mär 2015 12:41


UniversalDataUnit
 
Hallo DP'ler,

ich habe immer wieder mit den verschiedensten Konfigurationsdaten zu tun und habe mich entschieden eine Universelle Unit dafür zu schreiben und sie zu erweitern wann immer es nötig ist.

Da ich bei meinem neusten Projekt mit INI-Dateien zu tun habe, habe ich nun angefangen die Unit zu programmieren.
Der Code darf für alles verwendet werden, da er eigentlich keinen grossen Wert hat - könnte höchstens ne halbe bis ganze Stunde Programmierarbeit ersparen :)

Delphi-Quellcode:
// --------------------------------------------------------------------------
// UniversalConfigUnit
// Version 0.1DPP (DelphiPraxis Preview)
// --------------------------------------------------------------------------
// There is no limitation with this code.
// If you want to use, change and/or upload the code, just do it =)
// --------------------------------------------------------------------------
// Features:
// - INI Files
//  - Create INI Object
//  - Load File to Object
//  - Parse String/TStringList to Object
//  - Save Object to File
// --------------------------------------------------------------------------
// Authors:
// Stanojevic Milos
// Email: contact@svr2k.de
//
// (add yourself here)
// --------------------------------------------------------------------------

unit UniversalConfigUnit;

interface

uses
  Generics.Collections,
  Classes,
  SysUtils,
  System.RegularExpressions;

type
  { INI CLASSES START }
  TIniSection = class
  private
    FValues : TDictionary<string, string>;
    function FGetValue(AKey: string) : string;
    procedure FSetValue(AKey : string; AProperty : string);
  public
    Title : string;
    constructor Create(ATitle : string);
    function ContainsKey(AKey : string): Boolean;
    property Value[AKey : string] : string read FGetValue write FSetValue; default;
  end;

  TIniObject = class
  private
    FSections : TList<TIniSection>;
    function FGetSection(AIndex : string) : TIniSection;
    function FGetValue(ASection, AKey : string) : string;
    function FGetTitle(AIndex : integer) : string;
  public
    constructor Create();
    procedure Parse(AStringList : TStringList);
    procedure LoadFromFile(AFileName : string);
    procedure SaveToFile(AFileName : string);
    function ContainsSection(ASection : string) : Boolean;
    procedure AddSection(AIniSection : TIniSection);
    procedure AddValue(ASection, AKey, AProperty : string); overload;
    procedure AddValue(ASection, AKey, AProperty : string; AAutoCreate : boolean); overload;
    property Section[Index : string] : TIniSection read FGetSection; default;
    property Value[index, Index2: string] : string read FGetValue;
    property Title[index : integer] : string read FGetTitle;
  end;
  { INI CLASSES END }

implementation

{ TIniConfig }

procedure TIniObject.AddSection(AIniSection: TIniSection);
var
  LIndex : integer;
begin
  LIndex := FSections.Add(AIniSection);
end;

procedure TIniObject.AddValue(ASection, AKey, AProperty: string);
begin
  Section[ASection].FValues.Add(AKey, AProperty);
end;

procedure TIniObject.AddValue(ASection, AKey, AProperty: string; AAutoCreate : boolean);
begin
  if AAutoCreate then
  begin
    if ContainsSection(ASection) then
      AddValue(ASection, AKey, AProperty);
  end
  else
  begin
    AddSection(TIniSection.Create(ASection));
    AddValue(ASection, AKey, AProperty);
  end;
end;

function TIniObject.ContainsSection(ASection: string): Boolean;
var
  c: Integer;
begin
  for c := 0 to FSections.Count-1 do
    if FSections[c].Title = ASection then
      Result := true;
end;

constructor TIniObject.Create;
begin
  FSections := TList<TIniSection>.Create();
end;

function TIniObject.FGetSection(AIndex : string): TIniSection;
var
  c: Integer;
begin
  for c := 0 to FSections.Count-1 do
    if FSections[c].Title = AIndex then
      Result := FSections[c];


  if Result = nil then
    raise Exception.Create(AIndex + ' konnte nicht gefunden werden');
end;

function TIniObject.FGetTitle(AIndex: integer): string;
begin
  Result := FSections[AIndex].Title;
end;

function TIniObject.FGetValue(ASection, AKey: string): string;
begin
  Result := Section[ASection][AKey];
end;

procedure TIniObject.LoadFromFile(AFileName: string);
var
  LStringList : TStringList;
begin
  if FileExists(AFileName) then
  begin
    LStringList := TStringList.Create;
    LStringList.LoadFromFile(AFileName);
    Parse(LStringList);
  end
  else
    raise Exception.Create(AFileName + ' wurde nicht gefunden');

  LStringList.Free;
end;

procedure TIniObject.Parse(AStringList: TStringList);
var
  LLine : string;
  LParts : TStringList;
  c: Integer;
begin
  LParts := TStringList.Create;
  for c := 0 to AStringList.Count - 1 do
  begin
    LLine := AStringList[c];

    if Length(LLine) < 1 then
    Continue;

    if LLine[1] = ';' then
      Continue
    else if (LLine[1] = '[') and (LLine[Length(LLine)] = ']') then
    begin
      AddSection(TIniSection.Create(Copy(LLine,2,Length(LLine)-2)));
      Continue;
    end
    else if (TRegEx.IsMatch(LLine, '^[a-zA-Z0-9]*=[a-zA-Z0-9(./)]*$')) then
    begin
      LParts.Clear;
      LParts.StrictDelimiter := true;
      LParts.Delimiter := '=';
      LParts.DelimitedText := LLine;

      FSections[FSections.Count-1].FValues.Add(LParts[0],LParts[1]);
    end
    else
      raise Exception.Create('Fehler beim laden der INI-Datei.' + #10 +
                             'Zeile: ' + IntToStr(c + 1) + #10 +
                             '"' + LLine + '" ist keine gültige INI Anweisung.');


  end;

  LParts.Free;
end;

procedure TIniObject.SaveToFile(AFileName: string);
var
  LStringList : TStringList;
  LPair : TPair<string, string>;
  c: Integer;
begin
  LStringList := TStringList.Create;

  for c := 0 to FSections.Count-1 do
  begin
    LStringList.Add('[' + FSections[c].Title + ']');
    for LPair in FSections[c].FValues do
      LStringList.Add(LPair.Key + '=' + LPair.Value);


    LStringList.Add('');
  end;

  LStringList.SaveToFile(AFileName);
  LStringList.Free;
end;

{ TIniSection }

function TIniSection.ContainsKey(AKey : string): Boolean;
var
  c: Integer;
begin
  Result := FValues.ContainsKey(AKey);
end;

constructor TIniSection.Create(ATitle : string);
begin
  Title := ATitle;
  FValues := TDictionary<string, string>.Create;
end;


function TIniSection.FGetValue(AKey: string): string;
begin
  Result := FValues[AKey];
end;

procedure TIniSection.FSetValue(AKey, AProperty: string);
begin
  if ContainsKey(AKey) then
    FValues[AKey] := AProperty;
end;

end.
Ich habe versucht den Code so kurz wie möglich zu halten. Es gibt auch viel Optimierungspotenzial aber da ich nun Feierabend(mittag :D) habe, werde ich erst morgen weiter machen können.

Ich freue mich über jede Verbesserungsvorschläge oder sonstigen Anmerkungen.

Freundliche Grüsse

Sir Rufo 12. Mär 2015 12:49

AW: UniversalConfigUnit
 
Willst du die ganzen MemLeaks noch beseitigen? :stupid:

milos 12. Mär 2015 17:45

AW: UniversalConfigUnit
 
Hi :)

Ja die paar MemoryLeaks werden noch gefixt ;) Habe mir auch noch ein paa Sachen überlegt die man besser machen könnte. Hat sonst noch jemand Vorschläge oder Kritikpunkte? Bin für alles offen :thumb:

Der schöne Günther 12. Mär 2015 17:57

AW: UniversalConfigUnit
 
Ich habe die Implementation jetzt noch nicht gelesen, aber ich verstehe nicht, was denn nun eine
Delphi-Quellcode:
TIniSection
und was ein
Delphi-Quellcode:
TIniObject
ist. Eine Sektion scheint einen änderbaren Titel und String-String-Paare zu besitzen. Ein
Delphi-Quellcode:
TIniObject
scheint nun beliebig viele dieser Sektionen sowie beliebig viele Titel (?) zu beinhalten. Wenn ich es richtig verstehe wäre doch "TIniFile" ein passenderer Name, oder?

Vorausgesetzt ich liege nicht meilenweit daneben, verstehe ich eins noch nicht: Wo ist nun der Unterschied von TIniObject zum in Delphi bereits vorhandenen TIniFile? :|

Stevie 12. Mär 2015 17:58

AW: UniversalConfigUnit
 
Kannst du mal den Vorteil gegenüber einer TMemIniFile erörtern? Denn für mich sieht das nach einem flüchtigen Blick genau danach aus.

sx2008 15. Mär 2015 19:07

AW: UniversalConfigUnit
 
Füg' doch noch die Methode
Delphi-Quellcode:
LoadFromStream()
und
Delphi-Quellcode:
SaveToStream()
hinzu.
Denn ein File ist ja nur die Spezialisierung eines Streams; und wer weiß vielleicht willst du die Konfiguration ja mal von einem Webserver auslesen.
Natürlich rufen die Methoden LoadFromFile() und SaveToFile() ihre allgemeineren Streamvarianten auf und übergeben ein FileStream-Objekt.


Delphi-Quellcode:
procedure TIniObject.Parse(AStringList: TStringList); // falsch: TStringList ist zu spezifisch
procedure TIniObject.Parse(AStringList: TStrings);   // richtig: TStrings bietet dem Aufrufer alle Möglichkeiten

milos 18. Mär 2015 15:23

AW: UniversalConfigUnit
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1293275)
Ich habe die Implementation jetzt noch nicht gelesen, aber ich verstehe nicht, was denn nun eine
Delphi-Quellcode:
TIniSection
und was ein
Delphi-Quellcode:
TIniObject
ist. Eine Sektion scheint einen änderbaren Titel und String-String-Paare zu besitzen. Ein
Delphi-Quellcode:
TIniObject
scheint nun beliebig viele dieser Sektionen sowie beliebig viele Titel (?) zu beinhalten. Wenn ich es richtig verstehe wäre doch "TIniFile" ein passenderer Name, oder?

Vorausgesetzt ich liege nicht meilenweit daneben, verstehe ich eins noch nicht: Wo ist nun der Unterschied von TIniObject zum in Delphi bereits vorhandenen TIniFile? :|

Zitat:

Zitat von Stevie (Beitrag 1293276)
Kannst du mal den Vorteil gegenüber einer TMemIniFile erörtern? Denn für mich sieht das nach einem flüchtigen Blick genau danach aus.

Ihr habt natürlich recht, dass es schon dutzende Varianten gibt eine INI-Datei zu laden und zu speichern, jedoch ist mein Ziel eine schlanke Unit für verschiedene Konfigurationsdaten (XML, JSON usw [auch für Custom-Configs]) zu erstellen. Ich denke mal Vorteile wird meine Unit bisher noch keine haben (ausser dass sie schlank ist? :cyclops:) aber wie gesagt, vielleicht mags der eine oder andere brauchen und wenn nicht, dann halt nicht :) Ausserdem ist es doch meistens ziemlich entspannend nach mehreren Stunden krampfen in einem etwas grösserem Projekt eine kleine, simple aber funktionstüchtige Unit zu schreiben. ;)


Zitat:

Zitat von sx2008 (Beitrag 1293530)
Füg' doch noch die Methode
Delphi-Quellcode:
LoadFromStream()
und
Delphi-Quellcode:
SaveToStream()
hinzu.
Denn ein File ist ja nur die Spezialisierung eines Streams; und wer weiß vielleicht willst du die Konfiguration ja mal von einem Webserver auslesen.
Natürlich rufen die Methoden LoadFromFile() und SaveToFile() ihre allgemeineren Streamvarianten auf und übergeben ein FileStream-Objekt.


Delphi-Quellcode:
procedure TIniObject.Parse(AStringList: TStringList); // falsch: TStringList ist zu spezifisch
procedure TIniObject.Parse(AStringList: TStrings);   // richtig: TStrings bietet dem Aufrufer alle Möglichkeiten

Danke für die Tipps, werde ich beachten und einbauen wenn es die Zeit erlaubt :)
Zufälligerweise hatte ich schon ein Problem mit der StringList :oops:

Habe auch noch etwas kleines aber doofes bemerkt:
Erstmal ein schneller Crashkurs:
TIniObject beinhaltet die ganze IniDatei mit allen Sektionen.
TIniSection ist eine Sektion bei der, der Titel immer mit eckigen Klammern umgeben sind und dessen Eigenschaften unten aufgeführt sind.

Es gibt folgende Wege eine Eigenschaft auszulesen:
Delphi-Quellcode:
  IniObject[Sektion][Eigenschaftsvariable] : Eigenschaftswert als String

  IniObject.Value[Sektion, Eigenschaftsvariable] : Eigenschaftswert als String
Ich denke mal die Value variante wird raus genommen, da sie eigentlich unnötig ist. ^^

Freundliche Grüsse


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:06 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