1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <o3tl/string_view.hxx>
21 #include <osl/diagnose.h>
22 #include <rtl/character.hxx>
23 #include <rtl/ustrbuf.hxx>
24 #include <tools/color.hxx>
25 #include <tools/solar.h>
26 #include <svtools/htmltokn.h>
27 #include <comphelper/string.hxx>
28 #include "parcss1.hxx"
30 // Loop-Check: Used to avoid infinite loops, is checked after every
31 // loop, if there is progress of the input position
36 #define LOOP_CHECK_DECL \
37 sal_Int32 nOldInPos = SAL_MAX_INT32;
38 #define LOOP_CHECK_RESTART \
39 nOldInPos = SAL_MAX_INT32;
40 #define LOOP_CHECK_CHECK( where ) \
41 OSL_ENSURE( nOldInPos!=m_nInPos || m_cNextCh==sal_Unicode(EOF), where ); \
42 if( nOldInPos==m_nInPos && m_cNextCh!=sal_Unicode(EOF) ) \
49 #define LOOP_CHECK_DECL
50 #define LOOP_CHECK_RESTART
51 #define LOOP_CHECK_CHECK( where )
55 const sal_Int32 MAX_LEN
= 1024;
57 void CSS1Parser::InitRead( const OUString
& rIn
)
62 m_bWhiteSpace
= true; // if nothing was read it's like there was WS
64 m_eState
= CSS1_PAR_WORKING
;
69 m_cNextCh
= GetNextChar();
70 m_nToken
= GetNextToken();
73 sal_Unicode
CSS1Parser::GetNextChar()
75 if( m_nInPos
>= m_aIn
.getLength() )
78 return sal_Unicode(EOF
);
81 sal_Unicode c
= m_aIn
[m_nInPos
];
95 // This function implements the scanner described in
97 // http://www.w3.org/pub/WWW/TR/WD-css1.html
98 // resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html
100 // for CSS1. It's a direct implementation of the
101 // described Lex grammar.
103 CSS1Token
CSS1Parser::GetNextToken()
105 CSS1Token nRet
= CSS1_NULL
;
109 // remember if white space was read
110 bool bPrevWhiteSpace
= m_bWhiteSpace
;
111 m_bWhiteSpace
= false;
116 case '/': // COMMENT | '/'
118 m_cNextCh
= GetNextChar();
119 if( '*' == m_cNextCh
)
122 m_cNextCh
= GetNextChar();
124 bool bAsterisk
= false;
125 while( !(bAsterisk
&& '/'==m_cNextCh
) && !IsEOF() )
127 bAsterisk
= ('*'==m_cNextCh
);
128 m_cNextCh
= GetNextChar();
140 case '@': // '@import' | '@XXX'
142 m_cNextCh
= GetNextChar();
143 if (rtl::isAsciiAlpha(m_cNextCh
))
145 // scan the next identifier
146 OUStringBuffer
sTmpBuffer(32);
148 sTmpBuffer
.append( m_cNextCh
);
149 m_cNextCh
= GetNextChar();
150 } while( (rtl::isAsciiAlphanumeric(m_cNextCh
) ||
151 '-' == m_cNextCh
) && !IsEOF() );
153 m_aToken
+= sTmpBuffer
;
155 // check if we know it
156 switch( m_aToken
[0] )
160 if( m_aToken
.equalsIgnoreAsciiCase( "import" ) )
161 nRet
= CSS1_IMPORT_SYM
;
165 if( m_aToken
.equalsIgnoreAsciiCase( "page" ) )
166 nRet
= CSS1_PAGE_SYM
;
170 // error handling: ignore '@indent' and the rest until
171 // semicolon at end of the next block
172 if( CSS1_NULL
==nRet
)
176 sal_Unicode cQuoteCh
= 0;
177 bool bDone
= false, bEscape
= false;
178 while( !bDone
&& !IsEOF() )
180 bool bOldEscape
= bEscape
;
185 if( !cQuoteCh
&& !bOldEscape
)
189 if( !cQuoteCh
&& !bOldEscape
)
190 bDone
= nBlockLvl
==0;
193 if( !cQuoteCh
&& !bOldEscape
)
194 bDone
= --nBlockLvl
==0;
202 if( cQuoteCh
== m_cNextCh
)
207 cQuoteCh
= m_cNextCh
;
216 m_cNextCh
= GetNextChar();
225 case '!': // '!' 'legal' | '!' 'important' | syntax error
227 // ignore white space
228 m_cNextCh
= GetNextChar();
229 while( ( ' ' == m_cNextCh
||
230 (m_cNextCh
>= 0x09 && m_cNextCh
<= 0x0d) ) && !IsEOF() )
232 m_bWhiteSpace
= true;
233 m_cNextCh
= GetNextChar();
236 if( 'i'==m_cNextCh
|| 'I'==m_cNextCh
)
238 // scan next identifier
239 OUStringBuffer
sTmpBuffer(32);
241 sTmpBuffer
.append( m_cNextCh
);
242 m_cNextCh
= GetNextChar();
243 } while( (rtl::isAsciiAlphanumeric(m_cNextCh
) ||
244 '-' == m_cNextCh
) && !IsEOF() );
246 m_aToken
+= sTmpBuffer
;
248 if( ( 'i'==m_aToken
[0] || 'I'==m_aToken
[0] ) &&
249 m_aToken
.equalsIgnoreAsciiCase( "important" ) )
252 nRet
= CSS1_IMPORTANT_SYM
;
256 // error handling: ignore '!', not IDENT
260 m_bWhiteSpace
= false;
265 // error handling: ignore '!'
274 // \... isn't possible yet!!!
275 sal_Unicode cQuoteChar
= m_cNextCh
;
276 m_cNextCh
= GetNextChar();
278 OUStringBuffer
sTmpBuffer( MAX_LEN
);
280 sTmpBuffer
.append( m_cNextCh
);
281 m_cNextCh
= GetNextChar();
282 } while( cQuoteChar
!= m_cNextCh
&& !IsEOF() );
284 m_aToken
+= sTmpBuffer
;
299 case '9': // NUMBER | PERCENTAGE | LENGTH
301 // save current position
302 std::size_t nInPosSave
= m_nInPos
;
303 sal_Unicode cNextChSave
= m_cNextCh
;
304 sal_uInt32 nlLineNrSave
= m_nlLineNr
;
305 sal_uInt32 nlLinePosSave
= m_nlLinePos
;
306 bool bEOFSave
= m_bEOF
;
308 // first try to parse a hex digit
309 OUStringBuffer
sTmpBuffer( 16 );
311 sTmpBuffer
.append( m_cNextCh
);
312 m_cNextCh
= GetNextChar();
313 } while( sTmpBuffer
.getLength() < 7 &&
314 ( ('0'<=m_cNextCh
&& '9'>=m_cNextCh
) ||
315 ('A'<=m_cNextCh
&& 'F'>=m_cNextCh
) ||
316 ('a'<=m_cNextCh
&& 'f'>=m_cNextCh
) ) &&
319 if( sTmpBuffer
.getLength()==6 )
321 // we found a color in hex
322 m_aToken
+= sTmpBuffer
;
323 nRet
= CSS1_HEXCOLOR
;
329 // otherwise we try a number
330 m_nInPos
= nInPosSave
;
331 m_cNextCh
= cNextChSave
;
332 m_nlLineNr
= nlLineNrSave
;
333 m_nlLinePos
= nlLinePosSave
;
336 // first parse the number
337 sTmpBuffer
.setLength( 0 );
339 sTmpBuffer
.append( m_cNextCh
);
340 m_cNextCh
= GetNextChar();
341 } while( (('0'<=m_cNextCh
&& '9'>=m_cNextCh
) || '.'==m_cNextCh
) &&
344 m_aToken
+= sTmpBuffer
;
345 m_nValue
= m_aToken
.toDouble();
347 // ignore white space
348 while( ( ' ' == m_cNextCh
||
349 (m_cNextCh
>= 0x09 && m_cNextCh
<= 0x0d) ) && !IsEOF() )
351 m_bWhiteSpace
= true;
352 m_cNextCh
= GetNextChar();
355 // check now, of there is a unit
358 case '%': // PERCENTAGE
359 m_bWhiteSpace
= false;
360 nRet
= CSS1_PERCENTAGE
;
364 case 'C': // LENGTH cm | LENGTH IDENT
366 case 'E': // LENGTH (em | ex) | LENGTH IDENT
368 case 'I': // LENGTH inch | LENGTH IDENT
370 case 'P': // LENGTH (pt | px | pc) | LENGTH IDENT
372 case 'M': // LENGTH mm | LENGTH IDENT
374 // save current position
375 sal_Int32 nInPosOld
= m_nInPos
;
376 sal_Unicode cNextChOld
= m_cNextCh
;
377 sal_uInt32 nlLineNrOld
= m_nlLineNr
;
378 sal_uInt32 nlLinePosOld
= m_nlLinePos
;
379 bool bEOFOld
= m_bEOF
;
381 // parse the next identifier
383 OUStringBuffer
sTmpBuffer2(64);
385 sTmpBuffer2
.append( m_cNextCh
);
386 m_cNextCh
= GetNextChar();
387 } while( (rtl::isAsciiAlphanumeric(m_cNextCh
) ||
388 '-' == m_cNextCh
) && !IsEOF() );
390 aIdent
+= sTmpBuffer2
;
393 const char *pCmp1
= nullptr, *pCmp2
= nullptr, *pCmp3
= nullptr;
394 double nScale1
= 1., nScale2
= 1.;
395 CSS1Token nToken1
= CSS1_LENGTH
,
396 nToken2
= CSS1_LENGTH
,
397 nToken3
= CSS1_LENGTH
;
403 nScale1
= (72.*20.)/2.54; // twip
416 nScale1
= 72.*20.; // twip
421 nScale1
= (72.*20.)/25.4; // twip
426 nScale1
= 20.; // twip
429 nScale2
= 12.*20.; // twip
432 nToken3
= CSS1_PIXLENGTH
;
437 OSL_ENSURE( pCmp1
, "Where does the first digit come from?" );
438 if( aIdent
.equalsIgnoreAsciiCaseAscii( pCmp1
) )
444 aIdent
.equalsIgnoreAsciiCaseAscii( pCmp2
) )
450 aIdent
.equalsIgnoreAsciiCaseAscii( pCmp3
) )
452 nScale
= 1.; // nScale3
460 if( CSS1_LENGTH
==nRet
&& nScale
!=1.0 )
463 if( nRet
== CSS1_NUMBER
)
465 m_nInPos
= nInPosOld
;
466 m_cNextCh
= cNextChOld
;
467 m_nlLineNr
= nlLineNrOld
;
468 m_nlLinePos
= nlLinePosOld
;
473 m_bWhiteSpace
= false;
478 default: // NUMBER IDENT
487 // catch link/visited/active !!!
491 case '.': // DOT_W_WS | DOT_WO_WS
492 nRet
= bPrevWhiteSpace
? CSS1_DOT_W_WS
: CSS1_DOT_WO_WS
;
512 nRet
= CSS1_SEMICOLON
;
520 m_cNextCh
= GetNextChar();
521 if( ('0'<=m_cNextCh
&& '9'>=m_cNextCh
) ||
522 ('a'<=m_cNextCh
&& 'f'>=m_cNextCh
) ||
523 ('A'<=m_cNextCh
&& 'F'>=m_cNextCh
) )
525 // save current position
526 sal_Int32 nInPosSave
= m_nInPos
;
527 sal_Unicode cNextChSave
= m_cNextCh
;
528 sal_uInt32 nlLineNrSave
= m_nlLineNr
;
529 sal_uInt32 nlLinePosSave
= m_nlLinePos
;
530 bool bEOFSave
= m_bEOF
;
532 // first try to parse a hex digit
533 OUStringBuffer
sTmpBuffer(8);
535 sTmpBuffer
.append( m_cNextCh
);
536 m_cNextCh
= GetNextChar();
537 } while( sTmpBuffer
.getLength() < 9 &&
538 ( ('0'<=m_cNextCh
&& '9'>=m_cNextCh
) ||
539 ('A'<=m_cNextCh
&& 'F'>=m_cNextCh
) ||
540 ('a'<=m_cNextCh
&& 'f'>=m_cNextCh
) ) &&
543 if( sTmpBuffer
.getLength()==6 || sTmpBuffer
.getLength()==3 )
545 // we found a color in hex (RGB)
546 m_aToken
+= sTmpBuffer
;
547 nRet
= CSS1_HEXCOLOR
;
553 if( sTmpBuffer
.getLength()==8 )
555 // we found a color in hex (RGBA)
556 // we convert it to RGB assuming white background
557 sal_uInt32 nColor
= sTmpBuffer
.makeStringAndClear().toUInt32(16);
558 sal_uInt32 nRed
= (nColor
& 0xff000000) >> 24;
559 sal_uInt32 nGreen
= (nColor
& 0xff0000) >> 16;
560 sal_uInt32 nBlue
= (nColor
& 0xff00) >> 8;
561 double nAlpha
= (nColor
& 0xff) / 255.0;
562 nRed
= (1 - nAlpha
) * 255 + nAlpha
* nRed
;
563 nGreen
= (1 - nAlpha
) * 255 + nAlpha
* nGreen
;
564 nBlue
= (1 - nAlpha
) * 255 + nAlpha
* nBlue
;
565 nColor
= (nRed
<< 16) + (nGreen
<< 8) + nBlue
;
566 m_aToken
+= OUString::number(nColor
, 16);
567 nRet
= CSS1_HEXCOLOR
;
573 // otherwise we try a number
574 m_nInPos
= nInPosSave
;
575 m_cNextCh
= cNextChSave
;
576 m_nlLineNr
= nlLineNrSave
;
577 m_nlLinePos
= nlLinePosSave
;
588 case '\n': // White-Space
589 m_bWhiteSpace
= true;
592 case sal_Unicode(EOF
):
595 m_eState
= CSS1_PAR_ACCEPTED
;
601 default: // IDENT | syntax error
602 if (rtl::isAsciiAlpha(m_cNextCh
))
606 bool bHexColor
= true;
608 // parse the next identifier
609 OUStringBuffer
sTmpBuffer(64);
611 sTmpBuffer
.append( m_cNextCh
);
614 bHexColor
= sTmpBuffer
.getLength()<7 &&
615 ( ('0'<=m_cNextCh
&& '9'>=m_cNextCh
) ||
616 ('A'<=m_cNextCh
&& 'F'>=m_cNextCh
) ||
617 ('a'<=m_cNextCh
&& 'f'>=m_cNextCh
) );
619 m_cNextCh
= GetNextChar();
620 } while( (rtl::isAsciiAlphanumeric(m_cNextCh
) ||
621 '-' == m_cNextCh
) && !IsEOF() );
623 m_aToken
+= sTmpBuffer
;
625 if( bHexColor
&& sTmpBuffer
.getLength()==6 )
628 nRet
= CSS1_HEXCOLOR
;
632 if( '('==m_cNextCh
&&
633 ( (('u'==m_aToken
[0] || 'U'==m_aToken
[0]) &&
634 m_aToken
.equalsIgnoreAsciiCase( "url" )) ||
635 (('r'==m_aToken
[0] || 'R'==m_aToken
[0]) &&
636 (m_aToken
.equalsIgnoreAsciiCase( "rgb" ) || m_aToken
.equalsIgnoreAsciiCase( "rgba" ) )
640 OUStringBuffer
sTmpBuffer2(64);
642 sTmpBuffer2
.append( m_cNextCh
);
645 case '(': nNestCnt
++; break;
646 case ')': nNestCnt
--; break;
648 m_cNextCh
= GetNextChar();
649 } while( (nNestCnt
>1 || ')'!=m_cNextCh
) && !IsEOF() );
650 sTmpBuffer2
.append( m_cNextCh
);
651 m_aToken
+= sTmpBuffer2
;
653 nRet
= 'u'==m_aToken
[0] || 'U'==m_aToken
[0]
663 // error handling: ignore digit
667 m_cNextCh
= GetNextChar();
669 } while( CSS1_NULL
==nRet
&& IsParserWorking() );
674 // These functions implement the parser described in
676 // http://www.w3.org/pub/WWW/TR/WD-css1.html
677 // resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html
679 // for CSS1. It's a direct implementation of the
680 // described Lex grammar.
691 void CSS1Parser::ParseStyleSheet()
697 while( !bDone
&& IsParserWorking() )
699 LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/import *" )
703 case CSS1_IMPORT_SYM
:
705 // URL are skipped without checks
706 m_nToken
= GetNextToken();
708 case CSS1_IDENT
: // Look-Aheads
716 // error handling: ignore
721 m_nToken
= GetNextToken();
727 while( IsParserWorking() )
729 LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/rule *" )
733 case CSS1_IDENT
: // Look-Aheads
741 // error handling: ignore
742 m_nToken
= GetNextToken();
749 // : selector [ ',' selector ]*
750 // '{' declaration [ ';' declaration ]* '}'
752 void CSS1Parser::ParseRule()
755 std::unique_ptr
<CSS1Selector
> pSelector
= ParseSelector();
760 SelectorParsed( std::move(pSelector
), true );
765 while( CSS1_COMMA
==m_nToken
&& IsParserWorking() )
767 LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/selector *" )
770 m_nToken
= GetNextToken();
773 pSelector
= ParseSelector();
778 SelectorParsed( std::move(pSelector
), false );
782 if( CSS1_OBRACE
!= m_nToken
)
784 m_nToken
= GetNextToken();
788 std::unique_ptr
<CSS1Expression
> pExpr
= ParseDeclaration( aProperty
);
792 // process expression
793 DeclarationParsed( aProperty
, std::move(pExpr
) );
797 // [ ';' declaration ]*
798 while( CSS1_SEMICOLON
==m_nToken
&& IsParserWorking() )
800 LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/declaration *" )
803 m_nToken
= GetNextToken();
806 if( CSS1_IDENT
== m_nToken
)
808 std::unique_ptr
<CSS1Expression
> pExp
= ParseDeclaration( aProperty
);
811 // process expression
812 DeclarationParsed( aProperty
, std::move(pExp
));
818 if( CSS1_CBRACE
== m_nToken
)
819 m_nToken
= GetNextToken();
823 // : simple_selector+ [ ':' pseudo_element ]?
826 // : element_name [ DOT_WO_WS class ]?
842 std::unique_ptr
<CSS1Selector
> CSS1Parser::ParseSelector()
844 std::unique_ptr
<CSS1Selector
> pRoot
;
845 CSS1Selector
*pLast
= nullptr;
848 CSS1Selector
*pNew
= nullptr;
853 while( !bDone
&& IsParserWorking() )
855 LOOP_CHECK_CHECK( "Infinite loop in ParseSelector()" )
857 bool bNextToken
= true;
863 // element_name [ DOT_WO_WS class ]?
866 OUString aElement
= m_aToken
;
867 CSS1SelectorType eType
= CSS1_SELTYPE_ELEMENT
;
868 m_nToken
= GetNextToken();
870 if( CSS1_DOT_WO_WS
== m_nToken
)
873 m_nToken
= GetNextToken();
876 if( CSS1_IDENT
== m_nToken
)
878 aElement
+= "." + m_aToken
;
879 eType
= CSS1_SELTYPE_ELEM_CLASS
;
889 // that was a look-ahead
892 pNew
= new CSS1Selector( eType
, aElement
);
899 m_nToken
= GetNextToken();
901 if( CSS1_IDENT
==m_nToken
)
904 pNew
= new CSS1Selector( CSS1_SELTYPE_CLASS
, m_aToken
);
916 m_nToken
= GetNextToken();
918 if( CSS1_IDENT
==m_nToken
)
921 pNew
= new CSS1Selector( CSS1_SELTYPE_ID
, m_aToken
);
925 // missing id_selector
933 pNew
= new CSS1Selector( CSS1_SELTYPE_PAGE
, m_aToken
);
938 // stop because we don't know what's next
943 // if created a new selector then save it
946 OSL_ENSURE( (pRoot
!=nullptr) == (pLast
!=nullptr),
947 "Root-Selector, but no Last" );
949 pLast
->SetNext( pNew
);
957 if( bNextToken
&& !bDone
)
958 m_nToken
= GetNextToken();
963 // missing simple_selector
967 // [ ':' pseudo_element ]?
968 if( CSS1_COLON
==m_nToken
&& IsParserWorking() )
970 // ':' pseudo element
971 m_nToken
= GetNextToken();
972 if( CSS1_IDENT
==m_nToken
)
975 pLast
->SetNext( new CSS1Selector(CSS1_SELTYPE_PSEUDO
,m_aToken
) );
976 m_nToken
= GetNextToken();
980 // missing pseudo_element
989 // : property ':' expr prio?
993 // : term [ operator term ]*
997 // [ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS | IDENT |
998 // HEXCOLOR | URL | RGB ]
1001 // : '/' | ',' | /* empty */
1009 // the sign is only used for numeric values (except PERCENTAGE)
1010 // and it's applied on nValue!
1011 std::unique_ptr
<CSS1Expression
> CSS1Parser::ParseDeclaration( OUString
& rProperty
)
1013 std::unique_ptr
<CSS1Expression
> pRoot
;
1014 CSS1Expression
*pLast
= nullptr;
1017 if( CSS1_IDENT
!= m_nToken
)
1022 rProperty
= m_aToken
;
1024 m_nToken
= GetNextToken();
1027 if( CSS1_COLON
!= m_nToken
)
1032 m_nToken
= GetNextToken();
1034 // term [operator term]*
1035 // here we're pretty lax regarding the syntax, but this shouldn't
1038 sal_Unicode cSign
= 0, cOp
= 0;
1039 CSS1Expression
*pNew
= nullptr;
1043 while( !bDone
&& IsParserWorking() )
1045 LOOP_CHECK_CHECK( "Infinite loop in ParseDeclaration()" )
1059 case CSS1_PIXLENGTH
:
1063 m_nValue
= -m_nValue
;
1066 case CSS1_PERCENTAGE
:
1071 pNew
= new CSS1Expression( m_nToken
, m_aToken
, m_nValue
, cOp
);
1072 m_nValue
= 0; // otherwise this also is applied to next ident
1092 // if created a new expression save it
1095 OSL_ENSURE( (pRoot
!=nullptr) == (pLast
!=nullptr),
1096 "Root-Selector, but no Last" );
1098 pLast
->SetNext( pNew
);
1107 m_nToken
= GetNextToken();
1117 if( CSS1_IMPORTANT_SYM
==m_nToken
)
1120 m_nToken
= GetNextToken();
1126 CSS1Parser::CSS1Parser()
1127 : m_bWhiteSpace(false)
1134 , m_eState(CSS1_PAR_ACCEPTED
)
1135 , m_nToken(CSS1_NULL
)
1139 CSS1Parser::~CSS1Parser()
1143 void CSS1Parser::ParseStyleSheet( const OUString
& rIn
)
1145 OUString
aTmp( rIn
);
1148 while( !aTmp
.isEmpty() &&
1149 ( ' '==(c
=aTmp
[0]) || '\t'==c
|| '\r'==c
|| '\n'==c
) )
1150 aTmp
= aTmp
.copy( 1 );
1152 while( !aTmp
.isEmpty() && ( ' '==(c
=aTmp
[aTmp
.getLength()-1])
1153 || '\t'==c
|| '\r'==c
|| '\n'==c
) )
1154 aTmp
= aTmp
.copy( 0, aTmp
.getLength()-1 );
1156 // remove SGML comments
1157 if( aTmp
.getLength() >= 4 &&
1158 aTmp
.startsWith( "<!--" ) )
1159 aTmp
= aTmp
.copy( 4 );
1161 if( aTmp
.getLength() >=3 &&
1162 aTmp
.endsWith("-->") )
1163 aTmp
= aTmp
.copy( 0, aTmp
.getLength() - 3 );
1165 if( aTmp
.isEmpty() )
1173 void CSS1Parser::ParseStyleOption( const OUString
& rIn
)
1180 // fdo#41796: skip over spurious semicolons
1181 while (CSS1_SEMICOLON
== m_nToken
)
1183 m_nToken
= GetNextToken();
1187 std::unique_ptr
<CSS1Expression
> pExpr
= ParseDeclaration( aProperty
);
1191 // process expression
1192 DeclarationParsed( aProperty
, std::move(pExpr
) );
1196 // [ ';' declaration ]*
1197 while( CSS1_SEMICOLON
==m_nToken
&& IsParserWorking() )
1199 LOOP_CHECK_CHECK( "Infinite loop in ParseStyleOption()" )
1201 m_nToken
= GetNextToken();
1202 if( CSS1_IDENT
==m_nToken
)
1204 std::unique_ptr
<CSS1Expression
> pExp
= ParseDeclaration( aProperty
);
1207 // process expression
1208 DeclarationParsed( aProperty
, std::move(pExp
) );
1214 void CSS1Parser::SelectorParsed( std::unique_ptr
<CSS1Selector
> /* pSelector */, bool /*bFirst*/ )
1218 void CSS1Parser::DeclarationParsed( const OUString
& /*rProperty*/,
1219 std::unique_ptr
<CSS1Expression
> /* pExpr */ )
1223 CSS1Selector::~CSS1Selector()
1228 CSS1Expression::~CSS1Expression()
1233 void CSS1Expression::GetURL( OUString
& rURL
) const
1235 OSL_ENSURE( CSS1_URL
==eType
, "CSS1-Expression is not URL" );
1237 OSL_ENSURE( aValue
.startsWithIgnoreAsciiCase( "url" ) &&
1238 aValue
.getLength() > 5 &&
1240 ')' == aValue
[aValue
.getLength()-1],
1241 "no valid URL(...)" );
1243 if( aValue
.getLength() <= 5 )
1246 rURL
= aValue
.copy( 4, aValue
.getLength() - 5 );
1248 // tdf#94088 original stripped only spaces, but there may also be
1249 // double quotes in CSS style URLs, so be prepared to spaces followed
1250 // by a single quote followed by spaces
1251 const sal_Unicode
aSpace(' ');
1252 const sal_Unicode
aSingleQuote('\'');
1254 rURL
= comphelper::string::strip(rURL
, aSpace
);
1255 rURL
= comphelper::string::strip(rURL
, aSingleQuote
);
1256 rURL
= comphelper::string::strip(rURL
, aSpace
);
1259 bool CSS1Expression::GetColor( Color
&rColor
) const
1261 OSL_ENSURE( CSS1_IDENT
==eType
|| CSS1_RGB
==eType
||
1262 CSS1_HEXCOLOR
==eType
|| CSS1_STRING
==eType
,
1263 "CSS1-Expression cannot be colour" );
1266 sal_uInt32 nColor
= SAL_MAX_UINT32
;
1272 // fourth value to 255 means no alpha transparency
1273 // so the right by default value
1274 sal_uInt8 aColors
[4] = { 0, 0, 0, 255 };
1276 // it can be "rgb" or "rgba"
1277 if (!aValue
.startsWithIgnoreAsciiCase( "rgb" ) || aValue
.getLength() < 6 ||
1278 (aValue
[3] != '(' && aValue
[4] != '(' ) || aValue
[aValue
.getLength()-1] != ')')
1283 sal_Int32 nPos
= aValue
.startsWithIgnoreAsciiCase( "rgba" )?5:4; // start after "rgba(" or "rgb("
1284 char cSep
= (aValue
.indexOf(',') != -1)?',':' ';
1285 // alpha value can be after a "/" or ","
1286 bool bIsSepAlphaDiv
= (aValue
.indexOf('/') != -1)?true:false;
1287 for ( int nCol
= 0; nCol
< 4 && nPos
> 0; ++nCol
)
1289 const std::u16string_view aNumber
= o3tl::getToken(aValue
, 0, cSep
, nPos
);
1291 sal_Int32 nNumber
= o3tl::toInt32(aNumber
);
1296 else if( aNumber
.find('%') != std::u16string_view::npos
)
1303 else if( nNumber
> 255 )
1305 else if( aNumber
.find('.') != std::u16string_view::npos
)
1307 // in this case aNumber contains something like "0.3" so not an sal_Int32
1308 nNumber
= static_cast<sal_Int32
>(255.0*o3tl::toDouble(aNumber
));
1310 aColors
[nCol
] = static_cast<sal_uInt8
>(nNumber
);
1311 // rgb with alpha and '/' has this form: rgb(255 0 0 / 50%)
1312 if (bIsSepAlphaDiv
&& nCol
== 2)
1314 // but there can be some spaces or not before and after the "/", so skip them
1315 while (aValue
[nPos
] == '/' || aValue
[nPos
] == ' ')
1320 rColor
.SetRed( aColors
[0] );
1321 rColor
.SetGreen( aColors
[1] );
1322 rColor
.SetBlue( aColors
[2] );
1323 rColor
.SetAlpha( aColors
[3] );
1325 bRet
= true; // something different than a colour isn't possible
1332 OUString
aTmp( aValue
.toAsciiUpperCase() );
1333 nColor
= GetHTMLColor( aTmp
);
1334 bRet
= nColor
!= SAL_MAX_UINT32
;
1336 if( bRet
|| CSS1_STRING
!= eType
|| aValue
.isEmpty() ||
1342 // MS-IE hack: colour can also be a string
1343 sal_Int32 nOffset
= CSS1_STRING
==eType
? 1 : 0;
1344 bool bDouble
= aValue
.getLength()-nOffset
== 3;
1345 sal_Int32 i
= nOffset
, nEnd
= (bDouble
? 3 : 6) + nOffset
;
1348 for( ; i
<nEnd
; i
++ )
1350 sal_Unicode c
= (i
<aValue
.getLength() ? aValue
[i
]
1352 if( c
>= '0' && c
<= '9' )
1354 else if( c
>= 'A' && c
<= 'F' )
1356 else if( c
>= 'a' && c
<= 'f' )
1378 if( bRet
&& nColor
!=SAL_MAX_UINT32
)
1380 rColor
.SetRed( static_cast<sal_uInt8
>((nColor
& 0x00ff0000UL
) >> 16) );
1381 rColor
.SetGreen( static_cast<sal_uInt8
>((nColor
& 0x0000ff00UL
) >> 8) );
1382 rColor
.SetBlue( static_cast<sal_uInt8
>(nColor
& 0x000000ffUL
) );
1388 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */