// SPDX-License-Identifier: LGPL-3.0-linking-exception unit BGRALazResource; {$mode objfpc}{$H+} interface uses BGRAClasses, SysUtils, BGRAMultiFileType; type { TLazResourceEntry } TLazResourceEntry = class(TMultiFileEntry) private procedure Serialize(ADestination: TStream); protected FName: utf8string; FValueType: utf8string; FContent: TStream; function GetName: utf8string; override; procedure SetName(AValue: utf8string); override; function GetExtension: utf8string; override; function GetFileSize: int64; override; public constructor Create(AContainer: TMultiFileContainer; AName: utf8string; AValueType: utf8string; AContent: TStream); destructor Destroy; override; function CopyTo(ADestination: TStream): int64; override; function GetStream: TStream; override; end; { TFormDataEntry } TFormDataEntry = class(TLazResourceEntry) protected FTextContent: TStream; procedure RequireTextContent; function GetExtension: utf8string; override; function GetFileSize: int64; override; public constructor Create(AContainer: TMultiFileContainer; AName: utf8string; ABinaryContent: TStream); destructor Destroy; override; function CopyTo(ADestination: TStream): int64; override; end; { TLazResourceContainer } TLazResourceContainer = class(TMultiFileContainer) protected function CreateEntry(AName: utf8string; AExtension: utf8string; AContent: TStream): TMultiFileEntry; override; public procedure LoadFromStream(AStream: TStream); override; procedure SaveToStream(ADestination: TStream); override; end; implementation uses LResources, BGRAUTF8; { TFormDataEntry } procedure TFormDataEntry.RequireTextContent; begin if FTextContent = nil then begin FTextContent:= TMemoryStream.Create; FContent.Position:= 0; LRSObjectBinaryToText(FContent, FTextContent); end; end; function TFormDataEntry.GetExtension: utf8string; begin Result:= 'lfm'; end; function TFormDataEntry.GetFileSize: int64; begin RequireTextContent; Result:= FTextContent.Size; end; constructor TFormDataEntry.Create(AContainer: TMultiFileContainer; AName: utf8string; ABinaryContent: TStream); begin inherited Create(AContainer,AName,'FORMDATA',ABinaryContent); end; destructor TFormDataEntry.Destroy; begin FreeAndNil(FTextContent); inherited Destroy; end; function TFormDataEntry.CopyTo(ADestination: TStream): int64; begin RequireTextContent; if FTextContent.Size = 0 then result := 0 else begin FTextContent.Position:= 0; result := ADestination.CopyFrom(FTextContent,FTextContent.Size); end; end; { TLazResourceEntry } procedure TLazResourceEntry.Serialize(ADestination: TStream); begin FContent.Position := 0; BinaryToLazarusResourceCode(FContent, ADestination, Name, FValueType); end; function TLazResourceEntry.GetName: utf8string; begin Result:= FName; end; procedure TLazResourceEntry.SetName(AValue: utf8string); begin if AValue = FName then exit; if Container.IndexOf(AVAlue, Extension) <> -1 then raise Exception.Create('Name is already used for this extension'); FName := AValue; end; function TLazResourceEntry.GetExtension: utf8string; begin Result:= FValueType; end; function TLazResourceEntry.GetFileSize: int64; begin Result:= FContent.Size; end; destructor TLazResourceEntry.Destroy; begin FreeAndNil(FContent); inherited Destroy; end; constructor TLazResourceEntry.Create(AContainer: TMultiFileContainer; AName: utf8string; AValueType: utf8string; AContent: TStream); begin inherited Create(AContainer); FName := AName; FValueType := UTF8UpperCase(AValueType); FContent := AContent; end; function TLazResourceEntry.CopyTo(ADestination: TStream): int64; begin if FContent.Size = 0 then result := 0 else begin FContent.Position:= 0; result := ADestination.CopyFrom(FContent, FContent.Size); end; end; function TLazResourceEntry.GetStream: TStream; begin Result:= FContent; end; { TLazResourceContainer } procedure TLazResourceContainer.LoadFromStream(AStream: TStream); const entryStart = 'LazarusResources.Add('; entryEnd = ');'; whiteSpace = [' ',#9,#10,#13,#26]; var fileContent: String; filePos : integer; procedure SkipWhitespace; begin while (filePos <= length(fileContent)) and (fileContent[filePos] in whiteSpace) do inc(filePos); end; procedure SkipComma; begin SkipWhitespace; if (filePos <= length(fileContent)) and (fileContent[filePos] = ',') then inc(filePos) else raise Exception.Create('Comma expected'); end; function ParseString(ignoreCommas: boolean): TStream; var expectPlus: boolean; procedure AppendChar(c: char); begin result.WriteByte(ord(c)); end; function ParseNumber: integer; var numberStart, errPos: integer; s: String; begin numberStart:= filePos; if (filePos <= length(fileContent)) and (fileContent[filePos] = '$') then begin inc(filePos); while (filePos <= length(fileContent)) and (fileContent[filePos] in['0'..'9','a'..'f','A'..'F']) do inc(filePos); end else begin while (filePos <= length(fileContent)) and (fileContent[filePos] in['0'..'9']) do inc(filePos); end; s := copy(fileContent,numberStart,filePos-numberStart); val(s, result, errPos); if errPos <> 0 then raise exception.Create('Invalid number "' + s + '"'); end; function ParseStringPart: boolean; var charCode: integer; begin SkipWhitespace; if filePos <= length(fileContent) then begin if expectPlus then if fileContent[filePos] <> '+' then begin result := false; expectPlus := false; exit; end else inc(filePos); case fileContent[filePos] of '+': raise exception.Create('Unexpected "+"'); '''': begin inc(filePos); while (filePos <= length(fileContent)) do begin if fileContent[filePos] = '''' then begin inc(filePos); if (filePos <= length(fileContent)) and (fileContent[filePos] = '''') then begin AppendChar(''''); inc(filePos); end else break; end else if fileContent[filePos] in[#10,#13] then raise Exception.Create('Unexpected end of line') else begin AppendChar(fileContent[filePos]); inc(filePos); end; end; if (filePos <= length(fileContent)) and (fileContent[filePos] = '#') then expectPlus := false else expectPlus := true; result := true; end; '#': begin inc(filePos); charCode := ParseNumber; if (charCode < 0) or (charCode > 255) then raise exception.Create('Character code out of bounds'); AppendChar(chr(charCode)); if (filePos <= length(fileContent)) and (fileContent[filePos] in['#','''']) then expectPlus := false else expectPlus := true; result := true; end; else begin result := false; expectPlus := false; end; end; end else begin result := false; expectPlus := false; end; end; begin result := TMemoryStream.Create; expectPlus := false; if not ParseStringPart then raise exception.Create('Expecting string'); repeat if ignoreCommas then begin SkipWhitespace; if (filePos <= length(fileContent)) and (fileContent[filePos] = ',') then begin inc(filePos); expectPlus := false; end; end; until not ParseStringPart; end; procedure ReadContent; var bytesRead: integer; begin setlength(fileContent,AStream.Size-AStream.Position); bytesRead := AStream.Read(fileContent[1],length(fileContent)); setlength(fileContent, bytesRead); filePos := 1; end; function StreamToUTF8String(AStream: TStream): utf8String; begin setlength(result, AStream.Size); AStream.Position := 0; AStream.Read(result[1], length(result)); AStream.Free; end; var entryName: utf8string; entryType: utf8string; entryContent: TStream; inArray: boolean; begin Clear; ReadContent; while filePos <= length(fileContent) do begin if (upcase(fileContent[filePos]) = upcase(entryStart[1])) and (CompareText(copy(fileContent,filePos,length(entryStart)),entryStart)=0) then begin inc(filePos, length(entryStart)); entryName := StreamToUTF8String(ParseString(false)); SkipComma; entryType := StreamToUTF8String(ParseString(false)); SkipComma; SkipWhitespace; if (filePos <= length(fileContent)) and (fileContent[filePos] = '[') then begin inArray := true; inc(filePos); end else inArray := false; entryContent := ParseString(inArray); SkipWhitespace; if inArray then begin if (filePos <= length(fileContent)) and (fileContent[filePos] = ']') then inc(filePos) else raise exception.Create('Expecting "]"'); end; if entryType = 'FORMDATA' then AddEntry(TFormDataEntry.Create(self,entryName,entryContent)) else AddEntry(TLazResourceEntry.Create(self,entryName,entryType,entryContent)); if (filePos+length(entryEnd)-1 <= length(fileContent)) and (CompareText(copy(fileContent,filePos,length(entryEnd)),entryEnd)=0) then inc(filePos,length(entryEnd)) else raise exception.Create('Expecting "'+entryEnd+'"'); end else if fileContent[filePos] in whiteSpace then inc(filePos) else raise exception.Create('Unexpected character "'+fileContent[filePos]+'"'); end; end; function TLazResourceContainer.CreateEntry(AName: utf8string; AExtension: utf8string; AContent: TStream): TMultiFileEntry; var binContent: TMemoryStream; begin if UTF8CompareText(AExtension,'lfm')=0 then begin binContent := TMemoryStream.Create; try AContent.Position:= 0; LRSObjectTextToBinary(AContent, binContent); result := TFormDataEntry.Create(self,AName,binContent); except on ex:Exception do begin binContent.Free; result := nil; end; end; AContent.Free; end else result := TLazResourceEntry.Create(self,AName,UTF8UpperCase(AExtension),AContent); end; procedure TLazResourceContainer.SaveToStream(ADestination: TStream); var i: Integer; begin for i := 0 to Count-1 do TLazResourceEntry(Entry[i]).Serialize(ADestination); end; end.