type
TKeyValueOption = (
kvoDequoteValue, //remove surrouding quote characters from Value
kvoAllowSpaceAroundSep, //allow for spaces before and/or after the KeyValueSeparator like: Key ="Value", Key= "Value" or Key = "Value"
kvoAllowUltraCompact //allow for no space after Value like: Ke1="Value1"Key2="Value2"
);
TKeyValueOptions = set of TKeyValueOption;
const
DefaultKeyValueOptions = [kvoDequoteValue, kvoAllowSpaceAroundSep];
StrictKeyValueOptions = [kvoDequoteValue];
RelaxedKeyValueOptions = [kvoDequoteValue, kvoAllowSpaceAroundSep, kvoAllowUltraCompact];
function TryParseKeyValuePairs_Alt(Line: String; List: TStrings; out ErrorPos: Integer; QuoteStart, QuoteEnd, KeyValueSeparator: Char; Options: TKeyValueOptions = DefaultKeyValueOptions): Boolean;
var
Index, Len, ValueStart: Integer;
NormalChars: TSysCharSet;
Key, Value: String;
QuotedValues: Boolean;
const
WhiteSpace = [#32,#9];
LineBreaks = [#10,#13];
procedure AddKeyValuePair;
begin
if QuotedValues then
begin
if (kvoDequoteValue in Options) then
Value := Copy(Line, ValueStart, Index-ValueStart-1)
else
Value := Copy(Line, ValueStart-1, Index-ValueStart+1);
end
else
Value := Copy(Line, ValueStart, Index-ValueStart);
//writeln('Value=[',Value,']');
List.AddPair(Key, Value);
Key := '';
Value := '';
end;
//all these "automatons" will set ErrorPos upon failure
//upon success Index will be set to 1 after the last character that was parsed
function SkipWhiteSpace(Mandatory: Boolean=False): Boolean;
begin
Result := False;
if Mandatory and not (Line[Index] in WhiteSpace) then
begin
ErrorPos := Index;
Exit;
end;
while (Index <= Len) and (Line[Index] in WhiteSpace) do Inc(Index);
Result := True;
end;
function ParseKey: Boolean;
var
Start: Integer;
begin
Start := Index;
while (Index <= Len) and (Line[Index] in NormalChars) do Inc(Index);
if (Index > Len) then
begin
ErrorPos := -1;
Exit(False);
end;
Key := Copy(Line, Start, Index-Start);
Result := True;
end;
function ParseSeparator: Boolean;
begin
if (Index > Len) then
ErrorPos := -1
else if (Line[Index] <> KeyValueSeparator) then
ErrorPos := Index;
if (ErrorPos = 0) then
Inc(Index);
Result := (ErrorPos = 0);
//if ErrorPos<>0 then writeln('ParseSeparator: ErrorPos=',ErrorPos);
end;
function ParseQuoteChar(QC: Char): Boolean;
begin
if not QuotedValues then
Exit(True);
if (Index > Len) then
ErrorPos := -1
else if (Line[Index] <> QC) then
ErrorPos := Index;
if (ErrorPos = 0) then
Inc(Index);
Result := (ErrorPos = 0);
//if ErrorPos<>0 then writeln('ParseQuoteChar: ErrorPos=',ErrorPos);
end;
function ParseValue: Boolean;
begin
if not ParseQuoteChar(QuoteStart) then
Exit(False);
if (Index > Len) then
begin
ErrorPos := -1;
Exit(False);
end;
ValueStart := Index;
while (Index <= Len) and
((Line[Index] in NormalChars) or (QuotedValues and (Line[Index] in (WhiteSpace + [KeyValueSeparator])))) do Inc(Index);
if QuotedValues and (Index > Len) then
ErrorPos := -1;
Result := (ErrorPos = 0) and ParseQuoteChar(QuoteEnd);
//if ErrorPos<>0 then writeln('ParseValue: ErrorPos=',ErrorPos);
end;
begin
Result := False;
List.Clear;
ErrorPos := 0;
Index := 1;
Len := Length(Line);
if (Len = 0) then
Exit;
NormalChars := [#1..#255] - WhiteSpace - LineBreaks - [QuoteStart, QuoteEnd, KeyValueSeparator];
QuotedValues := (QuoteStart <> #0);
SkipWhiteSpace;
//writeln('First non-whitespace char @Index: ',Index,'=',Line[Index]);
while (Index <= Len) and (ErrorPos = 0) do
begin
if ParseKey then
begin
if (kvoAllowSpaceAroundSep in Options) then
SkipWhiteSpace;
if ParseSeparator then
begin
if (kvoAllowSpaceAroundSep in Options) then
SkipWhiteSpace;
if ParseValue then
begin
AddKeyValuePair;
if (Index < Len) then
SkipWhiteSpace(not (kvoAllowUltraCompact in Options)); // no need to break on False, since we jump to begin of while loop and check ErrorPos
end; //ParseValue
end; //ParseSeparator
end; //ParseKey
end; //while
Result := (ErrorPos = 0);
end;