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 |
|
---|