1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 {$INCLUDE ../../shared/a_modes.inc}
18 {.$DEFINE UI_STYLE_DEBUG_SEARCH}
26 xstreams
, xparser
, utils
, hashtable
;
30 TStyleSection
= class;
32 TStyleValue
= packed record
34 type TType
= (Empty
, Bool
, Int
, Color
, Str
);
37 constructor Create (v
: Boolean);
38 constructor Create (v
: Integer);
39 constructor Create (ar
, ag
, ab
: Integer; aa
: Integer=255);
40 constructor Create (const v
: TGxRGBA
);
41 constructor Create (const v
: AnsiString);
43 function isEmpty (): Boolean; inline;
45 function toString (): AnsiString;
46 function asRGBA
: TGxRGBA
; inline;
47 function asRGBADef (const def
: TGxRGBA
): TGxRGBA
; inline;
48 function asInt (const def
: Integer=0): Integer; inline;
49 function asBool (const def
: Boolean=false): Boolean; inline;
50 function asStr (const def
: AnsiString=''): AnsiString; inline;
55 TType
.Bool
: (bval
: Boolean);
56 TType
.Int
: (ival
: Integer);
57 TType
.Color
: (r
, g
, b
, a
: Byte);
58 TType
.Str
: (sval
: Pointer); // AnsiString
61 THashStrStyleVal
= specialize THashBase
<AnsiString, TStyleValue
, THashKeyStrAnsiCI
>;
62 THashStrSection
= specialize THashBase
<AnsiString, TStyleSection
, THashKeyStrAnsiCI
>;
66 mParent
: TStyleSection
; // for inheritance
67 mInherits
: AnsiString;
68 mHashName
: AnsiString; // for this section
69 mCtlName
: AnsiString; // for this section
70 mVals
: THashStrStyleVal
;
71 mHashes
: THashStrSection
;
72 mCtls
: THashStrSection
;
75 function getTopLevel (): TStyleSection
; inline;
76 // "text-color#inactive@label"
77 function getValue (const path
: AnsiString): TStyleValue
;
80 constructor Create ();
81 destructor Destroy (); override;
83 function get (name
, hash
, ctl
: AnsiString): TStyleValue
;
86 property value
[const path
: AnsiString]: TStyleValue read getValue
; default
;
87 property topLevel
: TStyleSection read getTopLevel
;
92 mId
: AnsiString; // style name ('default', for example)
96 procedure createMain ();
98 procedure parse (par
: TTextParser
);
100 function getValue (const path
: AnsiString): TStyleValue
; inline;
103 constructor Create (const aid
: AnsiString);
104 constructor Create (st
: TStream
); // parse from stream
105 constructor CreateFromFile (const fname
: AnsiString);
106 destructor Destroy (); override;
108 function get (name
, hash
, ctl
: AnsiString): TStyleValue
;
111 property id
: AnsiString read mId
;
112 property value
[const path
: AnsiString]: TStyleValue read getValue
; default
;
116 procedure uiLoadStyles (const fname
: AnsiString);
117 procedure uiLoadStyles (st
: TStream
);
119 // will return "default" (or raise an exception if there is no "default")
120 function uiFindStyle (const stname
: AnsiString): TUIStyle
;
126 // ////////////////////////////////////////////////////////////////////////// //
130 ' back-color: #008;'#10+
131 ' #active: { text-color: #fff; frame-color: #fff; frame-text-color: #fff; frame-icon-color: #0f0; }'#10+
132 ' #inactive: { text-color: #aaa; frame-color: #aaa; frame-text-color: #aaa; frame-icon-color: #0a0; }'#10+
133 ' #disabled: { text-color: #666; frame-color: #888; frame-text-color: #888; frame-icon-color: #080; }'#10+
134 ' @simple_text: { text-color: #ff0; #inactive(#active); }'#10+
135 ' @cb_listbox: { current-item-back-color: #080; text-color: #ff0; #inactive(#active) { current-item-back-color: #000; } }'#10+
136 ' @window: { #inactive(#active): { darken: 128; } }'#10+
137 ' @button: { back-color: #999; text-color: #000; hot-color: #600; #active: { back-color: #fff; hot-color: #c00; } #disabled: { back-color: #444; text-color: #333; hot-color: #333; } }'#10+
138 ' @label: { #active: {back-color: #440;} #inactive(#active); }'#10+
139 ' @static: { text-color: #ff0; #inactive(#active); }'#10+
140 ' @box: { #inactive(#active); }'#10+
144 styles
: array of TUIStyle
= nil;
147 function createDefaultStyle (): TUIStyle
;
152 st
:= TStringStream
.Create(defaultStyleStr
);
155 result
:= TUIStyle
.Create(st
);
162 function uiFindStyle (const stname
: AnsiString): TUIStyle
;
166 if (Length(stname
) > 0) then
168 for stl
in styles
do if (strEquCI1251(stl
.mId
, stname
)) then begin result
:= stl
; exit
; end;
170 for stl
in styles
do if (strEquCI1251(stl
.mId
, 'default')) then begin result
:= stl
; exit
; end;
171 stl
:= createDefaultStyle();
172 SetLength(styles
, Length(styles
)+1);
173 styles
[High(styles
)] := stl
;
178 procedure uiLoadStyles (const fname
: AnsiString);
182 st
:= openDiskFileRO(fname
);
191 procedure uiLoadStyles (st
: TStream
);
197 if (st
= nil) then raise Exception
.Create('cannot load UI styles from nil stream');
198 par
:= TFileTextParser
.Create(st
, false, [par
.TOption
.SignedNumbers
, par
.TOption
.DollarIsId
, par
.TOption
.DashIsId
, par
.TOption
.HtmlColors
]);
201 while (not par
.isEOF
) do
203 stl
:= TUIStyle
.Create('');
205 //writeln('new style: <', stl.mId, '>');
207 while (f
< Length(styles
)) do begin if (strEquCI1251(styles
[f
].mId
, stl
.mId
)) then break
; Inc(f
); end;
208 if (f
< Length(styles
)) then
210 FreeAndNil(styles
[f
]);
215 SetLength(styles
, f
+1);
224 // we should have "default" style
225 for f
:= 0 to High(styles
) do if (strEquCI1251(styles
[f
].mId
, 'default')) then exit
;
226 stl
:= createDefaultStyle();
227 SetLength(styles
, Length(styles
)+1);
228 styles
[High(styles
)] := stl
;
232 // ////////////////////////////////////////////////////////////////////////// //
233 procedure freeValueCB (var v
: TStyleValue
); begin
234 if (v
.vtype
= v
.TType
.Str
) then
236 AnsiString(v
.sval
) := '';
238 v
.vtype
:= v
.TType
.Empty
;
241 constructor TStyleValue
.Create (v
: Boolean); begin vtype
:= TType
.Bool
; bval
:= v
; end;
242 constructor TStyleValue
.Create (v
: Integer); begin vtype
:= TType
.Int
; ival
:= v
; end;
243 constructor TStyleValue
.Create (const v
: AnsiString); begin vtype
:= TType
.Str
; sval
:= Pointer(v
); end;
245 constructor TStyleValue
.Create (ar
, ag
, ab
: Integer; aa
: Integer=255);
247 vtype
:= TType
.Color
;
248 r
:= nmax(0, nmin(ar
, 255));
249 g
:= nmax(0, nmin(ag
, 255));
250 b
:= nmax(0, nmin(ab
, 255));
251 a
:= nmax(0, nmin(aa
, 255));
254 constructor TStyleValue
.Create (const v
: TGxRGBA
);
256 vtype
:= TType
.Color
;
263 function TStyleValue
.isEmpty (): Boolean; inline; begin result
:= (vtype
= TType
.Empty
); end;
264 function TStyleValue
.asRGBA
: TGxRGBA
; inline; begin if (vtype
= TType
.Color
) then result
:= TGxRGBA
.Create(r
, g
, b
, a
) else result
:= TGxRGBA
.Create(0, 0, 0, 0); end;
265 function TStyleValue
.asRGBADef (const def
: TGxRGBA
): TGxRGBA
; inline; begin if (vtype
= TType
.Color
) then result
:= TGxRGBA
.Create(r
, g
, b
, a
) else result
:= def
; end;
266 function TStyleValue
.asInt (const def
: Integer=0): Integer; inline; begin if (vtype
= TType
.Int
) then result
:= ival
else if (vtype
= TType
.Bool
) then begin if (bval
) then result
:= 1 else result
:= 0; end else result
:= def
; end;
267 function TStyleValue
.asBool (const def
: Boolean=false): Boolean; inline; begin if (vtype
= TType
.Bool
) then result
:= bval
else if (vtype
= TType
.Int
) then result
:= (ival
<> 0) else result
:= def
; end;
268 function TStyleValue
.asStr (const def
: AnsiString=''): AnsiString; inline; begin if (vtype
= TType
.Str
) then result
:= AnsiString(sval
) else result
:= def
; end;
270 function TStyleValue
.toString (): AnsiString;
273 TType
.Empty
: result
:= '<empty>';
274 TType
.Bool
: if bval
then result
:= 'true' else result
:= 'false';
275 TType
.Int
: result
:= formatstrf('%s', [ival
]);
276 TType
.Color
: if (a
= 255) then result
:= formatstrf('rgb(%s,%s,%s)', [r
, g
, b
]) else result
:= formatstrf('rgba(%s,%s,%s)', [r
, g
, b
, a
]);
277 else result
:= '<invalid>';
282 // ////////////////////////////////////////////////////////////////////////// //
283 procedure freeSectionCB (var v
: TStyleSection
); begin FreeAndNil(v
); end;
286 function splitPath (const path
: AnsiString; out name
, hash
, ctl
: AnsiString): Boolean;
288 hashPos
, atPos
: Integer;
294 hashPos
:= pos('#', path
);
295 atPos
:= pos('@', path
);
299 // has ctl, and (possible) hash
300 if (hashPos
> 0) then
303 if (atPos
< hashPos
) then
306 if (atPos
> 1) then name
:= Copy(path
, 1, atPos
-1);
307 Inc(atPos
); // skip "at"
308 if (atPos
< hashPos
) then ctl
:= Copy(path
, atPos
, hashPos
-atPos
);
309 Inc(hashPos
); // skip hash
310 if (hashPos
<= Length(path
)) then hash
:= Copy(path
, hashPos
, Length(path
)-hashPos
+1);
315 if (hashPos
> 1) then name
:= Copy(path
, 1, hashPos
-1);
316 Inc(hashPos
); // skip hash
317 if (hashPos
< atPos
) then hash
:= Copy(path
, hashPos
, atPos
-hashPos
);
318 Inc(atPos
); // skip "at"
319 if (atPos
<= Length(path
)) then ctl
:= Copy(path
, atPos
, Length(path
)-atPos
+1);
325 if (atPos
> 1) then name
:= Copy(path
, 1, atPos
-1);
326 Inc(atPos
); // skip "at"
327 if (atPos
<= Length(path
)) then ctl
:= Copy(path
, atPos
, Length(path
)-atPos
+1);
330 else if (hashPos
> 0) then
333 if (hashPos
> 1) then name
:= Copy(path
, 1, hashPos
-1);
334 Inc(hashPos
); // skip hash
335 if (hashPos
<= Length(path
)) then hash
:= Copy(path
, hashPos
, Length(path
)-hashPos
+1);
346 // ////////////////////////////////////////////////////////////////////////// //
347 constructor TStyleSection
.Create ();
353 mVals
:= THashStrStyleVal
.Create(freeValueCB
);
354 mHashes
:= THashStrSection
.Create(freeSectionCB
);
355 mCtls
:= THashStrSection
.Create(freeSectionCB
);
359 destructor TStyleSection
.Destroy ();
372 function TStyleSection
.getTopLevel (): TStyleSection
; inline;
375 while (result
.mParent
<> nil) do result
:= result
.mParent
;
379 function TStyleSection
.get (name
, hash
, ctl
: AnsiString): TStyleValue
;
382 sect
, s1
, so
: TStyleSection
;
383 jumpsLeft
: Integer = 32; // max inheritance level
384 skipInherits
: Boolean = false;
386 result
.vtype
:= result
.TType
.Empty
;
387 if (Length(name
) = 0) then exit
; // alas
388 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('***GET: <', name
, '#', hash
, '@', ctl
, '>');{$ENDIF}
391 if (Length(ctl
) > 0) then
393 if (not strEquCI1251(ctl
, mCtlName
)) then
396 if (not topLevel
.mCtls
.get(ctl
, sect
)) then sect
:= topLevel
;
400 if (Length(hash
) > 0) then
402 if (not strEquCI1251(hash
, sect
.mHashName
)) then
404 if (sect
.mHashes
.get(hash
, s1
)) then sect
:= s1
;
407 // try name, go up with inheritance
408 while (jumpsLeft
> 0) do
410 if (sect
.mVals
.get(name
, result
)) then
412 if (not result
.isEmpty
) then exit
; // i found her!
415 if (skipInherits
) or (Length(sect
.mInherits
) = 0) then
417 skipInherits
:= false;
418 // for hash section: try parent section first
419 if (Length(sect
.mHashName
) > 0) then
421 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('search for <#', hash
, '@', ctl
, '> at <#', sect
.mHashName
, '@', sect
.mCtlName
, '>: hash up');{$ENDIF}
422 sect
:= sect
.mParent
;
423 if (sect
= nil) then break
; // alas
424 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' hash up: trying <#', sect
.mHashName
, '@', sect
.mCtlName
, '>');{$ENDIF}
425 if (sect
.mVals
.get(name
, result
)) then
427 if (not result
.isEmpty
) then exit
; // i found her!
429 // move another parent up
430 sect
:= sect
.mParent
;
431 if (sect
= nil) then break
; // alas
432 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' hash up: jumped up twice to <#', sect
.mHashName
, '@', sect
.mCtlName
, '>');{$ENDIF}
437 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('search for <#', hash
, '@', ctl
, '> at <#', sect
.mHashName
, '@', sect
.mCtlName
, '>: jump up');{$ENDIF}
438 sect
:= sect
.mParent
;
439 if (sect
= nil) then break
; // alas
441 // here, we should have non-hash section
442 assert(Length(sect
.mHashName
) = 0);
443 // if we want hash, try to find it, otherwise do nothing
444 if (Length(hash
) > 0) then
446 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' search for <#', hash
, '@', ctl
, '> at <#', sect
.mHashName
, '@', sect
.mCtlName
, '>: hash down');{$ENDIF}
447 if (sect
.mHashes
.get(hash
, s1
)) then
450 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' found <#', sect
.mHashName
, '@', sect
.mCtlName
, '>');{$ENDIF}
458 if (jumpsLeft
< 1) then break
; // alas
459 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('inherits: <', sect
.mInherits
, '>');{$ENDIF}
460 // parse inherit string
461 if (not splitPath(sect
.mInherits
, tmp
, hash
, ctl
)) then exit
; // alas
462 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('inherits: <', hash
, '>:<', ctl
, '>');{$ENDIF}
464 if (Length(ctl
) > 0) then
467 if (strEquCI1251(ctl
, '$main$')) then sect
:= topLevel
468 else if (strEquCI1251(ctl
, '$up$')) then begin if (Length(sect
.mHashName
) <> 0) then sect
:= sect
.mParent
.mParent
else sect
:= sect
.mParent
; end
469 else if (not topLevel
.mCtls
.get(ctl
, sect
)) then sect
:= topLevel
;
470 if (sect
= nil) then break
; // alas
471 if (Length(hash
) > 0) then
473 if (sect
.mHashes
.get(hash
, s1
)) then sect
:= s1
;
479 assert(Length(hash
) > 0);
480 // dummy loop, so i can use `break`
482 // get out of hash section
483 if (Length(sect
.mHashName
) > 0) then
485 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln('hash-jump-up: <#', sect
.mHashName
, '@', sect
.mCtlName
, '>');{$ENDIF}
486 sect
:= sect
.mParent
;
487 if (sect
= nil) then break
; // alas
488 // check for hash section in parent; use parent if there is no such hash section
489 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' checking parent: <#', sect
.mHashName
, '@', sect
.mCtlName
, '> for <#', hash
, '>');{$ENDIF}
491 if (sect
.mHashes
.get(hash
, s1
)) then
493 if (s1
<> sect
) and (s1
<> so
) then
495 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' found in parent: <#', sect
.mHashName
, '@', sect
.mCtlName
, '> for <#', hash
, '>');{$ENDIF}
502 // we're in parent, try to find hash section
503 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' checking: <#', sect
.mHashName
, '@', sect
.mCtlName
, '> for <#', hash
, '>');{$ENDIF}
504 if (sect
.mHashes
.get(hash
, s1
)) then
506 {$IFDEF UI_STYLE_DEBUG_SEARCH}writeln(' found: <#', sect
.mHashName
, '@', sect
.mCtlName
, '> for <#', hash
, '>');{$ENDIF}
511 // reuse current parent, but don't follow inheritance for it
512 skipInherits
:= true;
516 if (sect
= nil) then break
;
521 result
.vtype
:= result
.TType
.Empty
;
525 // "text-color#inactive@label"
526 function TStyleSection
.getValue (const path
: AnsiString): TStyleValue
;
528 name
, hash
, ctl
: AnsiString;
530 result
.vtype
:= result
.TType
.Empty
;
531 if (not splitPath(path
, name
, hash
, ctl
)) then exit
; // alas
532 //writeln('name:<', name, '>; hash:<', hash, '>; ctl:<', ctl, '>');
533 result
:= get(name
, hash
, ctl
);
537 // ////////////////////////////////////////////////////////////////////////// //
538 constructor TUIStyle
.Create (const aid
: AnsiString);
545 constructor TUIStyle
.Create (st
: TStream
); // parse from stream
551 if (st
= nil) then exit
;
552 par
:= TFileTextParser
.Create(st
, false, [par
.TOption
.SignedNumbers
, par
.TOption
.DollarIsId
, par
.TOption
.DashIsId
, par
.TOption
.HtmlColors
]);
561 constructor TUIStyle
.CreateFromFile (const fname
: AnsiString);
565 st
:= openDiskFileRO(fname
);
574 destructor TUIStyle
.Destroy ();
581 procedure TUIStyle
.createMain ();
583 mMain
:= TStyleSection
.Create();
584 mMain
.mCtlName
:= '$main$';
588 function TUIStyle
.getValue (const path
: AnsiString): TStyleValue
; inline;
590 result
:= mMain
[path
];
593 function TUIStyle
.get (name
, hash
, ctl
: AnsiString): TStyleValue
;
595 result
:= mMain
.get(name
, hash
, ctl
);
599 procedure TUIStyle
.parse (par
: TTextParser
);
600 function getByte (): Byte;
602 if (par
.tokType
<> par
.TTInt
) then par
.expectInt();
603 if (par
.tokInt
< 0) or (par
.tokInt
> 255) then par
.error('invalid byte value');
604 result
:= Byte(par
.tokInt
);
608 procedure parseSection (sect
: TStyleSection
; ctlAllowed
: Boolean; hashAllowed
: Boolean);
611 sc
: TStyleSection
= nil;
614 procedure parseInherit ();
617 if (par
.eatDelim('(')) then
619 if (par
.eatDelim(')')) then par
.error('empty inheritance is not allowed');
620 if (par
.eatDelim('#')) then
623 inh
+= par
.expectId();
625 if (par
.eatDelim('@')) then
628 inh
+= par
.expectId();
630 par
.expectDelim(')');
634 function nib2c (n
: Integer): Byte; inline;
636 if (n
< 0) then result
:= 0
637 else if (n
> 15) then result
:= 255
638 else result
:= Byte(255*n
div 15);
644 par
.expectDelim('{');
645 while (not par
.isDelim('}')) do
647 while (par
.eatDelim(';')) do begin end;
649 if ctlAllowed
and (par
.eatDelim('@')) then
653 par
.eatDelim(':'); // optional
654 if (not sect
.mCtls
.get(s
, sc
)) then
656 // create new section
657 sc
:= TStyleSection
.Create();
662 sect
.mCtls
.put(s
, sc
);
666 assert(sc
.mParent
= sect
);
667 assert(sc
.mHashName
= '');
668 assert(sc
.mCtlName
= s
);
669 if (Length(sc
.mInherits
) <> 0) and (Length(inh
) <> 0) then par
.error('double inheritance');
672 if (not par
.eatDelim(';')) then parseSection(sc
, false, true);
676 if hashAllowed
and (par
.eatDelim('#')) then
680 par
.eatDelim(':'); // optional
681 if (not sect
.mHashes
.get(s
, sc
)) then
683 // create new section
684 sc
:= TStyleSection
.Create();
689 sect
.mHashes
.put(s
, sc
);
693 assert(sc
.mParent
= sect
);
694 assert(sc
.mHashName
= s
);
695 assert(sc
.mCtlName
= '');
696 if (Length(sc
.mInherits
) <> 0) and (Length(inh
) <> 0) then par
.error('double inheritance');
699 if (not par
.eatDelim(';')) then parseSection(sc
, false, false);
704 par
.expectDelim(':');
705 if (par
.eatId('rgb')) or (par
.eatId('rgba')) then
708 par
.expectDelim('(');
709 v
.vtype
:= v
.TType
.Color
;
710 v
.r
:= getByte(); par
.eatDelim(','); // optional
711 v
.g
:= getByte(); par
.eatDelim(','); // optional
712 v
.b
:= getByte(); par
.eatDelim(','); // optional
713 if (par
.tokType
= par
.TTInt
) then
715 v
.a
:= getByte(); par
.eatDelim(','); // optional
719 v
.a
:= 255; // opaque
721 par
.expectDelim(')');
723 else if (par
.isId
) and (par
.tokStr
[1] = '#') then
726 assert((Length(par
.tokStr
) = 4) or (Length(par
.tokStr
) = 7));
727 //writeln('<', par.tokStr, '>; {', par.curChar, '}');
728 v
.vtype
:= v
.TType
.Color
;
729 if (Length(par
.tokStr
) = 4) then
732 v
.r
:= nib2c(digitInBase(par
.tokStr
[2], 16));
733 v
.g
:= nib2c(digitInBase(par
.tokStr
[3], 16));
734 v
.b
:= nib2c(digitInBase(par
.tokStr
[4], 16));
739 v
.r
:= Byte(digitInBase(par
.tokStr
[2], 16)*16+digitInBase(par
.tokStr
[3], 16));
740 v
.g
:= Byte(digitInBase(par
.tokStr
[4], 16)*16+digitInBase(par
.tokStr
[5], 16));
741 v
.b
:= Byte(digitInBase(par
.tokStr
[6], 16)*16+digitInBase(par
.tokStr
[7], 16));
744 //writeln(' r=', v.r, '; g=', v.g, '; b=', v.b);
747 else if (par
.eatId('true')) or (par
.eatId('tan')) then
749 v
.vtype
:= v
.TType
.Bool
;
752 else if (par
.eatId('false')) or (par
.eatId('ona')) then
754 v
.vtype
:= v
.TType
.Bool
;
757 else if (par
.isStr
) then
760 v
:= TStyleValue
.Create(par
.tokStr
);
763 else if (par
.eatId('inherit')) then
765 v
.vtype
:= v
.TType
.Empty
;
770 v
.vtype
:= v
.TType
.Int
;
771 v
.ival
:= par
.expectInt();
773 par
.expectDelim(';');
774 sect
.mVals
.put(s
, v
);
776 par
.expectDelim('}');
781 if (not par
.isIdOrStr
) then
783 if (Length(mId
) = 0) then par
.error('style name expected');
789 if (Length(mId
) = 0) then mId
:= 'default';
791 if (not par
.eatDelim(';')) then parseSection(mMain
, true, true);