| 1 | unit BGRALazResource;
|
|---|
| 2 |
|
|---|
| 3 | {$mode objfpc}{$H+}
|
|---|
| 4 |
|
|---|
| 5 | interface
|
|---|
| 6 |
|
|---|
| 7 | uses
|
|---|
| 8 | Classes, SysUtils, BGRAMultiFileType;
|
|---|
| 9 |
|
|---|
| 10 | type
|
|---|
| 11 | { TLazResourceEntry }
|
|---|
| 12 |
|
|---|
| 13 | TLazResourceEntry = class(TMultiFileEntry)
|
|---|
| 14 | private
|
|---|
| 15 | procedure Serialize(ADestination: TStream);
|
|---|
| 16 | protected
|
|---|
| 17 | FName: utf8string;
|
|---|
| 18 | FValueType: utf8string;
|
|---|
| 19 | FContent: TStream;
|
|---|
| 20 | function GetName: utf8string; override;
|
|---|
| 21 | procedure SetName(AValue: utf8string); override;
|
|---|
| 22 | function GetExtension: utf8string; override;
|
|---|
| 23 | function GetFileSize: int64; override;
|
|---|
| 24 | public
|
|---|
| 25 | constructor Create(AContainer: TMultiFileContainer; AName: utf8string; AValueType: utf8string; AContent: TStream);
|
|---|
| 26 | destructor Destroy; override;
|
|---|
| 27 | function CopyTo(ADestination: TStream): int64; override;
|
|---|
| 28 | end;
|
|---|
| 29 |
|
|---|
| 30 | { TFormDataEntry }
|
|---|
| 31 |
|
|---|
| 32 | TFormDataEntry = class(TLazResourceEntry)
|
|---|
| 33 | protected
|
|---|
| 34 | FTextContent: TStream;
|
|---|
| 35 | procedure RequireTextContent;
|
|---|
| 36 | function GetExtension: utf8string; override;
|
|---|
| 37 | function GetFileSize: int64; override;
|
|---|
| 38 | public
|
|---|
| 39 | constructor Create(AContainer: TMultiFileContainer; AName: utf8string; ABinaryContent: TStream);
|
|---|
| 40 | destructor Destroy; override;
|
|---|
| 41 | function CopyTo(ADestination: TStream): int64; override;
|
|---|
| 42 | end;
|
|---|
| 43 |
|
|---|
| 44 | { TLazResourceContainer }
|
|---|
| 45 |
|
|---|
| 46 | TLazResourceContainer = class(TMultiFileContainer)
|
|---|
| 47 | protected
|
|---|
| 48 | function CreateEntry(AName: utf8string; AExtension: utf8string; AContent: TStream): TMultiFileEntry; override;
|
|---|
| 49 | public
|
|---|
| 50 | procedure LoadFromStream(AStream: TStream); override;
|
|---|
| 51 | procedure SaveToStream(ADestination: TStream); override;
|
|---|
| 52 | end;
|
|---|
| 53 |
|
|---|
| 54 | implementation
|
|---|
| 55 |
|
|---|
| 56 | uses LResources, BGRAUTF8;
|
|---|
| 57 |
|
|---|
| 58 | { TFormDataEntry }
|
|---|
| 59 |
|
|---|
| 60 | procedure TFormDataEntry.RequireTextContent;
|
|---|
| 61 | begin
|
|---|
| 62 | if FTextContent = nil then
|
|---|
| 63 | begin
|
|---|
| 64 | FTextContent:= TMemoryStream.Create;
|
|---|
| 65 | FContent.Position:= 0;
|
|---|
| 66 | LRSObjectBinaryToText(FContent, FTextContent);
|
|---|
| 67 | end;
|
|---|
| 68 | end;
|
|---|
| 69 |
|
|---|
| 70 | function TFormDataEntry.GetExtension: utf8string;
|
|---|
| 71 | begin
|
|---|
| 72 | Result:= 'lfm';
|
|---|
| 73 | end;
|
|---|
| 74 |
|
|---|
| 75 | function TFormDataEntry.GetFileSize: int64;
|
|---|
| 76 | begin
|
|---|
| 77 | RequireTextContent;
|
|---|
| 78 | Result:= FTextContent.Size;
|
|---|
| 79 | end;
|
|---|
| 80 |
|
|---|
| 81 | constructor TFormDataEntry.Create(AContainer: TMultiFileContainer;
|
|---|
| 82 | AName: utf8string; ABinaryContent: TStream);
|
|---|
| 83 | begin
|
|---|
| 84 | inherited Create(AContainer,AName,'FORMDATA',ABinaryContent);
|
|---|
| 85 | end;
|
|---|
| 86 |
|
|---|
| 87 | destructor TFormDataEntry.Destroy;
|
|---|
| 88 | begin
|
|---|
| 89 | FreeAndNil(FTextContent);
|
|---|
| 90 | inherited Destroy;
|
|---|
| 91 | end;
|
|---|
| 92 |
|
|---|
| 93 | function TFormDataEntry.CopyTo(ADestination: TStream): int64;
|
|---|
| 94 | begin
|
|---|
| 95 | RequireTextContent;
|
|---|
| 96 | if FTextContent.Size = 0 then
|
|---|
| 97 | result := 0
|
|---|
| 98 | else
|
|---|
| 99 | begin
|
|---|
| 100 | FTextContent.Position:= 0;
|
|---|
| 101 | result := ADestination.CopyFrom(FTextContent,FTextContent.Size);
|
|---|
| 102 | end;
|
|---|
| 103 | end;
|
|---|
| 104 |
|
|---|
| 105 | { TLazResourceEntry }
|
|---|
| 106 |
|
|---|
| 107 | procedure TLazResourceEntry.Serialize(ADestination: TStream);
|
|---|
| 108 | begin
|
|---|
| 109 | FContent.Position := 0;
|
|---|
| 110 | BinaryToLazarusResourceCode(FContent, ADestination, Name, FValueType);
|
|---|
| 111 | end;
|
|---|
| 112 |
|
|---|
| 113 | function TLazResourceEntry.GetName: utf8string;
|
|---|
| 114 | begin
|
|---|
| 115 | Result:= FName;
|
|---|
| 116 | end;
|
|---|
| 117 |
|
|---|
| 118 | procedure TLazResourceEntry.SetName(AValue: utf8string);
|
|---|
| 119 | begin
|
|---|
| 120 | if AValue = FName then exit;
|
|---|
| 121 | if Container.IndexOf(AVAlue, Extension) <> -1 then
|
|---|
| 122 | raise Exception.Create('Name is already used for this extension');
|
|---|
| 123 | FName := AValue;
|
|---|
| 124 | end;
|
|---|
| 125 |
|
|---|
| 126 | function TLazResourceEntry.GetExtension: utf8string;
|
|---|
| 127 | begin
|
|---|
| 128 | Result:= FValueType;
|
|---|
| 129 | end;
|
|---|
| 130 |
|
|---|
| 131 | function TLazResourceEntry.GetFileSize: int64;
|
|---|
| 132 | begin
|
|---|
| 133 | Result:= FContent.Size;
|
|---|
| 134 | end;
|
|---|
| 135 |
|
|---|
| 136 | destructor TLazResourceEntry.Destroy;
|
|---|
| 137 | begin
|
|---|
| 138 | FreeAndNil(FContent);
|
|---|
| 139 | inherited Destroy;
|
|---|
| 140 | end;
|
|---|
| 141 |
|
|---|
| 142 | constructor TLazResourceEntry.Create(AContainer: TMultiFileContainer; AName: utf8string; AValueType: utf8string;
|
|---|
| 143 | AContent: TStream);
|
|---|
| 144 | begin
|
|---|
| 145 | inherited Create(AContainer);
|
|---|
| 146 | FName := AName;
|
|---|
| 147 | FValueType := UTF8UpperCase(AValueType);
|
|---|
| 148 | FContent := AContent;
|
|---|
| 149 | end;
|
|---|
| 150 |
|
|---|
| 151 | function TLazResourceEntry.CopyTo(ADestination: TStream): int64;
|
|---|
| 152 | begin
|
|---|
| 153 | if FContent.Size = 0 then
|
|---|
| 154 | result := 0
|
|---|
| 155 | else
|
|---|
| 156 | begin
|
|---|
| 157 | FContent.Position:= 0;
|
|---|
| 158 | result := ADestination.CopyFrom(FContent, FContent.Size);
|
|---|
| 159 | end;
|
|---|
| 160 | end;
|
|---|
| 161 |
|
|---|
| 162 | { TLazResourceContainer }
|
|---|
| 163 |
|
|---|
| 164 | procedure TLazResourceContainer.LoadFromStream(AStream: TStream);
|
|---|
| 165 | const
|
|---|
| 166 | entryStart = 'LazarusResources.Add(';
|
|---|
| 167 | entryEnd = ');';
|
|---|
| 168 | whiteSpace = [' ',#9,#10,#13,#26];
|
|---|
| 169 | var
|
|---|
| 170 | fileContent: String;
|
|---|
| 171 | filePos : integer;
|
|---|
| 172 |
|
|---|
| 173 | procedure SkipWhitespace;
|
|---|
| 174 | begin
|
|---|
| 175 | while (filePos <= length(fileContent)) and (fileContent[filePos] in whiteSpace) do inc(filePos);
|
|---|
| 176 | end;
|
|---|
| 177 |
|
|---|
| 178 | procedure SkipComma;
|
|---|
| 179 | begin
|
|---|
| 180 | SkipWhitespace;
|
|---|
| 181 | if (filePos <= length(fileContent)) and (fileContent[filePos] = ',') then
|
|---|
| 182 | inc(filePos)
|
|---|
| 183 | else
|
|---|
| 184 | raise Exception.Create('Comma expected');
|
|---|
| 185 | end;
|
|---|
| 186 |
|
|---|
| 187 | function ParseString(ignoreCommas: boolean): TStream;
|
|---|
| 188 | var
|
|---|
| 189 | expectPlus: boolean;
|
|---|
| 190 |
|
|---|
| 191 | procedure AppendChar(c: char);
|
|---|
| 192 | begin
|
|---|
| 193 | result.WriteByte(ord(c));
|
|---|
| 194 | end;
|
|---|
| 195 |
|
|---|
| 196 | function ParseNumber: integer;
|
|---|
| 197 | var numberStart, errPos: integer;
|
|---|
| 198 | s: String;
|
|---|
| 199 | begin
|
|---|
| 200 | numberStart:= filePos;
|
|---|
| 201 | if (filePos <= length(fileContent)) and (fileContent[filePos] = '$') then
|
|---|
| 202 | begin
|
|---|
| 203 | inc(filePos);
|
|---|
| 204 | while (filePos <= length(fileContent)) and (fileContent[filePos] in['0'..'9','a'..'f','A'..'F']) do inc(filePos);
|
|---|
| 205 | end else
|
|---|
| 206 | begin
|
|---|
| 207 | while (filePos <= length(fileContent)) and (fileContent[filePos] in['0'..'9']) do inc(filePos);
|
|---|
| 208 | end;
|
|---|
| 209 | s := copy(fileContent,numberStart,filePos-numberStart);
|
|---|
| 210 | val(s, result, errPos);
|
|---|
| 211 | if errPos <> 0 then
|
|---|
| 212 | raise exception.Create('Invalid number "' + s + '"');
|
|---|
| 213 | end;
|
|---|
| 214 |
|
|---|
| 215 | function ParseStringPart: boolean;
|
|---|
| 216 | var charCode: integer;
|
|---|
| 217 | begin
|
|---|
| 218 | SkipWhitespace;
|
|---|
| 219 | if filePos <= length(fileContent) then
|
|---|
| 220 | begin
|
|---|
| 221 | if expectPlus then
|
|---|
| 222 | if fileContent[filePos] <> '+' then
|
|---|
| 223 | begin
|
|---|
| 224 | result := false;
|
|---|
| 225 | expectPlus := false;
|
|---|
| 226 | exit;
|
|---|
| 227 | end else
|
|---|
| 228 | inc(filePos);
|
|---|
| 229 |
|
|---|
| 230 | case fileContent[filePos] of
|
|---|
| 231 | '+': raise exception.Create('Unexpected "+"');
|
|---|
| 232 | '''': begin
|
|---|
| 233 | inc(filePos);
|
|---|
| 234 | while (filePos <= length(fileContent)) do
|
|---|
| 235 | begin
|
|---|
| 236 | if fileContent[filePos] = '''' then
|
|---|
| 237 | begin
|
|---|
| 238 | inc(filePos);
|
|---|
| 239 | if (filePos <= length(fileContent)) and (fileContent[filePos] = '''') then
|
|---|
| 240 | begin
|
|---|
| 241 | AppendChar('''');
|
|---|
| 242 | inc(filePos);
|
|---|
| 243 | end
|
|---|
| 244 | else break;
|
|---|
| 245 | end else
|
|---|
| 246 | if fileContent[filePos] in[#10,#13] then
|
|---|
| 247 | raise Exception.Create('Unexpected end of line')
|
|---|
| 248 | else
|
|---|
| 249 | begin
|
|---|
| 250 | AppendChar(fileContent[filePos]);
|
|---|
| 251 | inc(filePos);
|
|---|
| 252 | end;
|
|---|
| 253 | end;
|
|---|
| 254 | if (filePos <= length(fileContent)) and (fileContent[filePos] = '#') then
|
|---|
| 255 | expectPlus := false
|
|---|
| 256 | else
|
|---|
| 257 | expectPlus := true;
|
|---|
| 258 | result := true;
|
|---|
| 259 | end;
|
|---|
| 260 | '#': begin
|
|---|
| 261 | inc(filePos);
|
|---|
| 262 | charCode := ParseNumber;
|
|---|
| 263 | if (charCode < 0) or (charCode > 255) then
|
|---|
| 264 | raise exception.Create('Character code out of bounds');
|
|---|
| 265 | AppendChar(chr(charCode));
|
|---|
| 266 | if (filePos <= length(fileContent)) and (fileContent[filePos] in['#','''']) then
|
|---|
| 267 | expectPlus := false
|
|---|
| 268 | else
|
|---|
| 269 | expectPlus := true;
|
|---|
| 270 | result := true;
|
|---|
| 271 | end;
|
|---|
| 272 | else
|
|---|
| 273 | begin
|
|---|
| 274 | result := false;
|
|---|
| 275 | expectPlus := false;
|
|---|
| 276 | end;
|
|---|
| 277 | end;
|
|---|
| 278 | end
|
|---|
| 279 | else
|
|---|
| 280 | begin
|
|---|
| 281 | result := false;
|
|---|
| 282 | expectPlus := false;
|
|---|
| 283 | end;
|
|---|
| 284 | end;
|
|---|
| 285 |
|
|---|
| 286 | begin
|
|---|
| 287 | result := TMemoryStream.Create;
|
|---|
| 288 | expectPlus := false;
|
|---|
| 289 | if not ParseStringPart then raise exception.Create('Expecting string');
|
|---|
| 290 | repeat
|
|---|
| 291 | if ignoreCommas then
|
|---|
| 292 | begin
|
|---|
| 293 | SkipWhitespace;
|
|---|
| 294 | if (filePos <= length(fileContent)) and (fileContent[filePos] = ',') then
|
|---|
| 295 | begin
|
|---|
| 296 | inc(filePos);
|
|---|
| 297 | expectPlus := false;
|
|---|
| 298 | end;
|
|---|
| 299 | end;
|
|---|
| 300 | until not ParseStringPart;
|
|---|
| 301 | end;
|
|---|
| 302 |
|
|---|
| 303 | procedure ReadContent;
|
|---|
| 304 | var
|
|---|
| 305 | bytesRead: integer;
|
|---|
| 306 | begin
|
|---|
| 307 | setlength(fileContent,AStream.Size-AStream.Position);
|
|---|
| 308 | bytesRead := AStream.Read(fileContent[1],length(fileContent));
|
|---|
| 309 | setlength(fileContent, bytesRead);
|
|---|
| 310 | filePos := 1;
|
|---|
| 311 | end;
|
|---|
| 312 |
|
|---|
| 313 | function StreamToUTF8String(AStream: TStream): utf8String;
|
|---|
| 314 | begin
|
|---|
| 315 | setlength(result, AStream.Size);
|
|---|
| 316 | AStream.Position := 0;
|
|---|
| 317 | AStream.Read(result[1], length(result));
|
|---|
| 318 | AStream.Free;
|
|---|
| 319 | end;
|
|---|
| 320 |
|
|---|
| 321 | var
|
|---|
| 322 | entryName: utf8string;
|
|---|
| 323 | entryType: utf8string;
|
|---|
| 324 | entryContent: TStream;
|
|---|
| 325 | inArray: boolean;
|
|---|
| 326 |
|
|---|
| 327 | begin
|
|---|
| 328 | Clear;
|
|---|
| 329 | ReadContent;
|
|---|
| 330 | while filePos <= length(fileContent) do
|
|---|
| 331 | begin
|
|---|
| 332 | if (upcase(fileContent[filePos]) = upcase(entryStart[1])) and
|
|---|
| 333 | (CompareText(copy(fileContent,filePos,length(entryStart)),entryStart)=0) then
|
|---|
| 334 | begin
|
|---|
| 335 | inc(filePos, length(entryStart));
|
|---|
| 336 | entryName := StreamToUTF8String(ParseString(false));
|
|---|
| 337 | SkipComma;
|
|---|
| 338 | entryType := StreamToUTF8String(ParseString(false));
|
|---|
| 339 | SkipComma;
|
|---|
| 340 |
|
|---|
| 341 | SkipWhitespace;
|
|---|
| 342 | if (filePos <= length(fileContent)) and (fileContent[filePos] = '[') then
|
|---|
| 343 | begin
|
|---|
| 344 | inArray := true;
|
|---|
| 345 | inc(filePos);
|
|---|
| 346 | end else
|
|---|
| 347 | inArray := false;
|
|---|
| 348 | entryContent := ParseString(inArray);
|
|---|
| 349 | SkipWhitespace;
|
|---|
| 350 | if inArray then
|
|---|
| 351 | begin
|
|---|
| 352 | if (filePos <= length(fileContent)) and (fileContent[filePos] = ']') then
|
|---|
| 353 | inc(filePos)
|
|---|
| 354 | else
|
|---|
| 355 | raise exception.Create('Expecting "]"');
|
|---|
| 356 | end;
|
|---|
| 357 |
|
|---|
| 358 | if entryType = 'FORMDATA' then
|
|---|
| 359 | AddEntry(TFormDataEntry.Create(self,entryName,entryContent))
|
|---|
| 360 | else
|
|---|
| 361 | AddEntry(TLazResourceEntry.Create(self,entryName,entryType,entryContent));
|
|---|
| 362 |
|
|---|
| 363 | if (filePos+length(entryEnd)-1 <= length(fileContent)) and (CompareText(copy(fileContent,filePos,length(entryEnd)),entryEnd)=0) then
|
|---|
| 364 | inc(filePos,length(entryEnd))
|
|---|
| 365 | else
|
|---|
| 366 | raise exception.Create('Expecting "'+entryEnd+'"');
|
|---|
| 367 | end else
|
|---|
| 368 | if fileContent[filePos] in whiteSpace then
|
|---|
| 369 | inc(filePos)
|
|---|
| 370 | else
|
|---|
| 371 | raise exception.Create('Unexpected character "'+fileContent[filePos]+'"');
|
|---|
| 372 | end;
|
|---|
| 373 | end;
|
|---|
| 374 |
|
|---|
| 375 | function TLazResourceContainer.CreateEntry(AName: utf8string; AExtension: utf8string;
|
|---|
| 376 | AContent: TStream): TMultiFileEntry;
|
|---|
| 377 | var
|
|---|
| 378 | binContent: TMemoryStream;
|
|---|
| 379 | begin
|
|---|
| 380 | if UTF8CompareText(AExtension,'lfm')=0 then
|
|---|
| 381 | begin
|
|---|
| 382 | binContent := TMemoryStream.Create;
|
|---|
| 383 | try
|
|---|
| 384 | AContent.Position:= 0;
|
|---|
| 385 | LRSObjectTextToBinary(AContent, binContent);
|
|---|
| 386 | result := TFormDataEntry.Create(self,AName,binContent);
|
|---|
| 387 | except
|
|---|
| 388 | on ex:Exception do
|
|---|
| 389 | begin
|
|---|
| 390 | binContent.Free;
|
|---|
| 391 | result := nil;
|
|---|
| 392 | end;
|
|---|
| 393 | end;
|
|---|
| 394 | AContent.Free;
|
|---|
| 395 | end
|
|---|
| 396 | else
|
|---|
| 397 | result := TLazResourceEntry.Create(self,AName,UTF8UpperCase(AExtension),AContent);
|
|---|
| 398 | end;
|
|---|
| 399 |
|
|---|
| 400 | procedure TLazResourceContainer.SaveToStream(ADestination: TStream);
|
|---|
| 401 | var
|
|---|
| 402 | i: Integer;
|
|---|
| 403 | begin
|
|---|
| 404 | for i := 0 to Count-1 do
|
|---|
| 405 | TLazResourceEntry(Entry[i]).Serialize(ADestination);
|
|---|
| 406 | end;
|
|---|
| 407 |
|
|---|
| 408 | end.
|
|---|
| 409 |
|
|---|