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 const PRUint8
nsCSSScanner::IS_DIGIT
= 0x01;
65 const PRUint8
nsCSSScanner::IS_HEX_DIGIT
= 0x02;
66 const PRUint8
nsCSSScanner::START_IDENT
= 0x04;
67 const PRUint8
nsCSSScanner::IS_IDENT
= 0x08;
68 const PRUint8
nsCSSScanner::IS_WHITESPACE
= 0x10;
70 static PRBool gLexTableSetup
= PR_FALSE
;
71 PRUint8
nsCSSScanner::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
;
82 nsCSSScanner::BuildLexTable()
84 gLexTableSetup
= PR_TRUE
;
86 PRUint8
* lt
= gLexTable
;
88 lt
[CSS_ESCAPE
] = START_IDENT
;
90 lt
['_'] |= IS_IDENT
| START_IDENT
;
91 lt
[' '] |= IS_WHITESPACE
; // space
92 lt
['\t'] |= IS_WHITESPACE
; // horizontal tab
93 lt
['\r'] |= IS_WHITESPACE
; // carriage return
94 lt
['\n'] |= IS_WHITESPACE
; // line feed
95 lt
['\f'] |= IS_WHITESPACE
; // form feed
96 for (i
= 161; i
<= 255; i
++) {
97 lt
[i
] |= IS_IDENT
| START_IDENT
;
99 for (i
= '0'; i
<= '9'; i
++) {
100 lt
[i
] |= IS_DIGIT
| IS_HEX_DIGIT
| IS_IDENT
;
102 for (i
= 'A'; i
<= 'Z'; i
++) {
103 if ((i
>= 'A') && (i
<= 'F')) {
104 lt
[i
] |= IS_HEX_DIGIT
;
105 lt
[i
+32] |= IS_HEX_DIGIT
;
107 lt
[i
] |= IS_IDENT
| START_IDENT
;
108 lt
[i
+32] |= IS_IDENT
| START_IDENT
;
112 nsCSSToken::nsCSSToken()
114 mType
= eCSSToken_Symbol
;
118 nsCSSToken::AppendToString(nsString
& aBuffer
)
121 case eCSSToken_AtKeyword
:
122 aBuffer
.Append(PRUnichar('@')); // fall through intentional
123 case eCSSToken_Ident
:
124 case eCSSToken_WhiteSpace
:
125 case eCSSToken_Function
:
127 case eCSSToken_InvalidURL
:
128 case eCSSToken_HTMLComment
:
129 aBuffer
.Append(mIdent
);
131 case eCSSToken_Number
:
133 aBuffer
.AppendInt(mInteger
, 10);
136 aBuffer
.AppendFloat(mNumber
);
139 case eCSSToken_Percentage
:
140 NS_ASSERTION(!mIntegerValid
, "How did a percentage token get this set?");
141 aBuffer
.AppendFloat(mNumber
* 100.0f
);
142 aBuffer
.Append(PRUnichar('%')); // STRING USE WARNING: technically, this should be |AppendWithConversion|
144 case eCSSToken_Dimension
:
146 aBuffer
.AppendInt(mInteger
, 10);
149 aBuffer
.AppendFloat(mNumber
);
151 aBuffer
.Append(mIdent
);
153 case eCSSToken_String
:
154 aBuffer
.Append(mSymbol
);
155 aBuffer
.Append(mIdent
); // fall through intentional
156 case eCSSToken_Symbol
:
157 aBuffer
.Append(mSymbol
);
161 aBuffer
.Append(PRUnichar('#'));
162 aBuffer
.Append(mIdent
);
164 case eCSSToken_Includes
:
165 aBuffer
.AppendLiteral("~=");
167 case eCSSToken_Dashmatch
:
168 aBuffer
.AppendLiteral("|=");
170 case eCSSToken_Beginsmatch
:
171 aBuffer
.AppendLiteral("^=");
173 case eCSSToken_Endsmatch
:
174 aBuffer
.AppendLiteral("$=");
176 case eCSSToken_Containsmatch
:
177 aBuffer
.AppendLiteral("*=");
179 case eCSSToken_Error
:
180 aBuffer
.Append(mSymbol
);
181 aBuffer
.Append(mIdent
);
184 NS_ERROR("invalid token type");
189 nsCSSScanner::nsCSSScanner()
190 : mInputStream(nsnull
)
191 , mReadPointer(nsnull
)
195 #ifdef CSS_REPORT_PARSE_ERRORS
196 , mError(mErrorBuf
, NS_ARRAY_LENGTH(mErrorBuf
), 0)
199 MOZ_COUNT_CTOR(nsCSSScanner
);
200 if (!gLexTableSetup
) {
201 // XXX need a monitor
204 mPushback
= mLocalPushback
;
205 mPushbackSize
= NS_ARRAY_LENGTH(mLocalPushback
);
206 // No need to init the other members, since they represent state
207 // which can get cleared. We'll init them every time Init() is
211 nsCSSScanner::~nsCSSScanner()
213 MOZ_COUNT_DTOR(nsCSSScanner
);
215 if (mLocalPushback
!= mPushback
) {
220 #ifdef CSS_REPORT_PARSE_ERRORS
221 #define CSS_ERRORS_PREF "layout.css.report_errors"
223 PR_STATIC_CALLBACK(int) CSSErrorsPrefChanged(const char *aPref
, void *aClosure
)
225 gReportErrors
= nsContentUtils::GetBoolPref(CSS_ERRORS_PREF
, PR_TRUE
);
230 /* static */ PRBool
nsCSSScanner::InitGlobals()
232 #ifdef CSS_REPORT_PARSE_ERRORS
233 if (gConsoleService
&& gScriptErrorFactory
)
236 nsresult rv
= CallGetService(NS_CONSOLESERVICE_CONTRACTID
, &gConsoleService
);
237 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
239 rv
= CallGetClassObject(NS_SCRIPTERROR_CONTRACTID
, &gScriptErrorFactory
);
240 NS_ENSURE_SUCCESS(rv
, PR_FALSE
);
241 NS_ASSERTION(gConsoleService
&& gScriptErrorFactory
,
242 "unexpected null pointer without failure");
244 nsContentUtils::RegisterPrefCallback(CSS_ERRORS_PREF
, CSSErrorsPrefChanged
, nsnull
);
245 CSSErrorsPrefChanged(CSS_ERRORS_PREF
, nsnull
);
250 /* static */ void nsCSSScanner::ReleaseGlobals()
252 #ifdef CSS_REPORT_PARSE_ERRORS
253 nsContentUtils::UnregisterPrefCallback(CSS_ERRORS_PREF
, CSSErrorsPrefChanged
, nsnull
);
254 NS_IF_RELEASE(gConsoleService
);
255 NS_IF_RELEASE(gScriptErrorFactory
);
256 NS_IF_RELEASE(gStringBundle
);
260 void nsCSSScanner::Init(nsIUnicharInputStream
* aInput
,
261 const PRUnichar
* aBuffer
, PRUint32 aCount
,
262 nsIURI
* aURI
, PRUint32 aLineNumber
)
264 NS_PRECONDITION(!mInputStream
, "Should not have an existing input stream!");
265 NS_PRECONDITION(!mReadPointer
, "Should not have an existing input buffer!");
267 // Read from stream via my own buffer
269 NS_PRECONDITION(!aBuffer
, "Shouldn't have both input and buffer!");
270 NS_PRECONDITION(aCount
== 0, "Shouldn't have count with a stream");
271 mInputStream
= aInput
;
272 mReadPointer
= mBuffer
;
275 NS_PRECONDITION(aBuffer
, "Either aInput or aBuffer must be set");
276 // Read directly from the provided buffer
277 mInputStream
= nsnull
;
278 mReadPointer
= aBuffer
;
282 #ifdef CSS_REPORT_PARSE_ERRORS
283 // If aURI is the same as mURI, no need to reget mFileName -- it
284 // shouldn't have changed.
288 aURI
->GetSpec(mFileName
);
290 mFileName
.Adopt(NS_strdup("from DOM"));
293 #endif // CSS_REPORT_PARSE_ERRORS
294 mLineNumber
= aLineNumber
;
296 // Reset variables that we use to keep track of our progress through the input
300 #ifdef CSS_REPORT_PARSE_ERRORS
305 #ifdef CSS_REPORT_PARSE_ERRORS
307 // @see REPORT_UNEXPECTED_EOF in nsCSSParser.cpp
308 #define REPORT_UNEXPECTED_EOF(lf_) \
309 ReportUnexpectedEOF(#lf_)
311 void nsCSSScanner::AddToError(const nsSubstring
& aErrorText
)
313 if (mError
.IsEmpty()) {
314 mErrorLineNumber
= mLineNumber
;
315 mErrorColNumber
= mColNumber
;
318 mError
.Append(NS_LITERAL_STRING(" ") + aErrorText
);
322 void nsCSSScanner::ClearError()
327 void nsCSSScanner::OutputError()
329 if (mError
.IsEmpty()) return;
331 // Log it to the Error console
333 if (InitGlobals() && gReportErrors
) {
335 nsCOMPtr
<nsIScriptError
> errorObject
=
336 do_CreateInstance(gScriptErrorFactory
, &rv
);
337 if (NS_SUCCEEDED(rv
)) {
338 rv
= errorObject
->Init(mError
.get(),
339 NS_ConvertUTF8toUTF16(mFileName
).get(),
343 nsIScriptError::warningFlag
,
345 if (NS_SUCCEEDED(rv
))
346 gConsoleService
->LogMessage(errorObject
);
352 static PRBool
InitStringBundle()
357 nsCOMPtr
<nsIStringBundleService
> sbs
=
358 do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
363 sbs
->CreateBundle("chrome://global/locale/css.properties", &gStringBundle
);
365 gStringBundle
= nsnull
;
372 #define ENSURE_STRINGBUNDLE \
373 PR_BEGIN_MACRO if (!InitStringBundle()) return; PR_END_MACRO
375 // aMessage must take no parameters
376 void nsCSSScanner::ReportUnexpected(const char* aMessage
)
381 gStringBundle
->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage
).get(),
386 void nsCSSScanner::ReportUnexpectedParams(const char* aMessage
,
387 const PRUnichar
**aParams
,
388 PRUint32 aParamsLength
)
390 NS_PRECONDITION(aParamsLength
> 0, "use the non-params version");
394 gStringBundle
->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage
).get(),
395 aParams
, aParamsLength
,
400 // aLookingFor is a plain string, not a format string
401 void nsCSSScanner::ReportUnexpectedEOF(const char* aLookingFor
)
405 nsXPIDLString innerStr
;
406 gStringBundle
->GetStringFromName(NS_ConvertASCIItoUTF16(aLookingFor
).get(),
407 getter_Copies(innerStr
));
409 const PRUnichar
*params
[] = {
413 gStringBundle
->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
414 params
, NS_ARRAY_LENGTH(params
),
419 // aLookingFor is a single character
420 void nsCSSScanner::ReportUnexpectedEOF(PRUnichar aLookingFor
)
424 const PRUnichar lookingForStr
[] = {
425 PRUnichar('\''), aLookingFor
, PRUnichar('\''), PRUnichar(0)
427 const PRUnichar
*params
[] = { lookingForStr
};
429 gStringBundle
->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
430 params
, NS_ARRAY_LENGTH(params
),
435 // aMessage must take 1 parameter (for the string representation of the
437 void nsCSSScanner::ReportUnexpectedToken(nsCSSToken
& tok
,
438 const char *aMessage
)
442 nsAutoString tokenString
;
443 tok
.AppendToString(tokenString
);
445 const PRUnichar
*params
[] = {
449 ReportUnexpectedParams(aMessage
, params
, NS_ARRAY_LENGTH(params
));
452 // aParams's first entry must be null, and we'll fill in the token
453 void nsCSSScanner::ReportUnexpectedTokenParams(nsCSSToken
& tok
,
454 const char* aMessage
,
455 const PRUnichar
**aParams
,
456 PRUint32 aParamsLength
)
458 NS_PRECONDITION(aParamsLength
> 1, "use the non-params version");
459 NS_PRECONDITION(aParams
[0] == nsnull
, "first param should be empty");
463 nsAutoString tokenString
;
464 tok
.AppendToString(tokenString
);
465 aParams
[0] = tokenString
.get();
467 ReportUnexpectedParams(aMessage
, aParams
, aParamsLength
);
472 #define REPORT_UNEXPECTED_EOF(lf_)
474 #endif // CSS_REPORT_PARSE_ERRORS
476 void nsCSSScanner::Close()
478 mInputStream
= nsnull
;
479 mReadPointer
= nsnull
;
481 // Clean things up so we don't hold on to memory if our parser gets recycled.
482 #ifdef CSS_REPORT_PARSE_ERRORS
483 mFileName
.Truncate();
487 if (mPushback
!= mLocalPushback
) {
489 mPushback
= mLocalPushback
;
490 mPushbackSize
= NS_ARRAY_LENGTH(mLocalPushback
);
494 #ifdef CSS_REPORT_PARSE_ERRORS
495 #define TAB_STOP_WIDTH 8
498 PRBool
nsCSSScanner::EnsureData(nsresult
& aErrorCode
)
500 if (mOffset
< mCount
)
505 aErrorCode
= mInputStream
->Read(mBuffer
, CSS_BUFFER_SIZE
, &mCount
);
506 if (NS_FAILED(aErrorCode
) || mCount
== 0) {
516 // Returns -1 on error or eof
517 PRInt32
nsCSSScanner::Read(nsresult
& aErrorCode
)
520 if (0 < mPushbackCount
) {
521 rv
= PRInt32(mPushback
[--mPushbackCount
]);
523 if (mOffset
== mCount
&& !EnsureData(aErrorCode
)) {
526 rv
= PRInt32(mReadPointer
[mOffset
++]);
527 // There are four types of newlines in CSS: "\r", "\n", "\r\n", and "\f".
528 // To simplify dealing with newlines, they are all normalized to "\n" here
530 if (EnsureData(aErrorCode
) && mReadPointer
[mOffset
] == '\n') {
534 } else if (rv
== '\f') {
538 // 0 is a magical line number meaning that we don't know (i.e., script)
539 if (mLineNumber
!= 0)
541 #ifdef CSS_REPORT_PARSE_ERRORS
545 #ifdef CSS_REPORT_PARSE_ERRORS
546 else if (rv
== '\t') {
547 mColNumber
= ((mColNumber
- 1 + TAB_STOP_WIDTH
) / TAB_STOP_WIDTH
)
549 } else if (rv
!= '\n') {
554 //printf("Read => %x\n", rv);
558 PRInt32
nsCSSScanner::Peek(nsresult
& aErrorCode
)
560 if (0 == mPushbackCount
) {
561 PRInt32 ch
= Read(aErrorCode
);
565 mPushback
[0] = PRUnichar(ch
);
568 //printf("Peek => %x\n", mLookAhead);
569 return PRInt32(mPushback
[mPushbackCount
- 1]);
572 void nsCSSScanner::Pushback(PRUnichar aChar
)
574 if (mPushbackCount
== mPushbackSize
) { // grow buffer
575 PRUnichar
* newPushback
= new PRUnichar
[mPushbackSize
+ 4];
576 if (nsnull
== newPushback
) {
580 memcpy(newPushback
, mPushback
, sizeof(PRUnichar
) * mPushbackCount
);
581 if (mPushback
!= mLocalPushback
) {
584 mPushback
= newPushback
;
586 mPushback
[mPushbackCount
++] = aChar
;
589 PRBool
nsCSSScanner::LookAhead(nsresult
& aErrorCode
, PRUnichar aChar
)
591 PRInt32 ch
= Read(aErrorCode
);
602 PRBool
nsCSSScanner::EatWhiteSpace(nsresult
& aErrorCode
)
604 PRBool eaten
= PR_FALSE
;
606 PRInt32 ch
= Read(aErrorCode
);
610 if ((ch
== ' ') || (ch
== '\n') || (ch
== '\t')) {
620 PRBool
nsCSSScanner::EatNewline(nsresult
& aErrorCode
)
622 PRInt32 ch
= Read(aErrorCode
);
626 PRBool eaten
= PR_FALSE
;
635 PRBool
nsCSSScanner::Next(nsresult
& aErrorCode
, nsCSSToken
& aToken
)
637 PRInt32 ch
= Read(aErrorCode
);
643 if (StartsIdent(ch
, Peek(aErrorCode
)))
644 return ParseIdent(aErrorCode
, ch
, aToken
);
648 PRInt32 nextChar
= Read(aErrorCode
);
650 PRInt32 followingChar
= Peek(aErrorCode
);
652 if (StartsIdent(nextChar
, followingChar
))
653 return ParseAtKeyword(aErrorCode
, ch
, aToken
);
658 if ((ch
== '.') || (ch
== '+') || (ch
== '-')) {
659 PRInt32 nextChar
= Peek(aErrorCode
);
660 if (IsDigit(nextChar
)) {
661 return ParseNumber(aErrorCode
, ch
, aToken
);
663 else if (('.' == nextChar
) && ('.' != ch
)) {
664 nextChar
= Read(aErrorCode
);
665 PRInt32 followingChar
= Peek(aErrorCode
);
667 if (IsDigit(followingChar
))
668 return ParseNumber(aErrorCode
, ch
, aToken
);
672 return ParseNumber(aErrorCode
, ch
, aToken
);
677 return ParseRef(aErrorCode
, ch
, aToken
);
681 if ((ch
== '"') || (ch
== '\'')) {
682 return ParseString(aErrorCode
, ch
, aToken
);
686 if (IsWhitespace(ch
)) {
687 aToken
.mType
= eCSSToken_WhiteSpace
;
688 aToken
.mIdent
.Assign(PRUnichar(ch
));
689 (void) EatWhiteSpace(aErrorCode
);
693 PRInt32 nextChar
= Peek(aErrorCode
);
694 if (nextChar
== '*') {
695 (void) Read(aErrorCode
);
697 // If we change our storage data structures such that comments are
698 // stored (for Editor), we should reenable this code, condition it
699 // on being in editor mode, and apply glazou's patch from bug
701 aToken
.mIdent
.SetCapacity(2);
702 aToken
.mIdent
.Assign(PRUnichar(ch
));
703 aToken
.mIdent
.Append(PRUnichar(nextChar
));
704 return ParseCComment(aErrorCode
, aToken
);
706 return SkipCComment(aErrorCode
) && Next(aErrorCode
, aToken
);
709 if (ch
== '<') { // consume HTML comment tags
710 if (LookAhead(aErrorCode
, '!')) {
711 if (LookAhead(aErrorCode
, '-')) {
712 if (LookAhead(aErrorCode
, '-')) {
713 aToken
.mType
= eCSSToken_HTMLComment
;
714 aToken
.mIdent
.AssignLiteral("<!--");
722 if (ch
== '-') { // check for HTML comment end
723 if (LookAhead(aErrorCode
, '-')) {
724 if (LookAhead(aErrorCode
, '>')) {
725 aToken
.mType
= eCSSToken_HTMLComment
;
726 aToken
.mIdent
.AssignLiteral("-->");
733 // INCLUDES ("~=") and DASHMATCH ("|=")
734 if (( ch
== '|' ) || ( ch
== '~' ) || ( ch
== '^' ) ||
735 ( ch
== '$' ) || ( ch
== '*' )) {
736 PRInt32 nextChar
= Read(aErrorCode
);
737 if ( nextChar
== '=' ) {
739 aToken
.mType
= eCSSToken_Includes
;
741 else if (ch
== '|') {
742 aToken
.mType
= eCSSToken_Dashmatch
;
744 else if (ch
== '^') {
745 aToken
.mType
= eCSSToken_Beginsmatch
;
747 else if (ch
== '$') {
748 aToken
.mType
= eCSSToken_Endsmatch
;
750 else if (ch
== '*') {
751 aToken
.mType
= eCSSToken_Containsmatch
;
754 } else if (nextChar
>= 0) {
758 aToken
.mType
= eCSSToken_Symbol
;
763 PRBool
nsCSSScanner::NextURL(nsresult
& aErrorCode
, nsCSSToken
& aToken
)
765 PRInt32 ch
= Read(aErrorCode
);
771 if ((ch
== '"') || (ch
== '\'')) {
772 return ParseString(aErrorCode
, ch
, aToken
);
776 if (IsWhitespace(ch
)) {
777 aToken
.mType
= eCSSToken_WhiteSpace
;
778 aToken
.mIdent
.Assign(PRUnichar(ch
));
779 (void) EatWhiteSpace(aErrorCode
);
783 PRInt32 nextChar
= Peek(aErrorCode
);
784 if (nextChar
== '*') {
785 (void) Read(aErrorCode
);
787 // If we change our storage data structures such that comments are
788 // stored (for Editor), we should reenable this code, condition it
789 // on being in editor mode, and apply glazou's patch from bug
791 aToken
.mIdent
.SetCapacity(2);
792 aToken
.mIdent
.Assign(PRUnichar(ch
));
793 aToken
.mIdent
.Append(PRUnichar(nextChar
));
794 return ParseCComment(aErrorCode
, aToken
);
796 return SkipCComment(aErrorCode
) && Next(aErrorCode
, aToken
);
800 // Process a url lexical token. A CSS1 url token can contain
801 // characters beyond identifier characters (e.g. '/', ':', etc.)
802 // Because of this the normal rules for tokenizing the input don't
803 // apply very well. To simplify the parser and relax some of the
804 // requirements on the scanner we parse url's here. If we find a
805 // malformed URL then we emit a token of type "InvalidURL" so that
806 // the CSS1 parser can ignore the invalid input. We attempt to eat
807 // the right amount of input data when an invalid URL is presented.
809 aToken
.mType
= eCSSToken_InvalidURL
;
810 nsString
& ident
= aToken
.mIdent
;
815 // empty url spec; just get out of here
816 aToken
.mType
= eCSSToken_URL
;
818 // start of a non-quoted url
822 ch
= Read(aErrorCode
);
824 if (ch
== CSS_ESCAPE
) {
825 ParseAndAppendEscape(aErrorCode
, ident
);
826 } else if ((ch
== '"') || (ch
== '\'') || (ch
== '(')) {
827 // This is an invalid URL spec
829 } else if (IsWhitespace(ch
)) {
830 // Whitespace is allowed at the end of the URL
831 (void) EatWhiteSpace(aErrorCode
);
832 if (LookAhead(aErrorCode
, ')')) {
833 Pushback(')'); // leave the closing symbol
837 // Whitespace is followed by something other than a
838 // ")". This is an invalid url spec.
840 } else if (ch
== ')') {
845 // A regular url character.
846 ident
.Append(PRUnichar(ch
));
850 // If the result of the above scanning is ok then change the token
851 // type to a useful one.
853 aToken
.mType
= eCSSToken_URL
;
861 nsCSSScanner::ParseAndAppendEscape(nsresult
& aErrorCode
, nsString
& aOutput
)
863 PRInt32 ch
= Peek(aErrorCode
);
865 aOutput
.Append(CSS_ESCAPE
);
868 if (IsHexDigit(ch
)) {
871 for (i
= 0; i
< 6; i
++) { // up to six digits
872 ch
= Read(aErrorCode
);
874 // Whoops: error or premature eof
877 if (!IsHexDigit(ch
) && !IsWhitespace(ch
)) {
880 } else if (IsHexDigit(ch
)) {
882 rv
= rv
* 16 + (ch
- '0');
884 // Note: c&7 just keeps the low three bits which causes
885 // upper and lower case alphabetics to both yield their
886 // "relative to 10" value for computing the hex value.
887 rv
= rv
* 16 + ((ch
& 0x7) + 9);
890 NS_ASSERTION(IsWhitespace(ch
), "bad control flow");
891 // single space ends escape
895 if (6 == i
) { // look for trailing whitespace and eat it
896 ch
= Peek(aErrorCode
);
897 if (IsWhitespace(ch
)) {
898 ch
= Read(aErrorCode
);
901 NS_ASSERTION(rv
>= 0, "How did rv become negative?");
903 AppendUCS4ToUTF16(ENSURE_VALID_CHAR(rv
), aOutput
);
907 // "Any character except a hexidecimal digit can be escaped to
908 // remove its special meaning by putting a backslash in front"
909 // -- CSS1 spec section 7.1
910 if (!EatNewline(aErrorCode
)) { // skip escaped newline
911 (void) Read(aErrorCode
);
921 * Gather up the characters in an identifier. The identfier was
922 * started by "aChar" which will be appended to aIdent. The result
923 * will be aIdent with all of the identifier characters appended
924 * until the first non-identifier character is seen. The termination
925 * character is unread for the future re-reading.
927 PRBool
nsCSSScanner::GatherIdent(nsresult
& aErrorCode
, PRInt32 aChar
,
930 if (aChar
== CSS_ESCAPE
) {
931 ParseAndAppendEscape(aErrorCode
, aIdent
);
933 else if (0 < aChar
) {
934 aIdent
.Append(aChar
);
937 // If nothing in pushback, first try to get as much as possible in one go
938 if (!mPushbackCount
&& EnsureData(aErrorCode
)) {
939 // See how much we can consume and append in one go
940 PRUint32 n
= mOffset
;
941 // Count number of Ident characters that can be processed
942 while (n
< mCount
&& IsIdent(mReadPointer
[n
])) {
945 // Add to the token what we have so far
947 #ifdef CSS_REPORT_PARSE_ERRORS
948 mColNumber
+= n
- mOffset
;
950 aIdent
.Append(&mReadPointer
[mOffset
], n
- mOffset
);
955 aChar
= Read(aErrorCode
);
956 if (aChar
< 0) break;
957 if (aChar
== CSS_ESCAPE
) {
958 ParseAndAppendEscape(aErrorCode
, aIdent
);
959 } else if (IsIdent(aChar
)) {
960 aIdent
.Append(PRUnichar(aChar
));
969 PRBool
nsCSSScanner::ParseRef(nsresult
& aErrorCode
,
973 aToken
.mIdent
.SetLength(0);
974 aToken
.mType
= eCSSToken_Ref
;
975 PRInt32 ch
= Read(aErrorCode
);
979 if (IsIdent(ch
) || ch
== CSS_ESCAPE
) {
980 // First char after the '#' is a valid ident char (or an escape),
981 // so it makes sense to keep going
982 if (StartsIdent(ch
, Peek(aErrorCode
))) {
983 aToken
.mType
= eCSSToken_ID
;
985 return GatherIdent(aErrorCode
, ch
, aToken
.mIdent
);
988 // No ident chars after the '#'. Just unread |ch| and get out of here.
993 PRBool
nsCSSScanner::ParseIdent(nsresult
& aErrorCode
,
997 nsString
& ident
= aToken
.mIdent
;
999 if (!GatherIdent(aErrorCode
, aChar
, ident
)) {
1003 nsCSSTokenType tokenType
= eCSSToken_Ident
;
1004 // look for functions (ie: "ident(")
1005 if (PRUnichar('(') == PRUnichar(Peek(aErrorCode
))) { // this is a function definition
1006 tokenType
= eCSSToken_Function
;
1009 aToken
.mType
= tokenType
;
1013 PRBool
nsCSSScanner::ParseAtKeyword(nsresult
& aErrorCode
, PRInt32 aChar
,
1016 aToken
.mIdent
.SetLength(0);
1017 aToken
.mType
= eCSSToken_AtKeyword
;
1018 return GatherIdent(aErrorCode
, 0, aToken
.mIdent
);
1021 PRBool
nsCSSScanner::ParseNumber(nsresult
& aErrorCode
, PRInt32 c
,
1024 nsString
& ident
= aToken
.mIdent
;
1026 PRBool gotDot
= (c
== '.');
1027 aToken
.mHasSign
= (c
== '+' || c
== '-');
1029 ident
.Append(PRUnichar(c
));
1032 // Gather up characters that make up the number
1033 PRBool gotE
= PR_FALSE
;
1035 c
= Read(aErrorCode
);
1037 if (!gotDot
&& !gotE
&& (c
== '.') &&
1038 IsDigit(Peek(aErrorCode
))) {
1041 } else if (!gotE
&& (c
== 'e' || c
== 'E')) {
1045 PRInt32 nextChar
= Peek(aErrorCode
);
1047 if (nextChar
== '-' || nextChar
== '+') {
1048 sign
= Read(aErrorCode
);
1049 nextChar
= Peek(aErrorCode
);
1051 if (IsDigit(nextChar
)) {
1054 ident
.Append(PRUnichar(c
));
1064 } else if (!IsDigit(c
)) {
1067 ident
.Append(PRUnichar(c
));
1070 // Convert number to floating point
1071 nsCSSTokenType type
= eCSSToken_Number
;
1073 float value
= ident
.ToFloat(&ec
);
1075 // Set mIntegerValid for all cases (except %, below) because we need
1076 // it for the "2n" in :nth-child(2n).
1077 aToken
.mIntegerValid
= PR_FALSE
;
1078 if (!gotDot
&& !gotE
) {
1079 aToken
.mInteger
= ident
.ToInteger(&ec
);
1080 aToken
.mIntegerValid
= PR_TRUE
;
1084 // Look at character that terminated the number
1086 if (StartsIdent(c
, Peek(aErrorCode
))) {
1087 if (!GatherIdent(aErrorCode
, c
, ident
)) {
1090 type
= eCSSToken_Dimension
;
1091 } else if ('%' == c
) {
1092 type
= eCSSToken_Percentage
;
1093 value
= value
/ 100.0f
;
1094 aToken
.mIntegerValid
= PR_FALSE
;
1096 // Put back character that stopped numeric scan
1100 aToken
.mNumber
= value
;
1101 aToken
.mType
= type
;
1105 PRBool
nsCSSScanner::SkipCComment(nsresult
& aErrorCode
)
1108 PRInt32 ch
= Read(aErrorCode
);
1111 if (LookAhead(aErrorCode
, '/')) {
1117 REPORT_UNEXPECTED_EOF(PECommentEOF
);
1122 PRBool
nsCSSScanner::ParseCComment(nsresult
& aErrorCode
, nsCSSToken
& aToken
)
1124 nsString
& ident
= aToken
.mIdent
;
1126 PRInt32 ch
= Read(aErrorCode
);
1129 if (LookAhead(aErrorCode
, '/')) {
1130 ident
.Append(PRUnichar(ch
));
1131 ident
.Append(PRUnichar('/'));
1135 #ifdef COLLECT_WHITESPACE
1136 ident
.Append(PRUnichar(ch
));
1139 aToken
.mType
= eCSSToken_WhiteSpace
;
1145 PRBool
nsCSSScanner::ParseEOLComment(nsresult
& aErrorCode
, nsCSSToken
& aToken
)
1147 nsString
& ident
= aToken
.mIdent
;
1150 if (EatNewline(aErrorCode
)) {
1153 PRInt32 ch
= Read(aErrorCode
);
1157 #ifdef COLLECT_WHITESPACE
1158 ident
.Append(PRUnichar(ch
));
1161 aToken
.mType
= eCSSToken_WhiteSpace
;
1166 PRBool
nsCSSScanner::ParseString(nsresult
& aErrorCode
, PRInt32 aStop
,
1169 aToken
.mIdent
.SetLength(0);
1170 aToken
.mType
= eCSSToken_String
;
1171 aToken
.mSymbol
= PRUnichar(aStop
); // remember how it's quoted
1173 // If nothing in pushback, first try to get as much as possible in one go
1174 if (!mPushbackCount
&& EnsureData(aErrorCode
)) {
1175 // See how much we can consume and append in one go
1176 PRUint32 n
= mOffset
;
1177 // Count number of characters that can be processed
1178 for (;n
< mCount
; ++n
) {
1179 PRUnichar nextChar
= mReadPointer
[n
];
1180 if ((nextChar
== aStop
) || (nextChar
== CSS_ESCAPE
) ||
1181 (nextChar
== '\n') || (nextChar
== '\r') || (nextChar
== '\f')) {
1184 #ifdef CSS_REPORT_PARSE_ERRORS
1185 if (nextChar
== '\t') {
1186 mColNumber
= ((mColNumber
- 1 + TAB_STOP_WIDTH
) / TAB_STOP_WIDTH
)
1193 // Add to the token what we have so far
1195 aToken
.mIdent
.Append(&mReadPointer
[mOffset
], n
- mOffset
);
1199 PRInt32 ch
= Read(aErrorCode
);
1200 if (ch
< 0 || ch
== aStop
) {
1204 aToken
.mType
= eCSSToken_Error
;
1205 #ifdef CSS_REPORT_PARSE_ERRORS
1206 ReportUnexpectedToken(aToken
, "SEUnterminatedString");
1210 if (ch
== CSS_ESCAPE
) {
1211 ParseAndAppendEscape(aErrorCode
, aToken
.mIdent
);
1213 aToken
.mIdent
.Append(ch
);