{ Parsing attributes line in the form: atrib1=value1 atrib2="value2word1 value2word2" ..
 gets next couple and removes it from source line. }
unit Attrib;
{$I define.pas}
interface
uses
  SysUtils, {v0.67}Classes, {/v0.67}AttrType, Msgu;

function GetAttributesLine(var SourceLine: string; StartTag: string;
  StopTag: string; var IsInAttr: boolean; var AttributesLine: string):boolean;
  { Extracts from SourceLine="neco atd. " + StartTag + "x=y z=a ..." + StopTag
    part between XXXXtags (i.e. "x=y z=a ...") and assigns it to Attributes
    variable;
    using the IsInAttr flag can be the method used for attributes spreaded on
    more lines (i.e. StartTag and StopTag are not on the same line).
    Returns false if no Attributes found, SourceLine unchanged. }

function GetNextAttrib(var AttributesLine: string;
  var AttrName: string; var AttrValue: string): boolean;
{
 - reads from Attributes line in form:
     attrname1=attrvalue attrname2=attrvalue ...
   (i.e. separator is space, use attrvalue in quotes if contains spaces;
    attrname can not contain space)
   first attribute, assigns it to AttrName and AttrValue
 - removes the attr from the Attributes,
 - returns false if Atrributes is empty
 - returns AttrValue='' if "=" not found
}

function AddAttribute(var AttributesLine: string; const AttrName: string;
  AttrValue: string): boolean;
{ Adds to attributes line string "attrname=attrvalue "; returns false, if
  could not add - too long line would result }

function AddAttributesLine(var SrcLine: string;
  const AttributesLine: string):boolean;

function FindAttrib(var AttributesLine: string; const AttrName: string;
  var AttrValue: string; Extract: boolean):boolean;
{ tries to find attribute of given name AttrName in AttributesLine
  (looks for "AttrName=" string); returns true if found and sets AttrValue
  to the value of the attribute; if Extract is true, then also removes the
  attribute from the AttributesLine }

{v0.67}
{ Converts Attributes line (e.g. 'name1="val1" name2="val2" ...') to
  string list (each name/val pair on one line). Adds lines to AList, i.e.
  does not clear it. Returns false if Attributes line has wrong format. }
function AttrLineToStrings(const AttributesLine: string; AList: TStrings): boolean;
{/v0.67}
implementation

function GetAttributesLine(var SourceLine:string; StartTag:string;
  StopTag:string; var IsInAttr:boolean; var AttributesLine:string):boolean;
var
  p1, p2: integer;
  { index of first and last char of AttributsLine in the SourceLine }
begin
  GetAttributesLine := false;
  AttributesLine := '';
  if not IsInAttr then begin
    p1 := pos(StartTag, SourceLine);
    if (p1 = 0) then
      exit;
    p1 := p1 + length(StartTag);
    IsInAttr := true;
    p2 := pos(StopTag, SourceLine);
    if (p2 <> 0) then begin
      p2 := p2 - 1;
      IsInAttr := false;
    end else begin
      p2 := length(SourceLine);
    end;
  end else begin
    p1 := 1;
    p2 := pos(StopTag, SourceLine);
    if p2 = 0 then begin
      p2 := length(SourceLine);
    end else begin
      p2 := p2 - 1;
      IsInAttr := false;
    end;
  end;
  if (p2 < p1) then begin
    SysError('Attrib.GetAttributes: misplaced ' + StartTag + ' ' + StopTag);
    exit;
  end;
  AttributesLine := copy(SourceLine, p1, p2 - p1 + 1);
  SourceLine := copy(SourceLine, 1, p1 - length(StartTag) - 1) +
    copy(SourceLine, p2 + length(StopTag) + 1, length(SourceLine){255});
  GetAttributesLine := true;
end;

procedure StripQuotes(var AttrValue: string);
begin
  if (AttrValue <> '') and (AttrValue[length(AttrValue)] = '"') then
    SetLength(AttrValue, length(AttrValue) - 1);
  if (AttrValue <> '') and (AttrValue[1] = '"') then begin
    AttrValue := copy(AttrValue, 2, length(AttrValue));
  end;
end;

function GetNextAttrib(var AttributesLine:string; var AttrName:string; var AttrValue:string):boolean;
var
  p:{v0.29}integer{/v0.29};
  t: string;
  isInQuotes:boolean;
begin
  GetNextAttrib := false;
  AttrName := '';
  AttrValue := '';
  AttributesLine := trim(AttributesLine);
  if AttributesLine = '' then
    exit;
  isInQuotes := false;
  p := 1;
  while (p <= length(AttributesLine)) do begin
    if (AttributesLine[p] = ' ') and (not isInQuotes) then
      break;
    if AttributesLine[p] = '"' then
      isInQuotes := not isInQuotes;
    inc(p);
  end;
  t := copy(AttributesLine, 1, p - 1);
  AttributesLine := copy(AttributesLine, p + 1, {v0.29}length(AttributesLine){/v0.29 255});
  p := pos('=', t);
  if p = 0 then begin
    AttrValue := '';
    AttrName := t;
  end else begin
    AttrName := copy(t, 1, p - 1);
    AttrValue := copy(t, p + 1, {v0.29}length(t){/v0.29 255});
    StripQuotes(AttrValue);
  end;
  GetNextAttrib := (AttrName <> '') {and (AttrValue <> '')};
end;

function FindAttrib(var AttributesLine: string; const AttrName: string;
  var AttrValue: string; Extract: boolean):boolean;
{ tries to find attribute of given name AttrName in AttributesLine;
  returns true if found and sets AttrValue to the value of the attribute;
  if Extract is true, then also removes the attribute from the AttributesLine }
var
  p1, p2: integer;
  isInQuotes: boolean;
begin
  FindAttrib := false;
  AttrValue := '';
  p1 := pos(AttrName + '=', AttributesLine);
  isInQuotes := false;
  if p1 <> 0 then begin
    FindAttrib := true;
    p2 := p1 + length(AttrName) + 1;
    while (p2 <= length(AttributesLine)) do begin
      if (AttributesLine[p2] = ' ') and (not isInQuotes) then
        break;
      if AttributesLine[p2] = '"' then
        isInQuotes := not isInQuotes;
      inc(p2);
    end;
    if p2 <= length(AttributesLine) then begin
      if AttributesLine[p2] = ' ' then begin
        dec(p2)
      end;
    end else begin
      p2 := length(AttributesLine);
    end;
    AttrValue := copy(AttributesLine, p1 + length(AttrName) + 1,
      p2 - p1 - length(AttrName));
    StripQuotes(AttrValue);
    if Extract then begin
      AttributesLine := copy(AttributesLine, 1, p1 - 1) +
        copy(AttributesLine, p2 + 1, length(AttributesLine){255});
    end;
  end;
end;

function AddAttribute(var AttributesLine: string; const AttrName: string;
  AttrValue: string): boolean;
{adds to attributes line string "attrname=attrvalue "}
{var i:integer;}
begin
  AddAttribute := true;
  if pos(' ', AttrValue) > 0 then begin
    AttrValue := '"' + AttrValue + '"';
  end;
{  for i := 1 to length(AttrValue) do begin
    if AttrValue[i] = ' ' then
      AttrValue[i] := '_';
  end;
}
  {$IFOPT H-}
  if length(AttributesLine) + length(AttrName) + length(AttrValue) + 2 > 255 then
  begin
    SysError('attrib.AddAttribute: attrline too long ' + AttributesLine);
    AddAttribute := false;
    exit;
  end;
  {$ENDIF}
  AttributesLine := Trim(AttributesLine) + ' ' + AttrName + '=' + AttrValue ;
end;

function AddAttributesLine(var SrcLine: string;
  const AttributesLine: string):boolean;
begin
  AddAttributesLine := true;
  if AttributesLine <> '' then begin
    {$IFOPT H-}
    if length(SrcLine) + length(AttributesLine) > 255 then begin
     {$IFDEF DPMI}
     SysError('attrib.AddAttributesLine: sourcline too long ' + SrcLine);
     {$ENDIF}
      AddAttributesLine := false;
      exit;
    end;
    {$ENDIF}
    SrcLine := SrcLine +  '{' + AttributesLine + '}';
  end;
end;

{v0.67}
{ Converts Attributes line (e.g. 'name1="val1" name2="val2" ...') to
  string list (each name/val pair on one line). Adds lines to AList, i.e.
  does not clear it. Returns false if Attributes line has wrong format. }
function AttrLineToStrings(const AttributesLine: string; AList: TStrings): boolean;
var al, an, av: string;
begin
  Result := false;
  al := AttributesLine;
  while GetNextAttrib(al, an, av) do begin
    Result := true;
    AList.Add(an + '=' + av); //names    classes
  end;
end;
{/v0.67}

end.
