Files
lazarus-ccr/components/tvplanit/source/vpbasedatafiles.pas
2022-08-18 17:44:47 +00:00

352 lines
8.2 KiB
ObjectPascal

{ Visual PlanIt basic data files for import.
Is used for import of vCard and iCal files }
unit VpBaseDataFiles;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Contnrs,
VpBase;
type
TVpFileItem = class
protected
FRaw: String;
FKey: String;
FAttributes: TStrings;
FValue: String;
procedure GetParts(AText: String; out AKey: String; out Attr: TStringArray;
out AValue: String);
function UnEscape(AValueText: String): String;
function UnquotePrintable(AValueText: String): String;
public
constructor Create(AText: String);
destructor Destroy; override;
procedure Analyze;
property Key: String read FKey;
property Attributes: TStrings read FAttributes;
property Value: String read FValue;
end;
TVpFileItemClass = class of TVpFileItem;
TVpFileBlock = class
private
FItemClass: TVpFileItemClass;
function GetValue(const AKey, Attributes: String): String;
protected
FItems: TFPObjectList;
public
constructor Create(AClass: TVpFileItemClass);
destructor Destroy; override;
procedure Add(const AText: String);
procedure Analyze; virtual;
function FindItem(AKey, Attributes: String): TVpFileItem;
property Value[AKey: String; const Attributes: String]: String read GetValue;
end;
const
VALUE_DELIMITER = ';'; // semicolon
KEY_DELIMITER = ';';
KEY_VALUE_DELIMITER = ':'; // colon
TYPE_DELIMITER = ',';
implementation
uses
VpMisc;
{==============================================================================}
{ TVpFileItem }
{==============================================================================}
constructor TVpFileItem.Create(AText: String);
begin
inherited Create;
FRaw := AText;
end;
destructor TVpFileItem.Destroy;
begin
FAttributes.Free;
inherited;
end;
procedure TVpFileItem.Analyze;
var
attrArray: TStringArray;
i: Integer;
begin
GetParts(FRaw, FKey, attrArray, FValue);
FAttributes := TStringList.Create;
for i:=Low(attrArray) to High(attrArray) do
FAttributes.Add(attrArray[i]);
end;
// Example
// ADR;TYPE=WORK,POSTAL,PARCEL:;;One Microsoft Way;Redmond;WA;98052-6399;USA
procedure TVpFileItem.GetParts(AText: String; out AKey: String;
out Attr: TStringArray; out AValue: String);
var
p: Integer;
keypart, valuepart: String;
i, j, n: Integer;
QuotedPrintable: Boolean = false;
sa: TStringArray = nil;
begin
// Split at ':' into key and value parts
p := pos(KEY_VALUE_DELIMITER, AText);
if p = 0 then
raise EVpException.CreateFmt('Illegal file structure in line "%s"', [AText]);
keypart := Uppercase(copy(AText, 1, p-1));
valuepart := copy(AText, p+1, MaxInt);
// Process key part
Attr := nil;
(*
sa := keypart.Split(KEY_DELIMITER);
AKey := sa[0];
for i := 1 to High(sa) do
// We only consider the "type" attributes...
if pos('TYPE=', Uppercase(sa[i])) = 1 then
begin
keypart := copy(keypart, Length('TYPE='), MaxInt);
Attr := keypart.Split(TYPE_DELIMITER); // Split at ','
end else
Attr := keypart.Split(KEY_DELIMITER); // Split at ';'
for i := Low(Attr) to High(Attr) do
if Attr[i] = 'QUOTED-PRINTABLE' then
begin
QuotedPrintable := true;
break;
end;
*)
p := pos(KEY_DELIMITER, keypart);
if p = 0 then begin
AKey := keypart;
SetLength(Attr, 0);
end else begin
AKey := Copy(keypart, 1, p-1);
keypart := Copy(keypart, p+1, MaxInt);
if pos('TYPE=', keypart) = 1 then begin
keypart := copy(keypart, Length('TYPE=')+1, MaxInt);
sa := Split(keypart, TYPE_DELIMITER); // Split at ','
n := Length(Attr);
SetLength(Attr, n + Length(sa));
for j := 0 to High(sa) do
Attr[n+j] := sa[j];
end else
begin
sa := Split(keypart, KEY_DELIMITER); // Split at ';'
n := Length(Attr);
SetLength(Attr, n + Length(sa));
for j := 0 to High(sa) do
Attr[n+j] := sa[j];
end;
for i:=Low(Attr) to High(Attr) do
if Attr[i] = 'QUOTED-PRINTABLE' then begin
QuotedPrintable := true;
break;
end;
end;
// Process value part
if quotedPrintable then
AValue := UnquotePrintable(valuepart)
else
AValue := UnEscape(valuepart);
end;
function TVpFileItem.UnEscape(AValueText: String): String;
const
BUFSIZE = 100;
var
p, q: PChar;
idx: Integer;
procedure AddChar(ch: Char);
begin
Result[idx] := ch;
inc(idx);
if idx > Length(Result) then SetLength(Result, Length(Result) + BUFSIZE);
end;
begin
if AValueText = '' then begin
Result := '';
exit;
end;
SetLength(Result, Length(AValueText));
idx := 1;
p := @AValueText[1];
while p^ <> #0 do begin
if p^ = '\' then begin
inc(p);
if p^ = 'n' then begin
q := PChar(LineEnding);
AddChar(Char(q^));
if Length(LineEnding) > 1 then begin
inc(q);
AddChar(char(q^));
end;
end else
AddChar(char(p^));
end else
AddChar(char(p^));
inc(p);
end;
SetLength(Result, idx-1);
end;
function TVpFileItem.UnQuotePrintable(AValueText: String): String;
const
BUFSIZE = 100;
var
p: PChar;
idx: Integer;
code: String[2];
inUTF8: Boolean;
ch: Char;
procedure AddChar(ch: Char);
begin
Result[idx] := ch;
inc(idx);
if idx > Length(Result) then SetLength(Result, Length(Result) + BUFSIZE);
end;
begin
if AValueText = '' then begin
Result := '';
exit;
end;
SetLength(Result, Length(AValueText));
idx := 1;
inUTF8 := false;
p := @AValueText[1];
while p^ <> #0 do begin
if p^ = '=' then begin
code := '';
inUTF8 := true;
end else
if inUTF8 then begin
if code = '' then
code := p^
else begin
code := code + p^;
ch := char(StrToInt('$'+code));
inUTF8 := false;
AddChar(ch);
end;
end else
AddChar(char(p^));
inc(p);
end;
SetLength(Result, idx-1);
end;
{==============================================================================}
{ TVpFileBlock }
{==============================================================================}
constructor TVpFileBlock.Create(AClass: TVpFileItemClass);
begin
inherited Create;
FItems := TFPObjectList.Create;
FItemClass := AClass;
end;
destructor TVpFileBlock.Destroy;
begin
FItems.Free;
inherited;
end;
procedure TVpFileBlock.Add(const AText: String);
begin
FItems.Add(FItemClass.Create(AText));
end;
procedure TVpFileBlock.Analyze;
var
i: Integer;
item: TVpFileItem;
begin
for i := 0 to FItems.Count-1 do begin
item := TVpFileItem(FItems[i]);
item.Analyze;
end;
end;
{ Finds the item with the specified key and attributes.
Several attributes can be combined by a semicolon.
If an attribute name begins with a '-' then it must NOT be present.
The conditions are and-ed, i.e. all conditions must be met for the item to
be accepted. }
function TVpFileBlock.FindItem(AKey, Attributes: String): TVpFileItem;
var
i: Integer;
item: TVpFileItem;
attrArray: TStringArray;
attr, notAttr: String;
ok: Boolean;
begin
attrArray := Split(Attributes, ';');
for i:=0 to FItems.Count-1 do begin
item := TVpFileItem(FItems[i]);
if (AKey = item.Key) then
begin
ok := true; // No attr specified --> use first item found
if Length(attrArray) > 0 then begin
for attr in attrArray do begin
if attr[1] = '-' then
notAttr := Copy(attr, 2, MaxInt)
else
notAttr := '';
if item.Attributes.IndexOf(attr) = -1 then begin
// required attribute not found --> reject
ok := false;
break;
end;
if (notAttr <> '') and (item.Attributes.IndexOf(notAttr) <> -1) then begin
// forbidden attribute found --> reject
ok := false;
break;
end;
end;
end;
if ok then begin
Result := item;
exit;
end;
end;
end;
Result := nil;
end;
function TVpFileBlock.GetValue(const AKey, Attributes: String): String;
var
item: TVpFileItem;
begin
item := FindItem(AKey, Attributes);
if item <> nil then
Result := item.Value
else
Result := '';
end;
end.