Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <ctype.h>
21 : #include <stdlib.h>
22 : #include <stdio.h>
23 : #include <limits.h>
24 : #include <rtl/ustrbuf.hxx>
25 : #include <vcl/svapp.hxx>
26 : #include <svtools/htmltokn.h>
27 : #include <comphelper/string.hxx>
28 : #include "css1kywd.hxx"
29 : #include "parcss1.hxx"
30 :
31 : // Loop-Check: Um Endlos-Schleifen zu vermeiden, wird in jeder
32 : // Schalife geprueft, ob ein Fortschritt in der Eingabe-Position
33 : // stattgefunden hat
34 : #define LOOP_CHECK
35 :
36 : #ifdef LOOP_CHECK
37 :
38 : #define LOOP_CHECK_DECL \
39 : sal_Int32 nOldInPos = SAL_MAX_INT32;
40 : #define LOOP_CHECK_RESTART \
41 : nOldInPos = SAL_MAX_INT32;
42 : #define LOOP_CHECK_CHECK( where ) \
43 : OSL_ENSURE( nOldInPos!=nInPos || cNextCh==(sal_Unicode)EOF, where ); \
44 : if( nOldInPos==nInPos && cNextCh!=(sal_Unicode)EOF ) \
45 : break; \
46 : else \
47 : nOldInPos = nInPos;
48 :
49 : #else
50 :
51 : #define LOOP_CHECK_DECL
52 : #define LOOP_CHECK_RESTART
53 : #define LOOP_CHECK_CHECK( where )
54 :
55 : #endif
56 :
57 : const sal_Int32 MAX_LEN = 1024;
58 :
59 0 : void CSS1Parser::InitRead( const OUString& rIn )
60 : {
61 0 : nlLineNr = 0;
62 0 : nlLinePos = 0;
63 :
64 0 : bWhiteSpace = sal_True; // Wenn noch nichts gelesen wurde ist das wie WS
65 0 : bEOF = sal_False;
66 0 : eState = CSS1_PAR_WORKING;
67 0 : nValue = 0.;
68 :
69 0 : aIn = rIn;
70 0 : nInPos = 0;
71 0 : cNextCh = GetNextChar();
72 0 : nToken = GetNextToken();
73 0 : }
74 :
75 0 : sal_Unicode CSS1Parser::GetNextChar()
76 : {
77 0 : if( nInPos >= aIn.getLength() )
78 : {
79 0 : bEOF = sal_True;
80 0 : return (sal_Unicode)EOF;
81 : }
82 :
83 0 : sal_Unicode c = aIn[nInPos];
84 0 : nInPos++;
85 :
86 0 : if( c == '\n' )
87 : {
88 0 : IncLineNr();
89 0 : SetLinePos( 1L );
90 : }
91 : else
92 0 : IncLinePos();
93 :
94 0 : return c;
95 : }
96 :
97 : // Diese Funktion realisiert den in
98 :
99 : // http://www.w3.orh/pub/WWW/TR/WD-css1.html
100 : // bzw. http://www.w3.orh/pub/WWW/TR/WD-css1-960220.html
101 :
102 : // beschriebenen Scanner fuer CSS1. Es handelt sich um eine direkte
103 : // Umsetzung der dort beschriebenen Lex-Grammatik
104 :
105 0 : CSS1Token CSS1Parser::GetNextToken()
106 : {
107 0 : CSS1Token nRet = CSS1_NULL;
108 0 : aToken = "";
109 :
110 0 : do {
111 : // Merken, ob davor White-Space gelesen wurde
112 0 : sal_Bool bPrevWhiteSpace = bWhiteSpace;
113 0 : bWhiteSpace = sal_False;
114 :
115 0 : sal_Bool bNextCh = sal_True;
116 0 : switch( cNextCh )
117 : {
118 : case '/': // COMMENT | '/'
119 : {
120 0 : cNextCh = GetNextChar();
121 0 : if( '*' == cNextCh )
122 : {
123 : // COMMENT
124 0 : cNextCh = GetNextChar();
125 :
126 0 : sal_Bool bAsterisk = sal_False;
127 0 : while( !(bAsterisk && '/'==cNextCh) && !IsEOF() )
128 : {
129 0 : bAsterisk = ('*'==cNextCh);
130 0 : cNextCh = GetNextChar();
131 : }
132 : }
133 : else
134 : {
135 : // '/'
136 0 : bNextCh = sal_False;
137 0 : nRet = CSS1_SLASH;
138 : }
139 : }
140 0 : break;
141 :
142 : case '@': // '@import' | '@XXX'
143 : {
144 0 : cNextCh = GetNextChar();
145 0 : if (comphelper::string::isalphaAscii(cNextCh))
146 : {
147 : // den naechsten Identifer scannen
148 0 : OUStringBuffer sTmpBuffer( 32L );
149 0 : do {
150 0 : sTmpBuffer.append( cNextCh );
151 0 : cNextCh = GetNextChar();
152 0 : } while( (comphelper::string::isalnumAscii(cNextCh) ||
153 0 : '-' == cNextCh) && !IsEOF() );
154 :
155 0 : aToken += sTmpBuffer.makeStringAndClear();
156 :
157 : // und schauen, ob wir ihn kennen
158 0 : switch( aToken[0] )
159 : {
160 : case 'i':
161 : case 'I':
162 0 : if( aToken.equalsIgnoreAsciiCase( "import" ) )
163 0 : nRet = CSS1_IMPORT_SYM;
164 0 : break;
165 : // /Feature: PrintExt
166 : case 'p':
167 : case 'P':
168 0 : if( aToken.equalsIgnoreAsciiCase( "page" ) )
169 0 : nRet = CSS1_PAGE_SYM;
170 0 : break;
171 : // /Feature: PrintExt
172 : }
173 :
174 : // Fehlerbehandlung: '@ident' und alles bis
175 : // zu einem Semikolon der dem Ende des folgenden
176 : // Blocks ignorieren
177 0 : if( CSS1_NULL==nRet )
178 : {
179 0 : aToken = "";
180 0 : sal_uInt16 nBlockLvl = 0;
181 0 : sal_Unicode cQuoteCh = 0;
182 0 : sal_Bool bDone = sal_False, bEscape = sal_False;
183 0 : while( !bDone && !IsEOF() )
184 : {
185 0 : sal_Bool bOldEscape = bEscape;
186 0 : bEscape = sal_False;
187 0 : switch( cNextCh )
188 : {
189 : case '{':
190 0 : if( !cQuoteCh && !bOldEscape )
191 0 : nBlockLvl++;
192 0 : break;
193 : case ';':
194 0 : if( !cQuoteCh && !bOldEscape )
195 0 : bDone = nBlockLvl==0;
196 0 : break;
197 : case '}':
198 0 : if( !cQuoteCh && !bOldEscape )
199 0 : bDone = --nBlockLvl==0;
200 0 : break;
201 : case '\"':
202 : case '\'':
203 0 : if( !bOldEscape )
204 : {
205 0 : if( cQuoteCh )
206 : {
207 0 : if( cQuoteCh == cNextCh )
208 0 : cQuoteCh = 0;
209 : }
210 : else
211 : {
212 0 : cQuoteCh = cNextCh;
213 : }
214 : }
215 0 : break;
216 : case '\\':
217 0 : if( !bOldEscape )
218 0 : bEscape = sal_True;
219 0 : break;
220 : }
221 0 : cNextCh = GetNextChar();
222 : }
223 : }
224 :
225 0 : bNextCh = sal_False;
226 : }
227 : }
228 0 : break;
229 :
230 : case '!': // '!' 'legal' | '!' 'important' | syntax error
231 : {
232 : // White Space ueberlesen
233 0 : cNextCh = GetNextChar();
234 0 : while( ( ' ' == cNextCh ||
235 0 : (cNextCh >= 0x09 && cNextCh <= 0x0d) ) && !IsEOF() )
236 : {
237 0 : bWhiteSpace = sal_True;
238 0 : cNextCh = GetNextChar();
239 : }
240 :
241 0 : if( 'i'==cNextCh || 'I'==cNextCh)
242 : {
243 : // den naechsten Identifer scannen
244 0 : OUStringBuffer sTmpBuffer( 32L );
245 0 : do {
246 0 : sTmpBuffer.append( cNextCh );
247 0 : cNextCh = GetNextChar();
248 0 : } while( (comphelper::string::isalnumAscii(cNextCh) ||
249 0 : '-' == cNextCh) && !IsEOF() );
250 :
251 0 : aToken += sTmpBuffer.makeStringAndClear();
252 :
253 0 : if( ( 'i'==aToken[0] || 'I'==aToken[0] ) &&
254 0 : aToken.equalsIgnoreAsciiCase( "important" ) )
255 : {
256 : // '!' 'important'
257 0 : nRet = CSS1_IMPORTANT_SYM;
258 : }
259 : else
260 : {
261 : // Fehlerbehandlung: '!' ignorieren, IDENT nicht
262 0 : nRet = CSS1_IDENT;
263 : }
264 :
265 0 : bWhiteSpace = sal_False;
266 0 : bNextCh = sal_False;
267 : }
268 : else
269 : {
270 : // Fehlerbehandlung: '!' ignorieren
271 0 : bNextCh = sal_False;
272 : }
273 : }
274 0 : break;
275 :
276 : case '\"':
277 : case '\'': // STRING
278 : {
279 : // \... geht noch nicht!!!
280 0 : sal_Unicode cQuoteChar = cNextCh;
281 0 : cNextCh = GetNextChar();
282 :
283 0 : OUStringBuffer sTmpBuffer( MAX_LEN );
284 0 : do {
285 0 : sTmpBuffer.append( cNextCh );
286 0 : cNextCh = GetNextChar();
287 0 : } while( cQuoteChar != cNextCh && !IsEOF() );
288 :
289 0 : aToken += sTmpBuffer.toString();
290 :
291 0 : nRet = CSS1_STRING;
292 : }
293 0 : break;
294 :
295 : case '0':
296 : case '1':
297 : case '2':
298 : case '3':
299 : case '4':
300 : case '5':
301 : case '6':
302 : case '7':
303 : case '8':
304 : case '9': // NUMBER | PERCENTAGE | LENGTH
305 : {
306 : // die aktuelle Position retten
307 0 : sal_Size nInPosSave = nInPos;
308 0 : sal_Unicode cNextChSave = cNextCh;
309 0 : sal_uInt32 nlLineNrSave = nlLineNr;
310 0 : sal_uInt32 nlLinePosSave = nlLinePos;
311 0 : sal_Bool bEOFSave = bEOF;
312 :
313 : // erstmal versuchen eine Hex-Zahl zu scannen
314 0 : OUStringBuffer sTmpBuffer( 16 );
315 0 : do {
316 0 : sTmpBuffer.append( cNextCh );
317 0 : cNextCh = GetNextChar();
318 0 : } while( sTmpBuffer.getLength() < 7 &&
319 0 : ( ('0'<=cNextCh && '9'>=cNextCh) ||
320 0 : ('A'<=cNextCh && 'F'>=cNextCh) ||
321 0 : ('a'<=cNextCh && 'f'>=cNextCh) ) &&
322 0 : !IsEOF() );
323 :
324 0 : if( sTmpBuffer.getLength()==6 )
325 : {
326 : // wir haben eine hexadezimale Farbe gefunden
327 0 : aToken += sTmpBuffer.makeStringAndClear();
328 0 : nRet = CSS1_HEXCOLOR;
329 0 : bNextCh = sal_False;
330 :
331 0 : break;
332 : }
333 :
334 : // sonst versuchen wir es mit einer Zahl
335 0 : nInPos = nInPosSave;
336 0 : cNextCh = cNextChSave;
337 0 : nlLineNr = nlLineNrSave;
338 0 : nlLinePos = nlLinePosSave;
339 0 : bEOF = bEOFSave;
340 :
341 : // erstmal die Zahl scannen
342 0 : sTmpBuffer.setLength( 0L );
343 0 : do {
344 0 : sTmpBuffer.append( cNextCh );
345 0 : cNextCh = GetNextChar();
346 0 : } while( (('0'<=cNextCh && '9'>=cNextCh) || '.'==cNextCh) &&
347 0 : !IsEOF() );
348 :
349 0 : aToken += sTmpBuffer.makeStringAndClear();
350 0 : nValue = aToken.toDouble();
351 :
352 : // White Space ueberlesen
353 0 : while( ( ' ' == cNextCh ||
354 0 : (cNextCh >= 0x09 && cNextCh <= 0x0d) ) && !IsEOF() )
355 : {
356 0 : bWhiteSpace = sal_True;
357 0 : cNextCh = GetNextChar();
358 : }
359 :
360 : // und nun Schauen, ob es eine Einheit gibt
361 0 : switch( cNextCh )
362 : {
363 : case '%': // PERCENTAGE
364 0 : bWhiteSpace = sal_False;
365 0 : nRet = CSS1_PERCENTAGE;
366 0 : break;
367 :
368 : case 'c':
369 : case 'C': // LENGTH cm | LENGTH IDENT
370 : case 'e':
371 : case 'E': // LENGTH (em | ex) | LENGTH IDENT
372 : case 'i':
373 : case 'I': // LENGTH inch | LENGTH IDENT
374 : case 'p':
375 : case 'P': // LENGTH (pt | px | pc) | LENGTH IDENT
376 : case 'm':
377 : case 'M': // LENGTH mm | LENGTH IDENT
378 : {
379 : // die aktuelle Position retten
380 0 : sal_Int32 nInPosOld = nInPos;
381 0 : sal_Unicode cNextChOld = cNextCh;
382 0 : sal_uLong nlLineNrOld = nlLineNr;
383 0 : sal_uLong nlLinePosOld = nlLinePos;
384 0 : sal_Bool bEOFOld = bEOF;
385 :
386 : // den naechsten Identifer scannen
387 0 : OUString aIdent;
388 0 : OUStringBuffer sTmpBuffer2( 64L );
389 0 : do {
390 0 : sTmpBuffer2.append( cNextCh );
391 0 : cNextCh = GetNextChar();
392 0 : } while( (comphelper::string::isalnumAscii(cNextCh) ||
393 0 : '-' == cNextCh) && !IsEOF() );
394 :
395 0 : aIdent += sTmpBuffer2.makeStringAndClear();
396 :
397 : // Ist es eine Einheit?
398 0 : const sal_Char *pCmp1 = 0, *pCmp2 = 0, *pCmp3 = 0;
399 0 : double nScale1 = 1., nScale2 = 1.;
400 0 : CSS1Token nToken1 = CSS1_LENGTH,
401 0 : nToken2 = CSS1_LENGTH,
402 0 : nToken3 = CSS1_LENGTH;
403 0 : switch( aIdent[0] )
404 : {
405 : case 'c':
406 : case 'C':
407 0 : pCmp1 = "cm";
408 0 : nScale1 = (72.*20.)/2.54; // twip
409 0 : break;
410 : case 'e':
411 : case 'E':
412 0 : pCmp1 = "em";
413 0 : nToken1 = CSS1_EMS;
414 :
415 0 : pCmp2 = "ex";
416 0 : nToken2 = CSS1_EMX;
417 0 : break;
418 : case 'i':
419 : case 'I':
420 0 : pCmp1 = "in";
421 0 : nScale1 = 72.*20.; // twip
422 0 : break;
423 : case 'm':
424 : case 'M':
425 0 : pCmp1 = "mm";
426 0 : nScale1 = (72.*20.)/25.4; // twip
427 0 : break;
428 : case 'p':
429 : case 'P':
430 0 : pCmp1 = "pt";
431 0 : nScale1 = 20.; // twip
432 :
433 0 : pCmp2 = "pc";
434 0 : nScale2 = 12.*20.; // twip
435 :
436 0 : pCmp3 = "px";
437 0 : nToken3 = CSS1_PIXLENGTH;
438 0 : break;
439 : }
440 :
441 0 : double nScale = 0.0;
442 : OSL_ENSURE( pCmp1, "Wo kommt das erste Zeichen her?" );
443 0 : if( aIdent.equalsIgnoreAsciiCaseAscii( pCmp1 ) )
444 : {
445 0 : nScale = nScale1;
446 0 : nRet = nToken1;
447 : }
448 0 : else if( pCmp2 &&
449 0 : aIdent.equalsIgnoreAsciiCaseAscii( pCmp2 ) )
450 : {
451 0 : nScale = nScale2;
452 0 : nRet = nToken2;
453 : }
454 0 : else if( pCmp3 &&
455 0 : aIdent.equalsIgnoreAsciiCaseAscii( pCmp3 ) )
456 : {
457 0 : nScale = 1.; // nScale3
458 0 : nRet = nToken3;
459 : }
460 : else
461 : {
462 0 : nRet = CSS1_NUMBER;
463 : }
464 :
465 0 : if( CSS1_LENGTH==nRet && nScale!=1.0 )
466 0 : nValue *= nScale;
467 :
468 0 : if( nRet == CSS1_NUMBER )
469 : {
470 0 : nInPos = nInPosOld;
471 0 : cNextCh = cNextChOld;
472 0 : nlLineNr = nlLineNrOld;
473 0 : nlLinePos = nlLinePosOld;
474 0 : bEOF = bEOFOld;
475 : }
476 : else
477 : {
478 0 : bWhiteSpace = sal_False;
479 : }
480 0 : bNextCh = sal_False;
481 : }
482 0 : break;
483 : default: // NUMBER IDENT
484 0 : bNextCh = sal_False;
485 0 : nRet = CSS1_NUMBER;
486 0 : break;
487 0 : }
488 : }
489 0 : break;
490 :
491 : case ':': // ':'
492 : // link/visited/active abfangen !!!
493 0 : nRet = CSS1_COLON;
494 0 : break;
495 :
496 : case '.': // DOT_W_WS | DOT_WO_WS
497 0 : nRet = bPrevWhiteSpace ? CSS1_DOT_W_WS : CSS1_DOT_WO_WS;
498 0 : break;
499 :
500 : case '+': // '+'
501 0 : nRet = CSS1_PLUS;
502 0 : break;
503 :
504 : case '-': // '-'
505 0 : nRet = CSS1_MINUS;
506 0 : break;
507 :
508 : case '{': // '{'
509 0 : nRet = CSS1_OBRACE;
510 0 : break;
511 :
512 : case '}': // '}'
513 0 : nRet = CSS1_CBRACE;
514 0 : break;
515 :
516 : case ';': // ';'
517 0 : nRet = CSS1_SEMICOLON;
518 0 : break;
519 :
520 : case ',': // ','
521 0 : nRet = CSS1_COMMA;
522 0 : break;
523 :
524 : case '#': // '#'
525 0 : cNextCh = GetNextChar();
526 0 : if( ('0'<=cNextCh && '9'>=cNextCh) ||
527 0 : ('a'<=cNextCh && 'f'>=cNextCh) ||
528 0 : ('A'<=cNextCh && 'F'>=cNextCh) )
529 : {
530 : // die aktuelle Position retten
531 0 : sal_Int32 nInPosSave = nInPos;
532 0 : sal_Unicode cNextChSave = cNextCh;
533 0 : sal_uLong nlLineNrSave = nlLineNr;
534 0 : sal_uLong nlLinePosSave = nlLinePos;
535 0 : sal_Bool bEOFSave = bEOF;
536 :
537 : // erstmal versuchen eine Hex-Zahl zu scannen
538 0 : OUStringBuffer sTmpBuffer( 6L );
539 0 : do {
540 0 : sTmpBuffer.append( cNextCh );
541 0 : cNextCh = GetNextChar();
542 0 : } while( sTmpBuffer.getLength() < 7 &&
543 0 : ( ('0'<=cNextCh && '9'>=cNextCh) ||
544 0 : ('A'<=cNextCh && 'F'>=cNextCh) ||
545 0 : ('a'<=cNextCh && 'f'>=cNextCh) ) &&
546 0 : !IsEOF() );
547 :
548 0 : if( sTmpBuffer.getLength()==6 || sTmpBuffer.getLength()==3 )
549 : {
550 : // wir haben eine hexadezimale Farbe gefunden
551 0 : aToken += sTmpBuffer.makeStringAndClear();
552 0 : nRet = CSS1_HEXCOLOR;
553 0 : bNextCh = sal_False;
554 :
555 0 : break;
556 : }
557 :
558 : // sonst versuchen wir es mit einer Zahl
559 0 : nInPos = nInPosSave;
560 0 : cNextCh = cNextChSave;
561 0 : nlLineNr = nlLineNrSave;
562 0 : nlLinePos = nlLinePosSave;
563 0 : bEOF = bEOFSave;
564 : }
565 :
566 0 : nRet = CSS1_HASH;
567 0 : bNextCh = sal_False;
568 0 : break;
569 :
570 : case ' ':
571 : case '\t':
572 : case '\r':
573 : case '\n': // White-Space
574 0 : bWhiteSpace = sal_True;
575 0 : break;
576 :
577 : case (sal_Unicode)EOF:
578 0 : if( IsEOF() )
579 : {
580 0 : eState = CSS1_PAR_ACCEPTED;
581 0 : bNextCh = sal_False;
582 0 : break;
583 : }
584 : // no break
585 :
586 : default: // IDENT | syntax error
587 0 : if (comphelper::string::isalphaAscii(cNextCh))
588 : {
589 : // IDENT
590 :
591 0 : sal_Bool bHexColor = sal_True;
592 :
593 : // den naechsten Identifer scannen
594 0 : OUStringBuffer sTmpBuffer( 64L );
595 0 : do {
596 0 : sTmpBuffer.append( cNextCh );
597 0 : if( bHexColor )
598 : {
599 0 : bHexColor = sTmpBuffer.getLength()<7 &&
600 0 : ( ('0'<=cNextCh && '9'>=cNextCh) ||
601 0 : ('A'<=cNextCh && 'F'>=cNextCh) ||
602 0 : ('a'<=cNextCh && 'f'>=cNextCh) );
603 : }
604 0 : cNextCh = GetNextChar();
605 0 : } while( (comphelper::string::isalnumAscii(cNextCh) ||
606 0 : '-' == cNextCh) && !IsEOF() );
607 :
608 0 : aToken += sTmpBuffer.makeStringAndClear();
609 :
610 0 : if( bHexColor && sTmpBuffer.getLength()==6 )
611 : {
612 0 : bNextCh = sal_False;
613 0 : nRet = CSS1_HEXCOLOR;
614 :
615 0 : break;
616 : }
617 0 : if( '('==cNextCh &&
618 0 : ( (('u'==aToken[0] || 'U'==aToken[0]) &&
619 0 : aToken.equalsIgnoreAsciiCase( "url" )) ||
620 0 : (('r'==aToken[0] || 'R'==aToken[0]) &&
621 0 : aToken.equalsIgnoreAsciiCase( "rgb" )) ) )
622 : {
623 0 : sal_uInt16 nNestCnt = 0;
624 0 : OUStringBuffer sTmpBuffer2( 64L );
625 0 : do {
626 0 : sTmpBuffer2.append( cNextCh );
627 0 : switch( cNextCh )
628 : {
629 0 : case '(': nNestCnt++; break;
630 0 : case ')': nNestCnt--; break;
631 : }
632 0 : cNextCh = GetNextChar();
633 0 : } while( (nNestCnt>1 || ')'!=cNextCh) && !IsEOF() );
634 0 : sTmpBuffer2.append( cNextCh );
635 0 : aToken += sTmpBuffer2.makeStringAndClear();
636 0 : bNextCh = sal_True;
637 0 : nRet = 'u'==aToken[0] || 'U'==aToken[0]
638 : ? CSS1_URL
639 0 : : CSS1_RGB;
640 : }
641 : else
642 : {
643 0 : bNextCh = sal_False;
644 0 : nRet = CSS1_IDENT;
645 0 : }
646 : }
647 : // Fehlerbehandlung: Zeichen ignorieren
648 0 : break;
649 : }
650 0 : if( bNextCh )
651 0 : cNextCh = GetNextChar();
652 :
653 0 : } while( CSS1_NULL==nRet && IsParserWorking() );
654 :
655 0 : return nRet;
656 : }
657 :
658 : // Dies folegenden Funktionen realisieren den in
659 :
660 : // http://www.w3.orh/pub/WWW/TR/WD-css1.html
661 : // bzw. http://www.w3.orh/pub/WWW/TR/WD-css1-960220.html
662 :
663 : // beschriebenen Parser fuer CSS1. Es handelt sich um eine direkte
664 : // Umsetzung der dort beschriebenen Grammatik
665 :
666 : // stylesheet
667 : // : import* rule*
668 :
669 : // import
670 : // : IMPORT_SYM url
671 :
672 : // url
673 : // : STRING
674 :
675 0 : void CSS1Parser::ParseStyleSheet()
676 : {
677 0 : LOOP_CHECK_DECL
678 :
679 : // import*
680 0 : sal_Bool bDone = sal_False;
681 0 : while( !bDone && IsParserWorking() )
682 : {
683 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseStyleSheet()/import *" )
684 :
685 0 : switch( nToken )
686 : {
687 : case CSS1_IMPORT_SYM:
688 : // IMPORT_SYM url
689 : // url ueberspringen wir ungeprueft
690 0 : nToken = GetNextToken();
691 0 : break;
692 : case CSS1_IDENT: // Look-Aheads
693 : case CSS1_DOT_W_WS:
694 : case CSS1_HASH:
695 : // /Feature: PrintExt
696 : case CSS1_PAGE_SYM:
697 : // /Feature: PrintExt
698 : // rule
699 0 : bDone = sal_True;
700 0 : break;
701 : default:
702 : // Fehlerbehandlung: ueberlesen
703 0 : break;
704 : }
705 :
706 0 : if( !bDone )
707 0 : nToken = GetNextToken();
708 : }
709 :
710 0 : LOOP_CHECK_RESTART
711 :
712 : // rule *
713 0 : while( IsParserWorking() )
714 : {
715 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseStyleSheet()/rule *" )
716 :
717 0 : switch( nToken )
718 : {
719 : case CSS1_IDENT: // Look-Aheads
720 : case CSS1_DOT_W_WS:
721 : case CSS1_HASH:
722 : // /Feature: PrintExt
723 : case CSS1_PAGE_SYM:
724 : // /Feature: PrintExt
725 : // rule
726 0 : ParseRule();
727 0 : break;
728 : default:
729 : // Fehlerbehandlung: ueberlesen
730 0 : nToken = GetNextToken();
731 0 : break;
732 : }
733 : }
734 0 : }
735 :
736 : // rule
737 : // : selector [ ',' selector ]*
738 : // '{' declaration [ ';' declaration ]* '}'
739 :
740 0 : void CSS1Parser::ParseRule()
741 : {
742 : // selector
743 0 : CSS1Selector *pSelector = ParseSelector();
744 0 : if( !pSelector )
745 0 : return;
746 :
747 : // Selektor verarbeiten
748 0 : if( SelectorParsed( pSelector, true ) )
749 0 : delete pSelector;
750 :
751 0 : LOOP_CHECK_DECL
752 :
753 : // [ ',' selector ]*
754 0 : while( CSS1_COMMA==nToken && IsParserWorking() )
755 : {
756 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseRule()/selector *" )
757 :
758 : // ',' ueberelesen
759 0 : nToken = GetNextToken();
760 :
761 : // selector
762 0 : pSelector = ParseSelector();
763 0 : if( !pSelector )
764 0 : return;
765 :
766 : // Selektor verarbeiten
767 0 : if( SelectorParsed( pSelector, false ) )
768 0 : delete pSelector;
769 : }
770 :
771 : // '{'
772 0 : if( CSS1_OBRACE != nToken )
773 0 : return;
774 0 : nToken = GetNextToken();
775 :
776 : // declaration
777 0 : OUString aProperty;
778 0 : CSS1Expression *pExpr = ParseDeclaration( aProperty );
779 0 : if( !pExpr )
780 0 : return;
781 :
782 : // expression verarbeiten
783 0 : if( DeclarationParsed( aProperty, pExpr ) )
784 0 : delete pExpr;
785 :
786 0 : LOOP_CHECK_RESTART
787 :
788 : // [ ';' declaration ]*
789 0 : while( CSS1_SEMICOLON==nToken && IsParserWorking() )
790 : {
791 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseRule()/declaration *" )
792 :
793 : // ';'
794 0 : nToken = GetNextToken();
795 :
796 : // declaration
797 0 : if( CSS1_IDENT == nToken )
798 : {
799 0 : CSS1Expression *pExp = ParseDeclaration( aProperty );
800 0 : if( pExp )
801 : {
802 : // expression verarbeiten
803 0 : if( DeclarationParsed( aProperty, pExp ) )
804 0 : delete pExp;
805 : }
806 : }
807 : }
808 :
809 : // '}'
810 0 : if( CSS1_CBRACE == nToken )
811 0 : nToken = GetNextToken();
812 : }
813 :
814 : // selector
815 : // : simple_selector+ [ ':' pseudo_element ]?
816 :
817 : // simple_selector
818 : // : element_name [ DOT_WO_WS class ]?
819 : // | DOT_W_WS class
820 : // | id_selector
821 :
822 : // element_name
823 : // : IDENT
824 :
825 : // class
826 : // : IDENT
827 :
828 : // id_selector
829 : // : '#' IDENT
830 :
831 : // pseude_element
832 : // : IDENT
833 :
834 0 : CSS1Selector *CSS1Parser::ParseSelector()
835 : {
836 0 : CSS1Selector *pRoot = 0, *pLast = 0;
837 :
838 0 : sal_Bool bDone = sal_False;
839 0 : CSS1Selector *pNew = 0;
840 :
841 0 : LOOP_CHECK_DECL
842 :
843 : // simple_selector+
844 0 : while( !bDone && IsParserWorking() )
845 : {
846 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseSelector()" )
847 :
848 0 : sal_Bool bNextToken = sal_True;
849 :
850 0 : switch( nToken )
851 : {
852 : case CSS1_IDENT:
853 : {
854 : // element_name [ DOT_WO_WS class ]?
855 :
856 : // element_name
857 0 : OUString aElement = aToken;
858 0 : CSS1SelectorType eType = CSS1_SELTYPE_ELEMENT;
859 0 : nToken = GetNextToken();
860 :
861 0 : if( CSS1_DOT_WO_WS == nToken )
862 : {
863 : // DOT_WO_WS
864 0 : nToken = GetNextToken();
865 :
866 : // class
867 0 : if( CSS1_IDENT == nToken )
868 : {
869 0 : aElement += "." + aToken;
870 0 : eType = CSS1_SELTYPE_ELEM_CLASS;
871 : }
872 : else
873 : {
874 : // class fehlt
875 0 : return pRoot;
876 : }
877 : }
878 : else
879 : {
880 : // das war jetzt ein Look-Ahead
881 0 : bNextToken = sal_False;
882 : }
883 0 : pNew = new CSS1Selector( eType, aElement );
884 : }
885 0 : break;
886 : case CSS1_DOT_W_WS:
887 : // DOT_W_WS class
888 :
889 : // DOT_W_WS
890 0 : nToken = GetNextToken();
891 :
892 0 : if( CSS1_IDENT==nToken )
893 : {
894 : // class
895 0 : pNew = new CSS1Selector( CSS1_SELTYPE_CLASS, aToken );
896 : }
897 : else
898 : {
899 : // class fehlt
900 0 : return pRoot;
901 : }
902 0 : break;
903 : case CSS1_HASH:
904 : // '#' id_selector
905 :
906 : // '#'
907 0 : nToken = GetNextToken();
908 :
909 0 : if( CSS1_IDENT==nToken )
910 : {
911 : // id_selector
912 0 : pNew = new CSS1Selector( CSS1_SELTYPE_ID, aToken );
913 : }
914 : else
915 : {
916 : // id_selector fehlt
917 0 : return pRoot;
918 : }
919 0 : break;
920 :
921 : // /Feature: PrintExt
922 : case CSS1_PAGE_SYM:
923 : {
924 : // @page
925 0 : pNew = new CSS1Selector( CSS1_SELTYPE_PAGE, aToken );
926 : }
927 0 : break;
928 : // /Feature: PrintExt
929 :
930 : default:
931 : // wir wissen nicht was kommt, also aufhoehren
932 0 : bDone = sal_True;
933 0 : break;
934 : }
935 :
936 : // falls ein Selektor angelegt wurd, ihn speichern
937 0 : if( pNew )
938 : {
939 : OSL_ENSURE( (pRoot!=0) == (pLast!=0),
940 : "Root-Selektor, aber kein Last" );
941 0 : if( pLast )
942 0 : pLast->SetNext( pNew );
943 : else
944 0 : pRoot = pNew;
945 :
946 0 : pLast = pNew;
947 0 : pNew = 0;
948 : }
949 :
950 0 : if( bNextToken && !bDone )
951 0 : nToken = GetNextToken();
952 : }
953 :
954 0 : if( !pRoot )
955 : {
956 : // simple_selector fehlt
957 0 : return pRoot;
958 : }
959 :
960 : // [ ':' pseudo_element ]?
961 0 : if( CSS1_COLON==nToken && IsParserWorking() )
962 : {
963 : // ':' pseudo element
964 0 : nToken = GetNextToken();
965 0 : if( CSS1_IDENT==nToken )
966 : {
967 0 : pLast->SetNext( new CSS1Selector(CSS1_SELTYPE_PSEUDO,aToken) );
968 0 : nToken = GetNextToken();
969 : }
970 : else
971 : {
972 : // pseudo_element fehlt
973 0 : return pRoot;
974 : }
975 : }
976 :
977 0 : return pRoot;
978 : }
979 :
980 : // declaration
981 : // : property ':' expr prio?
982 : // | /* empty */
983 :
984 : // expression
985 : // : term [ operator term ]*
986 :
987 : // term
988 : // : unary_operator?
989 : // [ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS | IDENT |
990 : // HEXCOLOR | URL | RGB ]
991 :
992 : // operator
993 : // : '/' | ',' | /* empty */
994 :
995 : // unary_operator
996 : // : '-' | '+'
997 :
998 : // property
999 : // : ident
1000 :
1001 : // das Vorzeichen wird nur fuer numerische Werte (ausser PERCENTAGE)
1002 : // beruecksichtigt und wird auf nValue angewendet!
1003 0 : CSS1Expression *CSS1Parser::ParseDeclaration( OUString& rProperty )
1004 : {
1005 0 : CSS1Expression *pRoot = 0, *pLast = 0;
1006 :
1007 : // property
1008 0 : if( CSS1_IDENT != nToken )
1009 : {
1010 : // property fehlt
1011 0 : return pRoot;
1012 : }
1013 0 : rProperty = aToken;
1014 :
1015 0 : nToken = GetNextToken();
1016 :
1017 : // ':'
1018 0 : if( CSS1_COLON != nToken )
1019 : {
1020 : // ':' fehlt
1021 0 : return pRoot;
1022 : }
1023 0 : nToken = GetNextToken();
1024 :
1025 : // term [operator term]*
1026 : // hier sind wir sehr lax, was die Syntax angeht, sollte aber kein
1027 : // Problem sein
1028 0 : sal_Bool bDone = sal_False;
1029 0 : sal_Unicode cSign = 0, cOp = 0;
1030 0 : CSS1Expression *pNew = 0;
1031 :
1032 0 : LOOP_CHECK_DECL
1033 :
1034 0 : while( !bDone && IsParserWorking() )
1035 : {
1036 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseDeclaration()" )
1037 :
1038 0 : switch( nToken )
1039 : {
1040 : case CSS1_MINUS:
1041 0 : cSign = '-';
1042 0 : break;
1043 :
1044 : case CSS1_PLUS:
1045 0 : cSign = '+';
1046 0 : break;
1047 :
1048 : case CSS1_NUMBER:
1049 : case CSS1_LENGTH:
1050 : case CSS1_PIXLENGTH:
1051 : case CSS1_EMS:
1052 : case CSS1_EMX:
1053 0 : if( '-'==cSign )
1054 0 : nValue = -nValue;
1055 : case CSS1_STRING:
1056 : case CSS1_PERCENTAGE:
1057 : case CSS1_IDENT:
1058 : case CSS1_URL:
1059 : case CSS1_RGB:
1060 : case CSS1_HEXCOLOR:
1061 0 : pNew = new CSS1Expression( nToken, aToken, nValue, cOp );
1062 0 : nValue = 0; // sonst landet das auch im naechsten Ident
1063 0 : cSign = 0;
1064 0 : cOp = 0;
1065 0 : break;
1066 :
1067 : case CSS1_SLASH:
1068 0 : cOp = '/';
1069 0 : cSign = 0;
1070 0 : break;
1071 :
1072 : case CSS1_COMMA:
1073 0 : cOp = ',';
1074 0 : cSign = 0;
1075 0 : break;
1076 :
1077 : default:
1078 0 : bDone = sal_True;
1079 0 : break;
1080 : }
1081 :
1082 : // falls ein Expression angelegt wurde, diesen speichern
1083 0 : if( pNew )
1084 : {
1085 : OSL_ENSURE( (pRoot!=0) == (pLast!=0),
1086 : "Root-Selektor, aber kein Last" );
1087 0 : if( pLast )
1088 0 : pLast->SetNext( pNew );
1089 : else
1090 0 : pRoot = pNew;
1091 :
1092 0 : pLast = pNew;
1093 0 : pNew = 0;
1094 : }
1095 :
1096 0 : if( !bDone )
1097 0 : nToken = GetNextToken();
1098 : }
1099 :
1100 0 : if( !pRoot )
1101 : {
1102 : // term fehlt
1103 0 : return pRoot;
1104 : }
1105 :
1106 : // prio?
1107 0 : if( CSS1_IMPORTANT_SYM==nToken )
1108 : {
1109 : // IMPORTANT_SYM
1110 0 : nToken = GetNextToken();
1111 : }
1112 :
1113 0 : return pRoot;
1114 : }
1115 :
1116 0 : CSS1Parser::CSS1Parser()
1117 : : nValue(0)
1118 : , eState(CSS1_PAR_ACCEPTED)
1119 0 : , nToken(CSS1_NULL)
1120 : {
1121 0 : }
1122 :
1123 0 : CSS1Parser::~CSS1Parser()
1124 : {
1125 0 : }
1126 :
1127 0 : sal_Bool CSS1Parser::ParseStyleSheet( const OUString& rIn )
1128 : {
1129 0 : OUString aTmp( rIn );
1130 :
1131 : sal_Unicode c;
1132 0 : while( !aTmp.isEmpty() &&
1133 0 : ( ' '==(c=aTmp[0]) || '\t'==c || '\r'==c || '\n'==c ) )
1134 0 : aTmp = aTmp.copy( 1, aTmp.getLength() - 1 );
1135 :
1136 0 : while( !aTmp.isEmpty() && ( ' '==(c=aTmp[aTmp.getLength()-1])
1137 0 : || '\t'==c || '\r'==c || '\n'==c ) )
1138 0 : aTmp = aTmp.copy( 0, aTmp.getLength()-1 );
1139 :
1140 : // SGML-Kommentare entfernen
1141 0 : if( aTmp.getLength() >= 4 &&
1142 0 : aTmp.startsWith( "<!--" ) )
1143 0 : aTmp = aTmp.copy( 4, aTmp.getLength() - 4 );
1144 :
1145 0 : if( aTmp.getLength() >=3 &&
1146 0 : aTmp.endsWith("-->") )
1147 0 : aTmp = aTmp.copy( 0, aTmp.getLength() - 3 );
1148 :
1149 0 : if( aTmp.isEmpty() )
1150 0 : return sal_True;
1151 :
1152 0 : InitRead( aTmp );
1153 :
1154 0 : ParseStyleSheet();
1155 :
1156 0 : return sal_True;
1157 : }
1158 :
1159 0 : sal_Bool CSS1Parser::ParseStyleOption( const OUString& rIn )
1160 : {
1161 0 : if( rIn.isEmpty() )
1162 0 : return sal_True;
1163 :
1164 0 : InitRead( rIn );
1165 :
1166 : // fdo#41796: skip over spurious semicolons
1167 0 : while (CSS1_SEMICOLON == nToken)
1168 : {
1169 0 : nToken = GetNextToken();
1170 : }
1171 :
1172 0 : OUString aProperty;
1173 0 : CSS1Expression *pExpr = ParseDeclaration( aProperty );
1174 0 : if( !pExpr )
1175 : {
1176 0 : return sal_False;
1177 : }
1178 :
1179 : // expression verarbeiten
1180 0 : if( DeclarationParsed( aProperty, pExpr ) )
1181 0 : delete pExpr;
1182 :
1183 0 : LOOP_CHECK_DECL
1184 :
1185 : // [ ';' declaration ]*
1186 0 : while( CSS1_SEMICOLON==nToken && IsParserWorking() )
1187 : {
1188 0 : LOOP_CHECK_CHECK( "Endlos-Schleife in ParseStyleOption()" )
1189 :
1190 0 : nToken = GetNextToken();
1191 0 : if( CSS1_IDENT==nToken )
1192 : {
1193 0 : CSS1Expression *pExp = ParseDeclaration( aProperty );
1194 0 : if( pExp )
1195 : {
1196 : // expression verarbeiten
1197 0 : if( DeclarationParsed( aProperty, pExp ) )
1198 0 : delete pExp;
1199 : }
1200 : }
1201 : }
1202 :
1203 0 : return sal_True;
1204 : }
1205 :
1206 0 : bool CSS1Parser::SelectorParsed( CSS1Selector* /* pSelector */, bool /*bFirst*/ )
1207 : {
1208 : // Selektor loeschen
1209 0 : return true;
1210 : }
1211 :
1212 0 : sal_Bool CSS1Parser::DeclarationParsed( const OUString& /*rProperty*/,
1213 : const CSS1Expression * /* pExpr */ )
1214 : {
1215 : // Deklaration loeschen
1216 0 : return sal_True;
1217 : }
1218 :
1219 0 : CSS1Selector::~CSS1Selector()
1220 : {
1221 0 : delete pNext;
1222 0 : }
1223 :
1224 0 : CSS1Expression::~CSS1Expression()
1225 : {
1226 0 : delete pNext;
1227 0 : }
1228 :
1229 0 : sal_Bool CSS1Expression::GetURL( OUString& rURL ) const
1230 : {
1231 : OSL_ENSURE( CSS1_URL==eType, "CSS1-Ausruck ist keine Farbe URL" );
1232 :
1233 : OSL_ENSURE( aValue.startsWithIgnoreAsciiCase( "url" ) &&
1234 : aValue.getLength() > 5 &&
1235 : '(' == aValue[3] &&
1236 : ')' == aValue[aValue.getLength()-1],
1237 : "keine gueltiges URL(...)" );
1238 :
1239 0 : sal_Bool bRet = sal_False;
1240 :
1241 0 : if( aValue.getLength() > 5 )
1242 : {
1243 0 : rURL = aValue.copy( 4, aValue.getLength() - 5 );
1244 0 : rURL = comphelper::string::strip(rURL, ' ');
1245 0 : bRet = sal_True;
1246 : }
1247 :
1248 0 : return bRet;
1249 : }
1250 :
1251 0 : sal_Bool CSS1Expression::GetColor( Color &rColor ) const
1252 : {
1253 : OSL_ENSURE( CSS1_IDENT==eType || CSS1_RGB==eType ||
1254 : CSS1_HEXCOLOR==eType || CSS1_STRING==eType,
1255 : "CSS1-Ausruck kann keine Farbe sein" );
1256 :
1257 0 : sal_Bool bRet = sal_False;
1258 0 : sal_uInt32 nColor = SAL_MAX_UINT32;
1259 :
1260 0 : switch( eType )
1261 : {
1262 : case CSS1_RGB:
1263 : {
1264 0 : sal_uInt8 aColors[3] = { 0, 0, 0 };
1265 :
1266 0 : if (!aValue.startsWithIgnoreAsciiCase( "rgb" ) || aValue.getLength() < 6 ||
1267 0 : aValue[3] != '(' || aValue[aValue.getLength()-1] != ')')
1268 : {
1269 0 : break;
1270 : }
1271 :
1272 0 : OUString aColorStr(aValue.copy(4, aValue.getLength() - 5));
1273 :
1274 0 : sal_Int32 nPos = 0;
1275 0 : sal_uInt16 nCol = 0;
1276 :
1277 0 : while( nCol < 3 && nPos < aColorStr.getLength() )
1278 : {
1279 : sal_Unicode c;
1280 0 : while( nPos < aColorStr.getLength() &&
1281 0 : ((c=aColorStr[nPos]) == ' ' || c == '\t' ||
1282 0 : c == '\n' || c== '\r' ) )
1283 0 : nPos++;
1284 :
1285 0 : sal_Int32 nEnd = aColorStr.indexOf( ',', nPos );
1286 0 : OUString aNumber;
1287 0 : if( nEnd == -1 )
1288 : {
1289 0 : aNumber = aColorStr.copy(nPos);
1290 0 : nPos = aColorStr.getLength();
1291 : }
1292 : else
1293 : {
1294 0 : aNumber = aColorStr.copy( nPos, nEnd-nPos );
1295 0 : nPos = nEnd+1;
1296 : }
1297 :
1298 0 : sal_uInt16 nNumber = (sal_uInt16)aNumber.toInt32();
1299 0 : if( aNumber.indexOf('%') >= 0 )
1300 : {
1301 0 : if( nNumber > 100 )
1302 0 : nNumber = 100;
1303 0 : nNumber *= 255;
1304 0 : nNumber /= 100;
1305 : }
1306 0 : else if( nNumber > 255 )
1307 0 : nNumber = 255;
1308 :
1309 0 : aColors[nCol] = (sal_uInt8)nNumber;
1310 0 : nCol ++;
1311 0 : }
1312 :
1313 0 : rColor.SetRed( aColors[0] );
1314 0 : rColor.SetGreen( aColors[1] );
1315 0 : rColor.SetBlue( aColors[2] );
1316 :
1317 0 : bRet = sal_True; // etwas anderes als eine Farbe kann es nicht sein
1318 : }
1319 0 : break;
1320 :
1321 : case CSS1_IDENT:
1322 : case CSS1_STRING:
1323 : {
1324 0 : OUString aTmp( aValue.toAsciiUpperCase() );
1325 0 : nColor = GetHTMLColor( aTmp );
1326 0 : bRet = nColor != SAL_MAX_UINT32;
1327 : }
1328 0 : if( bRet || CSS1_STRING != eType || aValue.isEmpty() ||
1329 0 : aValue[0] != '#' )
1330 0 : break;
1331 :
1332 : case CSS1_HEXCOLOR:
1333 : {
1334 : // HACK fuer MS-IE: DIe Farbe kann auch in einem String stehen
1335 0 : sal_Int32 nOffset = CSS1_STRING==eType ? 1 : 0;
1336 0 : sal_Bool bDouble = aValue.getLength()-nOffset == 3;
1337 0 : sal_Int32 i = nOffset, nEnd = (bDouble ? 3 : 6) + nOffset;
1338 :
1339 0 : nColor = 0;
1340 0 : for( ; i<nEnd; i++ )
1341 : {
1342 0 : sal_Unicode c = (i<aValue.getLength() ? aValue[i]
1343 0 : : '0' );
1344 0 : if( c >= '0' && c <= '9' )
1345 0 : c -= 48;
1346 0 : else if( c >= 'A' && c <= 'F' )
1347 0 : c -= 55;
1348 0 : else if( c >= 'a' && c <= 'f' )
1349 0 : c -= 87;
1350 : else
1351 0 : c = 16;
1352 :
1353 0 : nColor *= 16;
1354 0 : if( c<16 )
1355 0 : nColor += c;
1356 0 : if( bDouble )
1357 : {
1358 0 : nColor *= 16;
1359 0 : if( c<16 )
1360 0 : nColor += c;
1361 : }
1362 : }
1363 0 : bRet = sal_True;
1364 : }
1365 0 : break;
1366 : default:
1367 : ;
1368 : }
1369 :
1370 0 : if( bRet && nColor!=SAL_MAX_UINT32 )
1371 : {
1372 0 : rColor.SetRed( (sal_uInt8)((nColor & 0x00ff0000UL) >> 16) );
1373 0 : rColor.SetGreen( (sal_uInt8)((nColor & 0x0000ff00UL) >> 8) );
1374 0 : rColor.SetBlue( (sal_uInt8)(nColor & 0x000000ffUL) );
1375 : }
1376 :
1377 0 : return bRet;
1378 : }
1379 :
1380 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|