1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * L. David Baron <dbaron@dbaron.org>
24 * Daniel Glazman <glazman@netscape.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 /* tokenization of CSS style sheets */
42 #include "nsCSSScanner.h"
43 #include "nsIFactory.h"
44 #include "nsIInputStream.h"
45 #include "nsIUnicharInputStream.h"
49 // for #ifdef CSS_REPORT_PARSE_ERRORS
51 #include "nsIServiceManager.h"
52 #include "nsIComponentManager.h"
53 #include "nsReadableUtils.h"
55 #include "nsIConsoleService.h"
56 #include "nsIScriptError.h"
57 #include "nsIStringBundle.h"
58 #include "nsContentUtils.h"
60 // Don't bother collecting whitespace characters in token's mIdent buffer
61 #undef COLLECT_WHITESPACE
63 static const PRUnichar CSS_ESCAPE
= PRUnichar('\\');
64 static const PRUint8 IS_DIGIT
= 0x01;
65 static const PRUint8 IS_HEX_DIGIT
= 0x02;
66 static const PRUint8 START_IDENT
= 0x04;
67 static const PRUint8 IS_IDENT
= 0x08;
68 static const PRUint8 IS_WHITESPACE
= 0x10;
70 static PRBool gLexTableSetup
= PR_FALSE
;
71 static PRUint8 gLexTable
[256];
73 #ifdef CSS_REPORT_PARSE_ERRORS
74 static PRBool gReportErrors
= PR_TRUE
;
75 static nsIConsoleService
*gConsoleService
;
76 static nsIFactory
*gScriptErrorFactory
;
77 static nsIStringBundle
*gStringBundle
;
83 gLexTableSetup
= PR_TRUE
;
85 PRUint8
* lt
= gLexTable
;
87 lt
[CSS_ESCAPE
] = START_IDENT
;
89 lt
['_'] |= IS_IDENT
| START_IDENT
;
90 lt
[' '] |= IS_WHITESPACE
; // space
91 lt
['\t'] |= IS_WHITESPACE
; // horizontal tab
92 lt
['\r'] |= IS_WHITESPACE
; // carriage return
93 lt
['\n'] |= IS_WHITESPACE
; // line feed
94 lt
['\f'] |= IS_WHITESPACE
; // form feed
95 for (i
= 161; i
<= 255; i
++) {
96 lt
[i
] |= IS_IDENT
| START_IDENT
;
98 for (i
= '0'; i
<= '9'; i
++) {
99 lt
[i
] |= IS_DIGIT
| IS_HEX_DIGIT
| IS_IDENT
;
101 for (i
= 'A'; i
<= 'Z'; i
++) {
102 if ((i
>= 'A') && (i
<= 'F')) {
103 lt
[i
] |= IS_HEX_DIGIT
;
104 lt
[i
+32] |= IS_HEX_DIGIT
;
106 lt
[i
] |= IS_IDENT
| START_IDENT
;
107 lt
[i
+32] |= IS_IDENT
| START_IDENT
;
112 IsIdentStart(PRInt32 aChar
)
115 (aChar
>= 256 || (gLexTable
[aChar
] & START_IDENT
) != 0);
119 StartsIdent(PRInt32 aFirstChar
, PRInt32 aSecondChar
)
121 return IsIdentStart(aFirstChar
) ||
122 (aFirstChar
== '-' && IsIdentStart(aSecondChar
));
126 IsWhitespace(PRInt32 ch
) {
127 return PRUint32(ch
) < 256 && (gLexTable
[ch
] & IS_WHITESPACE
) != 0;
131 IsDigit(PRInt32 ch
) {
132 return PRUint32(ch
) < 256 && (gLexTable
[ch
] & IS_DIGIT
) != 0;
136 IsHexDigit(PRInt32 ch
) {
137 return PRUint32(ch
) < 256 && (gLexTable
[ch
] & IS_HEX_DIGIT
) != 0;
141 IsIdent(PRInt32 ch
) {
142 return ch
>= 0 && (ch
>= 256 || (gLexTable
[ch
] & IS_IDENT
) != 0);
145 nsCSSToken::nsCSSToken()
147 mType
= eCSSToken_Symbol
;
151 nsCSSToken::AppendToString(nsString
& aBuffer
)
154 case eCSSToken_AtKeyword
:
155 aBuffer
.Append(PRUnichar('@')); // fall through intentional
156 case eCSSToken_Ident
:
157 case eCSSToken_WhiteSpace
:
158 case eCSSToken_Function
:
160 case eCSSToken_InvalidURL
:
161 case eCSSToken_HTMLComment
:
162 aBuffer
.Append(mIdent
);
164 case eCSSToken_Number
:
166 aBuffer
.AppendInt(mInteger
, 10);
169 aBuffer
.AppendFloat(mNumber
);
172 case eCSSToken_Percentage
:
173 NS_ASSERTION(!mIntegerValid
, "How did a percentage token get this set?");
174 aBuffer
.AppendFloat(mNumber
* 100.0f
);
175 aBuffer
.Append(PRUnichar('%')); // STRING USE WARNING: technically, this should be |AppendWithConversion|
177 case eCSSToken_Dimension
:
179 aBuffer
.AppendInt(mInteger
, 10);
182 aBuffer
.AppendFloat(mNumber
);
184 aBuffer
.Append(mIdent
);
186 case eCSSToken_String
:
187 aBuffer
.Append(mSymbol
);
188 aBuffer
.Append(mIdent
); // fall through intentional
189 case eCSSToken_Symbol
:
190 aBuffer
.Append(mSymbol
);
194 aBuffer
.Append(PRUnichar('#'));
195 aBuffer
.Append(mIdent
);
197 case eCSSToken_Includes
:
198 aBuffer
.AppendLiteral("~=");
200 case eCSSToken_Dashmatch
:
201 aBuffer
.AppendLiteral("|=");
203 case eCSSToken_Beginsmatch
:
204 aBuffer
.AppendLiteral("^=");
206 case eCSSToken_Endsmatch
:
207 aBuffer
.AppendLiteral("$=");
209 case eCSSToken_Containsmatch
:
210 aBuffer
.AppendLiteral("*=");
212 case eCSSToken_Error
:
213 aBuffer
.Append(mSymbol
);
214 aBuffer
.Append(mIdent
);
217 NS_ERROR("invalid token type");
222 nsCSSScanner::nsCSSScanner()
223 : mInputStream(nsnull
)
224 , mReadPointer(nsnull
)
225 , mLowLevelError(NS_OK
)
229 #ifdef CSS_REPORT_PARSE_ERRORS
230 , mError(mErrorBuf
, NS_ARRAY_LENGTH(mErrorBuf
), 0)
233 MOZ_COUNT_CTOR(nsCSSScanner
);
234 if (!gLexTableSetup
) {
235 // XXX need a monitor
238 mPushback
= mLocalPushback
;
239 mPushbackSize
= NS_ARRAY_LENGTH(mLocalPushback
);
240 // No need to init the other members, since they represent state
241 // which can get cleared. We'll init them every time Init() is
245 nsCSSScanner::~nsCSSScanner()
247 MOZ_COUNT_DTOR(nsCSSScanner
);
249 if (mLocalPushback
!= mPushback
) {
255 nsCSSScanner::GetLowLevelError()
257 return mLowLevelError
;
261 nsCSSScanner::SetLowLevelError(nsresult aErrorCode
)
263 NS_ASSERTION(aErrorCode
!= NS_OK
, "SetLowLevelError() used to clear error");
264 NS_ASSERTION(mLowLevelError
== NS_OK
, "there is already a low-level error");
265 mLowLevelError
= aErrorCode
;
268 #ifdef CSS_REPORT_PARSE_ERRORS
269 #define CSS_ERRORS_PREF "layout.css.report_errors"
271 PR_STATIC_CALLBACK(int)
272 CSSErrorsPrefChanged(const char *aPref
, void *aClosure
)
274 gReportErrors
= nsContentUtils::GetBoolPref(CSS_ERRORS_PREF
, PR_TRUE
);
280 nsCSSScanner::InitGlobals()
282 #ifdef CSS_REPORT_PARSE_ERRORS
283 if (gConsoleService
&& gScriptErrorFactory
)
286 nsresult rv
= CallGetService(NS_CONSOLESERVICE_CONTRACTID
, &gConsoleService
);
287 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
289 rv
= CallGetClassObject(NS_SCRIPTERROR_CONTRACTID
, &gScriptErrorFactory
);
290 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
291 NS_ASSERTION(gConsoleService
&& gScriptErrorFactory
,
292 "unexpected null pointer without failure");
294 nsContentUtils::RegisterPrefCallback(CSS_ERRORS_PREF
, CSSErrorsPrefChanged
, nsnull
);
295 CSSErrorsPrefChanged(CSS_ERRORS_PREF
, nsnull
);
301 nsCSSScanner::ReleaseGlobals()
303 #ifdef CSS_REPORT_PARSE_ERRORS
304 nsContentUtils::UnregisterPrefCallback(CSS_ERRORS_PREF
, CSSErrorsPrefChanged
, nsnull
);
305 NS_IF_RELEASE(gConsoleService
);
306 NS_IF_RELEASE(gScriptErrorFactory
);
307 NS_IF_RELEASE(gStringBundle
);
312 nsCSSScanner::Init(nsIUnicharInputStream
* aInput
,
313 const PRUnichar
* aBuffer
, PRUint32 aCount
,
314 nsIURI
* aURI
, PRUint32 aLineNumber
)
316 NS_PRECONDITION(!mInputStream
, "Should not have an existing input stream!");
317 NS_PRECONDITION(!mReadPointer
, "Should not have an existing input buffer!");
319 // Read from stream via my own buffer
321 NS_PRECONDITION(!aBuffer
, "Shouldn't have both input and buffer!");
322 NS_PRECONDITION(aCount
== 0, "Shouldn't have count with a stream");
323 mInputStream
= aInput
;
324 mReadPointer
= mBuffer
;
327 NS_PRECONDITION(aBuffer
, "Either aInput or aBuffer must be set");
328 // Read directly from the provided buffer
329 mInputStream
= nsnull
;
330 mReadPointer
= aBuffer
;
334 #ifdef CSS_REPORT_PARSE_ERRORS
335 // If aURI is the same as mURI, no need to reget mFileName -- it
336 // shouldn't have changed.
340 aURI
->GetSpec(mFileName
);
342 mFileName
.Adopt(NS_strdup("from DOM"));
345 #endif // CSS_REPORT_PARSE_ERRORS
346 mLineNumber
= aLineNumber
;
348 // Reset variables that we use to keep track of our progress through the input
351 mLowLevelError
= NS_OK
;
353 #ifdef CSS_REPORT_PARSE_ERRORS
358 #ifdef CSS_REPORT_PARSE_ERRORS
360 // @see REPORT_UNEXPECTED_EOF in nsCSSParser.cpp
361 #define REPORT_UNEXPECTED_EOF(lf_) \
362 ReportUnexpectedEOF(#lf_)
365 nsCSSScanner::AddToError(const nsSubstring
& aErrorText
)
367 if (mError
.IsEmpty()) {
368 mErrorLineNumber
= mLineNumber
;
369 mErrorColNumber
= mColNumber
;
372 mError
.Append(NS_LITERAL_STRING(" ") + aErrorText
);
377 nsCSSScanner::ClearError()
383 nsCSSScanner::OutputError()
385 if (mError
.IsEmpty()) return;
387 // Log it to the Error console
389 if (InitGlobals() && gReportErrors
) {
391 nsCOMPtr
<nsIScriptError
> errorObject
=
392 do_CreateInstance(gScriptErrorFactory
, &rv
);
393 if (NS_SUCCEEDED(rv
)) {
394 rv
= errorObject
->Init(mError
.get(),
395 NS_ConvertUTF8toUTF16(mFileName
).get(),
399 nsIScriptError::warningFlag
,
401 if (NS_SUCCEEDED(rv
))
402 gConsoleService
->LogMessage(errorObject
);
414 nsCOMPtr
<nsIStringBundleService
> sbs
=
415 do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
420 sbs
->CreateBundle("chrome://global/locale/css.properties", &gStringBundle
);
422 gStringBundle
= nsnull
;
429 #define ENSURE_STRINGBUNDLE \
430 PR_BEGIN_MACRO if (!InitStringBundle()) return; PR_END_MACRO
432 // aMessage must take no parameters
433 void nsCSSScanner::ReportUnexpected(const char* aMessage
)
438 gStringBundle
->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage
).get(),
444 nsCSSScanner::ReportUnexpectedParams(const char* aMessage
,
445 const PRUnichar
**aParams
,
446 PRUint32 aParamsLength
)
448 NS_PRECONDITION(aParamsLength
> 0, "use the non-params version");
452 gStringBundle
->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage
).get(),
453 aParams
, aParamsLength
,
458 // aLookingFor is a plain string, not a format string
460 nsCSSScanner::ReportUnexpectedEOF(const char* aLookingFor
)
464 nsXPIDLString innerStr
;
465 gStringBundle
->GetStringFromName(NS_ConvertASCIItoUTF16(aLookingFor
).get(),
466 getter_Copies(innerStr
));
468 const PRUnichar
*params
[] = {
472 gStringBundle
->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
473 params
, NS_ARRAY_LENGTH(params
),
478 // aLookingFor is a single character
480 nsCSSScanner::ReportUnexpectedEOF(PRUnichar aLookingFor
)
484 const PRUnichar lookingForStr
[] = {
485 PRUnichar('\''), aLookingFor
, PRUnichar('\''), PRUnichar(0)
487 const PRUnichar
*params
[] = { lookingForStr
};
489 gStringBundle
->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
490 params
, NS_ARRAY_LENGTH(params
),
495 // aMessage must take 1 parameter (for the string representation of the
498 nsCSSScanner::ReportUnexpectedToken(nsCSSToken
& tok
,
499 const char *aMessage
)
503 nsAutoString tokenString
;
504 tok
.AppendToString(tokenString
);
506 const PRUnichar
*params
[] = {
510 ReportUnexpectedParams(aMessage
, params
, NS_ARRAY_LENGTH(params
));
513 // aParams's first entry must be null, and we'll fill in the token
515 nsCSSScanner::ReportUnexpectedTokenParams(nsCSSToken
& tok
,
516 const char* aMessage
,
517 const PRUnichar
**aParams
,
518 PRUint32 aParamsLength
)
520 NS_PRECONDITION(aParamsLength
> 1, "use the non-params version");
521 NS_PRECONDITION(aParams
[0] == nsnull
, "first param should be empty");
525 nsAutoString tokenString
;
526 tok
.AppendToString(tokenString
);
527 aParams
[0] = tokenString
.get();
529 ReportUnexpectedParams(aMessage
, aParams
, aParamsLength
);
534 #define REPORT_UNEXPECTED_EOF(lf_)
536 #endif // CSS_REPORT_PARSE_ERRORS
539 nsCSSScanner::Close()
541 mInputStream
= nsnull
;
542 mReadPointer
= nsnull
;
544 // Clean things up so we don't hold on to memory if our parser gets recycled.
545 #ifdef CSS_REPORT_PARSE_ERRORS
546 mFileName
.Truncate();
550 if (mPushback
!= mLocalPushback
) {
552 mPushback
= mLocalPushback
;
553 mPushbackSize
= NS_ARRAY_LENGTH(mLocalPushback
);
557 #ifdef CSS_REPORT_PARSE_ERRORS
558 #define TAB_STOP_WIDTH 8
562 nsCSSScanner::EnsureData()
564 if (mOffset
< mCount
)
571 nsresult rv
= mInputStream
->Read(mBuffer
, CSS_BUFFER_SIZE
, &mCount
);
575 SetLowLevelError(rv
);
582 // Returns -1 on error or eof
587 if (0 < mPushbackCount
) {
588 rv
= PRInt32(mPushback
[--mPushbackCount
]);
590 if (mOffset
== mCount
&& !EnsureData()) {
593 rv
= PRInt32(mReadPointer
[mOffset
++]);
594 // There are four types of newlines in CSS: "\r", "\n", "\r\n", and "\f".
595 // To simplify dealing with newlines, they are all normalized to "\n" here
597 if (EnsureData() && mReadPointer
[mOffset
] == '\n') {
601 } else if (rv
== '\f') {
605 // 0 is a magical line number meaning that we don't know (i.e., script)
606 if (mLineNumber
!= 0)
608 #ifdef CSS_REPORT_PARSE_ERRORS
612 #ifdef CSS_REPORT_PARSE_ERRORS
613 else if (rv
== '\t') {
614 mColNumber
= ((mColNumber
- 1 + TAB_STOP_WIDTH
) / TAB_STOP_WIDTH
)
616 } else if (rv
!= '\n') {
621 //printf("Read => %x\n", rv);
628 if (0 == mPushbackCount
) {
633 mPushback
[0] = PRUnichar(ch
);
636 //printf("Peek => %x\n", mLookAhead);
637 return PRInt32(mPushback
[mPushbackCount
- 1]);
641 nsCSSScanner::Pushback(PRUnichar aChar
)
643 if (mPushbackCount
== mPushbackSize
) { // grow buffer
644 PRUnichar
* newPushback
= new PRUnichar
[mPushbackSize
+ 4];
645 if (nsnull
== newPushback
) {
649 memcpy(newPushback
, mPushback
, sizeof(PRUnichar
) * mPushbackCount
);
650 if (mPushback
!= mLocalPushback
) {
653 mPushback
= newPushback
;
655 mPushback
[mPushbackCount
++] = aChar
;
659 nsCSSScanner::LookAhead(PRUnichar aChar
)
673 nsCSSScanner::EatWhiteSpace()
675 PRBool eaten
= PR_FALSE
;
681 if ((ch
== ' ') || (ch
== '\n') || (ch
== '\t')) {
692 nsCSSScanner::EatNewline()
698 PRBool eaten
= PR_FALSE
;
708 nsCSSScanner::Next(nsCSSToken
& aToken
)
716 if (StartsIdent(ch
, Peek()))
717 return ParseIdent(ch
, aToken
);
721 PRInt32 nextChar
= Read();
723 PRInt32 followingChar
= Peek();
725 if (StartsIdent(nextChar
, followingChar
))
726 return ParseAtKeyword(ch
, aToken
);
731 if ((ch
== '.') || (ch
== '+') || (ch
== '-')) {
732 PRInt32 nextChar
= Peek();
733 if (IsDigit(nextChar
)) {
734 return ParseNumber(ch
, aToken
);
736 else if (('.' == nextChar
) && ('.' != ch
)) {
738 PRInt32 followingChar
= Peek();
740 if (IsDigit(followingChar
))
741 return ParseNumber(ch
, aToken
);
745 return ParseNumber(ch
, aToken
);
750 return ParseRef(ch
, aToken
);
754 if ((ch
== '"') || (ch
== '\'')) {
755 return ParseString(ch
, aToken
);
759 if (IsWhitespace(ch
)) {
760 aToken
.mType
= eCSSToken_WhiteSpace
;
761 aToken
.mIdent
.Assign(PRUnichar(ch
));
762 (void) EatWhiteSpace();
766 PRInt32 nextChar
= Peek();
767 if (nextChar
== '*') {
770 // If we change our storage data structures such that comments are
771 // stored (for Editor), we should reenable this code, condition it
772 // on being in editor mode, and apply glazou's patch from bug
774 aToken
.mIdent
.SetCapacity(2);
775 aToken
.mIdent
.Assign(PRUnichar(ch
));
776 aToken
.mIdent
.Append(PRUnichar(nextChar
));
777 return ParseCComment(aToken
);
779 return SkipCComment() && Next(aToken
);
782 if (ch
== '<') { // consume HTML comment tags
783 if (LookAhead('!')) {
784 if (LookAhead('-')) {
785 if (LookAhead('-')) {
786 aToken
.mType
= eCSSToken_HTMLComment
;
787 aToken
.mIdent
.AssignLiteral("<!--");
795 if (ch
== '-') { // check for HTML comment end
796 if (LookAhead('-')) {
797 if (LookAhead('>')) {
798 aToken
.mType
= eCSSToken_HTMLComment
;
799 aToken
.mIdent
.AssignLiteral("-->");
806 // INCLUDES ("~=") and DASHMATCH ("|=")
807 if (( ch
== '|' ) || ( ch
== '~' ) || ( ch
== '^' ) ||
808 ( ch
== '$' ) || ( ch
== '*' )) {
809 PRInt32 nextChar
= Read();
810 if ( nextChar
== '=' ) {
812 aToken
.mType
= eCSSToken_Includes
;
814 else if (ch
== '|') {
815 aToken
.mType
= eCSSToken_Dashmatch
;
817 else if (ch
== '^') {
818 aToken
.mType
= eCSSToken_Beginsmatch
;
820 else if (ch
== '$') {
821 aToken
.mType
= eCSSToken_Endsmatch
;
823 else if (ch
== '*') {
824 aToken
.mType
= eCSSToken_Containsmatch
;
827 } else if (nextChar
>= 0) {
831 aToken
.mType
= eCSSToken_Symbol
;
837 nsCSSScanner::NextURL(nsCSSToken
& aToken
)
845 if ((ch
== '"') || (ch
== '\'')) {
846 return ParseString(ch
, aToken
);
850 if (IsWhitespace(ch
)) {
851 aToken
.mType
= eCSSToken_WhiteSpace
;
852 aToken
.mIdent
.Assign(PRUnichar(ch
));
853 (void) EatWhiteSpace();
857 PRInt32 nextChar
= Peek();
858 if (nextChar
== '*') {
861 // If we change our storage data structures such that comments are
862 // stored (for Editor), we should reenable this code, condition it
863 // on being in editor mode, and apply glazou's patch from bug
865 aToken
.mIdent
.SetCapacity(2);
866 aToken
.mIdent
.Assign(PRUnichar(ch
));
867 aToken
.mIdent
.Append(PRUnichar(nextChar
));
868 return ParseCComment(aToken
);
870 return SkipCComment() && Next(aToken
);
874 // Process a url lexical token. A CSS1 url token can contain
875 // characters beyond identifier characters (e.g. '/', ':', etc.)
876 // Because of this the normal rules for tokenizing the input don't
877 // apply very well. To simplify the parser and relax some of the
878 // requirements on the scanner we parse url's here. If we find a
879 // malformed URL then we emit a token of type "InvalidURL" so that
880 // the CSS1 parser can ignore the invalid input. We attempt to eat
881 // the right amount of input data when an invalid URL is presented.
883 aToken
.mType
= eCSSToken_InvalidURL
;
884 nsString
& ident
= aToken
.mIdent
;
889 // empty url spec; just get out of here
890 aToken
.mType
= eCSSToken_URL
;
892 // start of a non-quoted url
898 if (ch
== CSS_ESCAPE
) {
899 ParseAndAppendEscape(ident
);
900 } else if ((ch
== '"') || (ch
== '\'') || (ch
== '(')) {
901 // This is an invalid URL spec
903 } else if (IsWhitespace(ch
)) {
904 // Whitespace is allowed at the end of the URL
905 (void) EatWhiteSpace();
906 if (LookAhead(')')) {
907 Pushback(')'); // leave the closing symbol
911 // Whitespace is followed by something other than a
912 // ")". This is an invalid url spec.
914 } else if (ch
== ')') {
919 // A regular url character.
920 ident
.Append(PRUnichar(ch
));
924 // If the result of the above scanning is ok then change the token
925 // type to a useful one.
927 aToken
.mType
= eCSSToken_URL
;
935 nsCSSScanner::ParseAndAppendEscape(nsString
& aOutput
)
939 aOutput
.Append(CSS_ESCAPE
);
942 if (IsHexDigit(ch
)) {
945 for (i
= 0; i
< 6; i
++) { // up to six digits
948 // Whoops: error or premature eof
951 if (!IsHexDigit(ch
) && !IsWhitespace(ch
)) {
954 } else if (IsHexDigit(ch
)) {
956 rv
= rv
* 16 + (ch
- '0');
958 // Note: c&7 just keeps the low three bits which causes
959 // upper and lower case alphabetics to both yield their
960 // "relative to 10" value for computing the hex value.
961 rv
= rv
* 16 + ((ch
& 0x7) + 9);
964 NS_ASSERTION(IsWhitespace(ch
), "bad control flow");
965 // single space ends escape
969 if (6 == i
) { // look for trailing whitespace and eat it
971 if (IsWhitespace(ch
)) {
975 NS_ASSERTION(rv
>= 0, "How did rv become negative?");
977 AppendUCS4ToUTF16(ENSURE_VALID_CHAR(rv
), aOutput
);
981 // "Any character except a hexidecimal digit can be escaped to
982 // remove its special meaning by putting a backslash in front"
983 // -- CSS1 spec section 7.1
984 if (!EatNewline()) { // skip escaped newline
995 * Gather up the characters in an identifier. The identfier was
996 * started by "aChar" which will be appended to aIdent. The result
997 * will be aIdent with all of the identifier characters appended
998 * until the first non-identifier character is seen. The termination
999 * character is unread for the future re-reading.
1002 nsCSSScanner::GatherIdent(PRInt32 aChar
, nsString
& aIdent
)
1004 if (aChar
== CSS_ESCAPE
) {
1005 ParseAndAppendEscape(aIdent
);
1007 else if (0 < aChar
) {
1008 aIdent
.Append(aChar
);
1011 // If nothing in pushback, first try to get as much as possible in one go
1012 if (!mPushbackCount
&& EnsureData()) {
1013 // See how much we can consume and append in one go
1014 PRUint32 n
= mOffset
;
1015 // Count number of Ident characters that can be processed
1016 while (n
< mCount
&& IsIdent(mReadPointer
[n
])) {
1019 // Add to the token what we have so far
1021 #ifdef CSS_REPORT_PARSE_ERRORS
1022 mColNumber
+= n
- mOffset
;
1024 aIdent
.Append(&mReadPointer
[mOffset
], n
- mOffset
);
1030 if (aChar
< 0) break;
1031 if (aChar
== CSS_ESCAPE
) {
1032 ParseAndAppendEscape(aIdent
);
1033 } else if (IsIdent(aChar
)) {
1034 aIdent
.Append(PRUnichar(aChar
));
1044 nsCSSScanner::ParseRef(PRInt32 aChar
, nsCSSToken
& aToken
)
1046 aToken
.mIdent
.SetLength(0);
1047 aToken
.mType
= eCSSToken_Ref
;
1048 PRInt32 ch
= Read();
1052 if (IsIdent(ch
) || ch
== CSS_ESCAPE
) {
1053 // First char after the '#' is a valid ident char (or an escape),
1054 // so it makes sense to keep going
1055 if (StartsIdent(ch
, Peek())) {
1056 aToken
.mType
= eCSSToken_ID
;
1058 return GatherIdent(ch
, aToken
.mIdent
);
1061 // No ident chars after the '#'. Just unread |ch| and get out of here.
1067 nsCSSScanner::ParseIdent(PRInt32 aChar
, nsCSSToken
& aToken
)
1069 nsString
& ident
= aToken
.mIdent
;
1071 if (!GatherIdent(aChar
, ident
)) {
1075 nsCSSTokenType tokenType
= eCSSToken_Ident
;
1076 // look for functions (ie: "ident(")
1077 if (PRUnichar('(') == PRUnichar(Peek())) { // this is a function definition
1078 tokenType
= eCSSToken_Function
;
1081 aToken
.mType
= tokenType
;
1086 nsCSSScanner::ParseAtKeyword(PRInt32 aChar
, nsCSSToken
& aToken
)
1088 aToken
.mIdent
.SetLength(0);
1089 aToken
.mType
= eCSSToken_AtKeyword
;
1090 return GatherIdent(0, aToken
.mIdent
);
1094 nsCSSScanner::ParseNumber(PRInt32 c
, nsCSSToken
& aToken
)
1096 nsString
& ident
= aToken
.mIdent
;
1098 PRBool gotDot
= (c
== '.');
1099 aToken
.mHasSign
= (c
== '+' || c
== '-');
1101 ident
.Append(PRUnichar(c
));
1104 // Gather up characters that make up the number
1105 PRBool gotE
= PR_FALSE
;
1109 if (!gotDot
&& !gotE
&& (c
== '.') &&
1113 } else if (!gotE
&& (c
== 'e' || c
== 'E')) {
1117 PRInt32 nextChar
= Peek();
1119 if (nextChar
== '-' || nextChar
== '+') {
1123 if (IsDigit(nextChar
)) {
1126 ident
.Append(PRUnichar(c
));
1136 } else if (!IsDigit(c
)) {
1139 ident
.Append(PRUnichar(c
));
1142 // Convert number to floating point
1143 nsCSSTokenType type
= eCSSToken_Number
;
1145 float value
= ident
.ToFloat(&ec
);
1147 // Set mIntegerValid for all cases (except %, below) because we need
1148 // it for the "2n" in :nth-child(2n).
1149 aToken
.mIntegerValid
= PR_FALSE
;
1150 if (!gotDot
&& !gotE
) {
1151 aToken
.mInteger
= ident
.ToInteger(&ec
);
1152 aToken
.mIntegerValid
= PR_TRUE
;
1156 // Look at character that terminated the number
1158 if (StartsIdent(c
, Peek())) {
1159 if (!GatherIdent(c
, ident
)) {
1162 type
= eCSSToken_Dimension
;
1163 } else if ('%' == c
) {
1164 type
= eCSSToken_Percentage
;
1165 value
= value
/ 100.0f
;
1166 aToken
.mIntegerValid
= PR_FALSE
;
1168 // Put back character that stopped numeric scan
1172 aToken
.mNumber
= value
;
1173 aToken
.mType
= type
;
1178 nsCSSScanner::SkipCComment()
1181 PRInt32 ch
= Read();
1184 if (LookAhead('/')) {
1190 REPORT_UNEXPECTED_EOF(PECommentEOF
);
1196 nsCSSScanner::ParseCComment(nsCSSToken
& aToken
)
1198 nsString
& ident
= aToken
.mIdent
;
1200 PRInt32 ch
= Read();
1203 if (LookAhead('/')) {
1204 ident
.Append(PRUnichar(ch
));
1205 ident
.Append(PRUnichar('/'));
1209 #ifdef COLLECT_WHITESPACE
1210 ident
.Append(PRUnichar(ch
));
1213 aToken
.mType
= eCSSToken_WhiteSpace
;
1220 nsCSSScanner::ParseEOLComment(nsCSSToken
& aToken
)
1222 nsString
& ident
= aToken
.mIdent
;
1228 PRInt32 ch
= Read();
1232 #ifdef COLLECT_WHITESPACE
1233 ident
.Append(PRUnichar(ch
));
1236 aToken
.mType
= eCSSToken_WhiteSpace
;
1242 nsCSSScanner::ParseString(PRInt32 aStop
, nsCSSToken
& aToken
)
1244 aToken
.mIdent
.SetLength(0);
1245 aToken
.mType
= eCSSToken_String
;
1246 aToken
.mSymbol
= PRUnichar(aStop
); // remember how it's quoted
1248 // If nothing in pushback, first try to get as much as possible in one go
1249 if (!mPushbackCount
&& EnsureData()) {
1250 // See how much we can consume and append in one go
1251 PRUint32 n
= mOffset
;
1252 // Count number of characters that can be processed
1253 for (;n
< mCount
; ++n
) {
1254 PRUnichar nextChar
= mReadPointer
[n
];
1255 if ((nextChar
== aStop
) || (nextChar
== CSS_ESCAPE
) ||
1256 (nextChar
== '\n') || (nextChar
== '\r') || (nextChar
== '\f')) {
1259 #ifdef CSS_REPORT_PARSE_ERRORS
1260 if (nextChar
== '\t') {
1261 mColNumber
= ((mColNumber
- 1 + TAB_STOP_WIDTH
) / TAB_STOP_WIDTH
)
1268 // Add to the token what we have so far
1270 aToken
.mIdent
.Append(&mReadPointer
[mOffset
], n
- mOffset
);
1274 PRInt32 ch
= Read();
1275 if (ch
< 0 || ch
== aStop
) {
1279 aToken
.mType
= eCSSToken_Error
;
1280 #ifdef CSS_REPORT_PARSE_ERRORS
1281 ReportUnexpectedToken(aToken
, "SEUnterminatedString");
1285 if (ch
== CSS_ESCAPE
) {
1286 ParseAndAppendEscape(aToken
.mIdent
);
1288 aToken
.mIdent
.Append(ch
);