Back to index

lightning-sunbird  0.9+nobinonly
nsCSSScanner.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   L. David Baron <dbaron@dbaron.org>
00024  *   Daniel Glazman <glazman@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 #include "nsCSSScanner.h"
00040 #include "nsIFactory.h"
00041 #include "nsIInputStream.h"
00042 #include "nsIUnicharInputStream.h"
00043 #include "nsString.h"
00044 #include "nsCRT.h"
00045 
00046 // for #ifdef CSS_REPORT_PARSE_ERRORS
00047 #include "nsCOMPtr.h"
00048 #include "nsIServiceManager.h"
00049 #include "nsIComponentManager.h"
00050 #include "nsReadableUtils.h"
00051 #include "nsIURI.h"
00052 #include "nsIConsoleService.h"
00053 #include "nsIScriptError.h"
00054 #include "nsIStringBundle.h"
00055 #include "nsContentUtils.h"
00056 
00057 // Don't bother collecting whitespace characters in token's mIdent buffer
00058 #undef COLLECT_WHITESPACE
00059 
00060 #define BUFFER_SIZE 256
00061 
00062 static const PRUnichar CSS_ESCAPE = PRUnichar('\\');
00063 const PRUint8 nsCSSScanner::IS_DIGIT = 0x01;
00064 const PRUint8 nsCSSScanner::IS_HEX_DIGIT = 0x02;
00065 const PRUint8 nsCSSScanner::START_IDENT = 0x04;
00066 const PRUint8 nsCSSScanner::IS_IDENT = 0x08;
00067 const PRUint8 nsCSSScanner::IS_WHITESPACE = 0x10;
00068 
00069 static PRBool gLexTableSetup = PR_FALSE;
00070 PRUint8 nsCSSScanner::gLexTable[256];
00071 
00072 #ifdef CSS_REPORT_PARSE_ERRORS
00073 static PRBool gReportErrors = PR_TRUE;
00074 static nsIConsoleService *gConsoleService;
00075 static nsIFactory *gScriptErrorFactory;
00076 static nsIStringBundle *gStringBundle;
00077 #endif
00078 
00079 /* static */
00080 void
00081 nsCSSScanner::BuildLexTable()
00082 {
00083   gLexTableSetup = PR_TRUE;
00084 
00085   PRUint8* lt = gLexTable;
00086   int i;
00087   lt[CSS_ESCAPE] = START_IDENT;
00088   lt['-'] |= IS_IDENT;
00089   lt['_'] |= IS_IDENT | START_IDENT;
00090   // XXX add in other whitespace chars
00091   lt[' '] |= IS_WHITESPACE;   // space
00092   lt['\t'] |= IS_WHITESPACE;  // horizontal tab
00093   lt['\v'] |= IS_WHITESPACE;  // vertical tab
00094   lt['\r'] |= IS_WHITESPACE;  // carriage return
00095   lt['\n'] |= IS_WHITESPACE;  // line feed
00096   lt['\f'] |= IS_WHITESPACE;  // form feed
00097   for (i = 161; i <= 255; i++) {
00098     lt[i] |= IS_IDENT | START_IDENT;
00099   }
00100   for (i = '0'; i <= '9'; i++) {
00101     lt[i] |= IS_DIGIT | IS_HEX_DIGIT | IS_IDENT;
00102   }
00103   for (i = 'A'; i <= 'Z'; i++) {
00104     if ((i >= 'A') && (i <= 'F')) {
00105       lt[i] |= IS_HEX_DIGIT;
00106       lt[i+32] |= IS_HEX_DIGIT;
00107     }
00108     lt[i] |= IS_IDENT | START_IDENT;
00109     lt[i+32] |= IS_IDENT | START_IDENT;
00110   }
00111 }
00112 
00113 nsCSSToken::nsCSSToken()
00114 {
00115   mType = eCSSToken_Symbol;
00116 }
00117 
00118 void 
00119 nsCSSToken::AppendToString(nsString& aBuffer)
00120 {
00121   switch (mType) {
00122     case eCSSToken_AtKeyword:
00123       aBuffer.Append(PRUnichar('@')); // fall through intentional
00124     case eCSSToken_Ident:
00125     case eCSSToken_WhiteSpace:
00126     case eCSSToken_Function:
00127     case eCSSToken_URL:
00128     case eCSSToken_InvalidURL:
00129     case eCSSToken_HTMLComment:
00130       aBuffer.Append(mIdent);
00131       break;
00132     case eCSSToken_Number:
00133       if (mIntegerValid) {
00134         aBuffer.AppendInt(mInteger, 10);
00135       }
00136       else {
00137         aBuffer.AppendFloat(mNumber);
00138       }
00139       break;
00140     case eCSSToken_Percentage:
00141       NS_ASSERTION(!mIntegerValid, "How did a percentage token get this set?");
00142       aBuffer.AppendFloat(mNumber * 100.0f);
00143       aBuffer.Append(PRUnichar('%')); // STRING USE WARNING: technically, this should be |AppendWithConversion|
00144       break;
00145     case eCSSToken_Dimension:
00146       if (mIntegerValid) {
00147         aBuffer.AppendInt(mInteger, 10);
00148       }
00149       else {
00150         aBuffer.AppendFloat(mNumber);
00151       }
00152       aBuffer.Append(mIdent);
00153       break;
00154     case eCSSToken_String:
00155       aBuffer.Append(mSymbol);
00156       aBuffer.Append(mIdent); // fall through intentional
00157     case eCSSToken_Symbol:
00158       aBuffer.Append(mSymbol);
00159       break;
00160     case eCSSToken_ID:
00161     case eCSSToken_Ref:
00162       aBuffer.Append(PRUnichar('#'));
00163       aBuffer.Append(mIdent);
00164       break;
00165     case eCSSToken_Includes:
00166       aBuffer.AppendLiteral("~=");
00167       break;
00168     case eCSSToken_Dashmatch:
00169       aBuffer.AppendLiteral("|=");
00170       break;
00171     case eCSSToken_Error:
00172       aBuffer.Append(mSymbol);
00173       aBuffer.Append(mIdent);
00174       break;
00175     default:
00176       NS_ERROR("invalid token type");
00177       break;
00178   }
00179 }
00180 
00181 MOZ_DECL_CTOR_COUNTER(nsCSSScanner)
00182 
00183 nsCSSScanner::nsCSSScanner()
00184 #ifdef CSS_REPORT_PARSE_ERRORS
00185   : mError(mErrorBuf, NS_ARRAY_LENGTH(mErrorBuf), 0)
00186 #endif
00187 {
00188   MOZ_COUNT_CTOR(nsCSSScanner);
00189   if (!gLexTableSetup) {
00190     // XXX need a monitor
00191     BuildLexTable();
00192   }
00193   mBuffer = new PRUnichar[BUFFER_SIZE];
00194   mPushback = mLocalPushback;
00195   mPushbackSize = 4;
00196   // No need to init the other members, since they represent state
00197   // which can get cleared.  We'll init them every time Init() is
00198   // called.
00199 }
00200 
00201 nsCSSScanner::~nsCSSScanner()
00202 {
00203   MOZ_COUNT_DTOR(nsCSSScanner);
00204   Close();
00205   if (nsnull != mBuffer) {
00206     delete [] mBuffer;
00207     mBuffer = nsnull;
00208   }
00209   if (mLocalPushback != mPushback) {
00210     delete [] mPushback;
00211   }
00212 }
00213 
00214 #ifdef CSS_REPORT_PARSE_ERRORS
00215 #define CSS_ERRORS_PREF "layout.css.report_errors"
00216 
00217 PR_STATIC_CALLBACK(int) CSSErrorsPrefChanged(const char *aPref, void *aClosure)
00218 {
00219   gReportErrors = nsContentUtils::GetBoolPref(CSS_ERRORS_PREF, PR_TRUE);
00220   return NS_OK;
00221 }
00222 #endif
00223 
00224 /* static */ PRBool nsCSSScanner::InitGlobals()
00225 {
00226 #ifdef CSS_REPORT_PARSE_ERRORS
00227   if (gConsoleService && gScriptErrorFactory)
00228     return PR_TRUE;
00229   
00230   nsresult rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &gConsoleService);
00231   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00232 
00233   rv = CallGetClassObject(NS_SCRIPTERROR_CONTRACTID, &gScriptErrorFactory);
00234   NS_ENSURE_SUCCESS(rv, PR_FALSE);
00235   NS_ASSERTION(gConsoleService && gScriptErrorFactory,
00236                "unexpected null pointer without failure");
00237 
00238   nsContentUtils::RegisterPrefCallback(CSS_ERRORS_PREF, CSSErrorsPrefChanged, nsnull);
00239   CSSErrorsPrefChanged(CSS_ERRORS_PREF, nsnull);
00240 #endif
00241   return PR_TRUE;
00242 }
00243 
00244 /* static */ void nsCSSScanner::ReleaseGlobals()
00245 {
00246 #ifdef CSS_REPORT_PARSE_ERRORS
00247   nsContentUtils::UnregisterPrefCallback(CSS_ERRORS_PREF, CSSErrorsPrefChanged, nsnull);
00248   NS_IF_RELEASE(gConsoleService);
00249   NS_IF_RELEASE(gScriptErrorFactory);
00250   NS_IF_RELEASE(gStringBundle);
00251 #endif
00252 }
00253 
00254 void nsCSSScanner::Init(nsIUnicharInputStream* aInput, nsIURI* aURI,
00255                         PRUint32 aLineNumber)
00256 {
00257   NS_PRECONDITION(aInput, "Null input stream pointer");
00258   NS_PRECONDITION(!mInput, "Should not have an existing input stream!");
00259 
00260   mInput = aInput;
00261 
00262 #ifdef CSS_REPORT_PARSE_ERRORS
00263   // If aURI is the same as mURI, no need to reget mFileName -- it
00264   // shouldn't have changed.
00265   if (aURI != mURI) {
00266     mURI = aURI;
00267     if (aURI) {
00268       aURI->GetSpec(mFileName);
00269     } else {
00270       mFileName.Adopt(nsCRT::strdup("from DOM"));
00271     }
00272   }
00273 #endif // CSS_REPORT_PARSE_ERRORS
00274   mLineNumber = aLineNumber;
00275 
00276   // Reset variables that we use to keep track of our progress through mInput
00277   mOffset = 0;
00278   mCount = 0;
00279   mPushbackCount = 0;
00280   mLastRead = 0;
00281 
00282 #ifdef CSS_REPORT_PARSE_ERRORS
00283   mColNumber = 0;
00284 #endif
00285 
00286   // Note that we do NOT want to change mBuffer, mPushback, or
00287   // mPushbackCount here.  We can keep using the existing values even
00288   // if the input stream we're using has changed.
00289 }
00290 
00291 #ifdef CSS_REPORT_PARSE_ERRORS
00292 
00293 // @see REPORT_UNEXPECTED_EOF in nsCSSParser.cpp
00294 #define REPORT_UNEXPECTED_EOF(lf_) \
00295   ReportUnexpectedEOF(#lf_)
00296 
00297 void nsCSSScanner::AddToError(const nsSubstring& aErrorText)
00298 {
00299   if (mError.IsEmpty()) {
00300     mErrorLineNumber = mLineNumber;
00301     mErrorColNumber = mColNumber;
00302     mError = aErrorText;
00303   } else {
00304     mError.Append(NS_LITERAL_STRING("  ") + aErrorText);
00305   }
00306 }
00307 
00308 void nsCSSScanner::ClearError()
00309 {
00310   mError.Truncate();
00311 }
00312 
00313 void nsCSSScanner::OutputError()
00314 {
00315   if (mError.IsEmpty()) return;
00316  
00317 #ifdef DEBUG
00318   fprintf(stderr, "CSS Error (%s :%u.%u): %s\n",
00319                   mFileName.get(), mErrorLineNumber, mErrorColNumber,
00320                   NS_ConvertUCS2toUTF8(mError).get());
00321 #endif
00322 
00323   // Log it to the Error console
00324 
00325   if (InitGlobals() && gReportErrors) {
00326     nsresult rv;
00327     nsCOMPtr<nsIScriptError> errorObject =
00328       do_CreateInstance(gScriptErrorFactory, &rv);
00329     if (NS_SUCCEEDED(rv)) {
00330       rv = errorObject->Init(mError.get(),
00331                              NS_ConvertASCIItoUCS2(mFileName.get()).get(),
00332                              EmptyString().get(),
00333                              mErrorLineNumber,
00334                              mErrorColNumber,
00335                              nsIScriptError::warningFlag,
00336                              "CSS Parser");
00337       if (NS_SUCCEEDED(rv))
00338         gConsoleService->LogMessage(errorObject);
00339     }
00340   }
00341   ClearError();
00342 }
00343 
00344 static PRBool InitStringBundle()
00345 {
00346   if (gStringBundle)
00347     return PR_TRUE;
00348 
00349   nsCOMPtr<nsIStringBundleService> sbs =
00350     do_GetService(NS_STRINGBUNDLE_CONTRACTID);
00351   if (!sbs)
00352     return PR_FALSE;
00353 
00354   nsresult rv = 
00355     sbs->CreateBundle("chrome://global/locale/css.properties", &gStringBundle);
00356   if (NS_FAILED(rv)) {
00357     gStringBundle = nsnull;
00358     return PR_FALSE;
00359   }
00360 
00361   return PR_TRUE;
00362 }
00363 
00364 #define ENSURE_STRINGBUNDLE \
00365   PR_BEGIN_MACRO if (!InitStringBundle()) return; PR_END_MACRO
00366 
00367 // aMessage must take no parameters
00368 void nsCSSScanner::ReportUnexpected(const char* aMessage)
00369 {
00370   ENSURE_STRINGBUNDLE;
00371 
00372   nsXPIDLString str;
00373   gStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
00374                                    getter_Copies(str));
00375   AddToError(str);
00376 }
00377   
00378 void nsCSSScanner::ReportUnexpectedParams(const char* aMessage,
00379                                           const PRUnichar **aParams,
00380                                           PRUint32 aParamsLength)
00381 {
00382   NS_PRECONDITION(aParamsLength > 0, "use the non-params version");
00383   ENSURE_STRINGBUNDLE;
00384 
00385   nsXPIDLString str;
00386   gStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
00387                                       aParams, aParamsLength,
00388                                       getter_Copies(str));
00389   AddToError(str);
00390 }
00391 
00392 // aMessage must take no parameters
00393 void nsCSSScanner::ReportUnexpectedEOF(const char* aLookingFor)
00394 {
00395   ENSURE_STRINGBUNDLE;
00396 
00397   nsXPIDLString innerStr;
00398   gStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aLookingFor).get(),
00399                                    getter_Copies(innerStr));
00400 
00401   const PRUnichar *params[] = {
00402     innerStr.get()
00403   };
00404   nsXPIDLString str;
00405   gStringBundle->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF").get(),
00406                                       params, NS_ARRAY_LENGTH(params),
00407                                       getter_Copies(str));
00408   AddToError(str);
00409 }
00410 
00411 // aMessage must take 1 parameter (for the string representation of the
00412 // unexpected token)
00413 void nsCSSScanner::ReportUnexpectedToken(nsCSSToken& tok,
00414                                          const char *aMessage)
00415 {
00416   ENSURE_STRINGBUNDLE;
00417   
00418   nsAutoString tokenString;
00419   tok.AppendToString(tokenString);
00420 
00421   const PRUnichar *params[] = {
00422     tokenString.get()
00423   };
00424 
00425   ReportUnexpectedParams(aMessage, params, NS_ARRAY_LENGTH(params));
00426 }
00427 
00428 // aParams's first entry must be null, and we'll fill in the token
00429 void nsCSSScanner::ReportUnexpectedTokenParams(nsCSSToken& tok,
00430                                                const char* aMessage,
00431                                                const PRUnichar **aParams,
00432                                                PRUint32 aParamsLength)
00433 {
00434   NS_PRECONDITION(aParamsLength > 1, "use the non-params version");
00435   NS_PRECONDITION(aParams[0] == nsnull, "first param should be empty");
00436 
00437   ENSURE_STRINGBUNDLE;
00438   
00439   nsAutoString tokenString;
00440   tok.AppendToString(tokenString);
00441   aParams[0] = tokenString.get();
00442 
00443   ReportUnexpectedParams(aMessage, aParams, aParamsLength);
00444 }
00445 
00446 #else
00447 
00448 #define REPORT_UNEXPECTED_EOF(lf_)
00449 
00450 #endif // CSS_REPORT_PARSE_ERRORS
00451 
00452 void nsCSSScanner::Close()
00453 {
00454   mInput = nsnull;
00455 }
00456 
00457 #ifdef CSS_REPORT_PARSE_ERRORS
00458 #define TAB_STOP_WIDTH 8
00459 #endif
00460 
00461 // Returns -1 on error or eof
00462 PRInt32 nsCSSScanner::Read(nsresult& aErrorCode)
00463 {
00464   PRInt32 rv;
00465   if (0 < mPushbackCount) {
00466     rv = PRInt32(mPushback[--mPushbackCount]);
00467   } else {
00468     if (mCount < 0) {
00469       return -1;
00470     }
00471     if (mOffset == mCount) {
00472       mOffset = 0;
00473       aErrorCode = mInput->Read(mBuffer, BUFFER_SIZE, (PRUint32*)&mCount);
00474       if (NS_FAILED(aErrorCode) || mCount == 0) {
00475         mCount = 0;
00476         return -1;
00477       }
00478     }
00479     rv = PRInt32(mBuffer[mOffset++]);
00480     if (((rv == '\n') && (mLastRead != '\r')) || (rv == '\r')) {
00481       // 0 is a magical line number meaning that we don't know (i.e., script)
00482       if (mLineNumber != 0)
00483         ++mLineNumber;
00484 #ifdef CSS_REPORT_PARSE_ERRORS
00485       mColNumber = 0;
00486 #endif
00487     } 
00488 #ifdef CSS_REPORT_PARSE_ERRORS
00489     else if (rv == '\t') {
00490       mColNumber = ((mColNumber - 1 + TAB_STOP_WIDTH) / TAB_STOP_WIDTH)
00491                    * TAB_STOP_WIDTH;
00492     } else if (rv != '\n') {
00493       mColNumber++;
00494     }
00495 #endif
00496   }
00497   mLastRead = rv;
00498 //printf("Read => %x\n", rv);
00499   return rv;
00500 }
00501 
00502 PRInt32 nsCSSScanner::Peek(nsresult& aErrorCode)
00503 {
00504   if (0 == mPushbackCount) {
00505     PRInt32 ch = Read(aErrorCode);
00506     if (ch < 0) {
00507       return -1;
00508     }
00509     mPushback[0] = PRUnichar(ch);
00510     mPushbackCount++;
00511   }
00512 //printf("Peek => %x\n", mLookAhead);
00513   return PRInt32(mPushback[mPushbackCount - 1]);
00514 }
00515 
00516 void nsCSSScanner::Unread()
00517 {
00518   NS_PRECONDITION((mLastRead >= 0), "double pushback");
00519   Pushback(PRUnichar(mLastRead));
00520   mLastRead = -1;
00521 }
00522 
00523 void nsCSSScanner::Pushback(PRUnichar aChar)
00524 {
00525   if (mPushbackCount == mPushbackSize) { // grow buffer
00526     PRUnichar*  newPushback = new PRUnichar[mPushbackSize + 4];
00527     if (nsnull == newPushback) {
00528       return;
00529     }
00530     mPushbackSize += 4;
00531     memcpy(newPushback, mPushback, sizeof(PRUnichar) * mPushbackCount);
00532     if (mPushback != mLocalPushback) {
00533       delete [] mPushback;
00534     }
00535     mPushback = newPushback;
00536   }
00537   mPushback[mPushbackCount++] = aChar;
00538 }
00539 
00540 PRBool nsCSSScanner::LookAhead(nsresult& aErrorCode, PRUnichar aChar)
00541 {
00542   PRInt32 ch = Read(aErrorCode);
00543   if (ch < 0) {
00544     return PR_FALSE;
00545   }
00546   if (ch == aChar) {
00547     return PR_TRUE;
00548   }
00549   Unread();
00550   return PR_FALSE;
00551 }
00552 
00553 PRBool nsCSSScanner::EatWhiteSpace(nsresult& aErrorCode)
00554 {
00555   PRBool eaten = PR_FALSE;
00556   for (;;) {
00557     PRInt32 ch = Read(aErrorCode);
00558     if (ch < 0) {
00559       break;
00560     }
00561     if ((ch == ' ') || (ch == '\n') || (ch == '\r') || (ch == '\t')) {
00562       eaten = PR_TRUE;
00563       continue;
00564     }
00565     Unread();
00566     break;
00567   }
00568   return eaten;
00569 }
00570 
00571 PRBool nsCSSScanner::EatNewline(nsresult& aErrorCode)
00572 {
00573   PRInt32 ch = Read(aErrorCode);
00574   if (ch < 0) {
00575     return PR_FALSE;
00576   }
00577   PRBool eaten = PR_FALSE;
00578   if (ch == '\r') {
00579     eaten = PR_TRUE;
00580     ch = Peek(aErrorCode);
00581     if (ch == '\n') {
00582       (void) Read(aErrorCode);
00583     }
00584   } else if (ch == '\n') {
00585     eaten = PR_TRUE;
00586   } else {
00587     Unread();
00588   }
00589   return eaten;
00590 }
00591 
00592 /* static */
00593 PRBool
00594 nsCSSScanner::CheckLexTable(PRInt32 aChar, PRUint8 aBit, PRUint8* aLexTable)
00595 {
00596   NS_ASSERTION(!(aBit & (START_IDENT | IS_IDENT)),
00597                "can't use CheckLexTable with identifiers");
00598   return aChar >= 0 && aChar < 256 && (aLexTable[aChar] & aBit) != 0;
00599 }
00600 
00601 PRBool nsCSSScanner::Next(nsresult& aErrorCode, nsCSSToken& aToken)
00602 {
00603   PRInt32 ch = Read(aErrorCode);
00604   if (ch < 0) {
00605     return PR_FALSE;
00606   }
00607   PRUint8* lexTable = gLexTable;
00608 
00609   // IDENT
00610   if (StartsIdent(ch, Peek(aErrorCode), lexTable))
00611     return ParseIdent(aErrorCode, ch, aToken);
00612 
00613   // From this point on, 0 <= ch < 256.
00614      
00615   // AT_KEYWORD
00616   if (ch == '@') {
00617     PRInt32 nextChar = Read(aErrorCode);
00618     PRInt32 followingChar = Peek(aErrorCode);
00619     Pushback(nextChar);
00620     if (StartsIdent(nextChar, followingChar, lexTable))
00621       return ParseAtKeyword(aErrorCode, ch, aToken);
00622   }
00623 
00624   // NUMBER or DIM
00625   if ((ch == '.') || (ch == '+') || (ch == '-')) {
00626     PRInt32 nextChar = Peek(aErrorCode);
00627     if (CheckLexTable(nextChar, IS_DIGIT, lexTable)) {
00628       return ParseNumber(aErrorCode, ch, aToken);
00629     }
00630     else if (('.' == nextChar) && ('.' != ch)) {
00631       nextChar = Read(aErrorCode);
00632       PRInt32 followingChar = Peek(aErrorCode);
00633       Pushback(nextChar);
00634       if (CheckLexTable(followingChar, IS_DIGIT, lexTable))
00635         return ParseNumber(aErrorCode, ch, aToken);
00636     }
00637   }
00638   if ((lexTable[ch] & IS_DIGIT) != 0) {
00639     return ParseNumber(aErrorCode, ch, aToken);
00640   }
00641 
00642   // ID
00643   if (ch == '#') {
00644     return ParseRef(aErrorCode, ch, aToken);
00645   }
00646 
00647   // STRING
00648   if ((ch == '"') || (ch == '\'')) {
00649     return ParseString(aErrorCode, ch, aToken);
00650   }
00651 
00652   // WS
00653   if ((lexTable[ch] & IS_WHITESPACE) != 0) {
00654     aToken.mType = eCSSToken_WhiteSpace;
00655     aToken.mIdent.Assign(PRUnichar(ch));
00656     (void) EatWhiteSpace(aErrorCode);
00657     return PR_TRUE;
00658   }
00659   if (ch == '/') {
00660     PRInt32 nextChar = Peek(aErrorCode);
00661     if (nextChar == '*') {
00662       (void) Read(aErrorCode);
00663 #if 0
00664       // If we change our storage data structures such that comments are
00665       // stored (for Editor), we should reenable this code, condition it
00666       // on being in editor mode, and apply glazou's patch from bug
00667       // 60290.
00668       aToken.mIdent.SetCapacity(2);
00669       aToken.mIdent.Assign(PRUnichar(ch));
00670       aToken.mIdent.Append(PRUnichar(nextChar));
00671       return ParseCComment(aErrorCode, aToken);
00672 #endif
00673       return SkipCComment(aErrorCode) && Next(aErrorCode, aToken);
00674     }
00675   }
00676   if (ch == '<') {  // consume HTML comment tags
00677     if (LookAhead(aErrorCode, '!')) {
00678       if (LookAhead(aErrorCode, '-')) {
00679         if (LookAhead(aErrorCode, '-')) {
00680           aToken.mType = eCSSToken_HTMLComment;
00681           aToken.mIdent.AssignLiteral("<!--");
00682           return PR_TRUE;
00683         }
00684         Pushback('-');
00685       }
00686       Pushback('!');
00687     }
00688   }
00689   if (ch == '-') {  // check for HTML comment end
00690     if (LookAhead(aErrorCode, '-')) {
00691       if (LookAhead(aErrorCode, '>')) {
00692         aToken.mType = eCSSToken_HTMLComment;
00693         aToken.mIdent.AssignLiteral("-->");
00694         return PR_TRUE;
00695       }
00696       Pushback('-');
00697     }
00698   }
00699 
00700   // INCLUDES ("~=") and DASHMATCH ("|=")
00701   if (( ch == '|' ) || ( ch == '~' ) || ( ch == '^' ) ||
00702       ( ch == '$' ) || ( ch == '*' )) {
00703     PRInt32 nextChar = Read(aErrorCode);
00704     if ( nextChar == '=' ) {
00705       if (ch == '~') {
00706         aToken.mType = eCSSToken_Includes;
00707       }
00708       else if (ch == '|') {
00709         aToken.mType = eCSSToken_Dashmatch;
00710       }
00711       else if (ch == '^') {
00712         aToken.mType = eCSSToken_Beginsmatch;
00713       }
00714       else if (ch == '$') {
00715         aToken.mType = eCSSToken_Endsmatch;
00716       }
00717       else if (ch == '*') {
00718         aToken.mType = eCSSToken_Containsmatch;
00719       }
00720       return PR_TRUE;
00721     } else {
00722       Pushback(nextChar);
00723     }
00724   }
00725   aToken.mType = eCSSToken_Symbol;
00726   aToken.mSymbol = ch;
00727   return PR_TRUE;
00728 }
00729 
00730 PRBool nsCSSScanner::NextURL(nsresult& aErrorCode, nsCSSToken& aToken)
00731 {
00732   PRInt32 ch = Read(aErrorCode);
00733   if (ch < 0) {
00734     return PR_FALSE;
00735   }
00736   if (ch < 256) {
00737     PRUint8* lexTable = gLexTable;
00738 
00739     // STRING
00740     if ((ch == '"') || (ch == '\'')) {
00741       return ParseString(aErrorCode, ch, aToken);
00742     }
00743 
00744     // WS
00745     if ((lexTable[ch] & IS_WHITESPACE) != 0) {
00746       aToken.mType = eCSSToken_WhiteSpace;
00747       aToken.mIdent.Assign(PRUnichar(ch));
00748       (void) EatWhiteSpace(aErrorCode);
00749       return PR_TRUE;
00750     }
00751     if (ch == '/') {
00752       PRInt32 nextChar = Peek(aErrorCode);
00753       if (nextChar == '*') {
00754         (void) Read(aErrorCode);
00755 #if 0
00756         // If we change our storage data structures such that comments are
00757         // stored (for Editor), we should reenable this code, condition it
00758         // on being in editor mode, and apply glazou's patch from bug
00759         // 60290.
00760         aToken.mIdent.SetCapacity(2);
00761         aToken.mIdent.Assign(PRUnichar(ch));
00762         aToken.mIdent.Append(PRUnichar(nextChar));
00763         return ParseCComment(aErrorCode, aToken);
00764 #endif
00765         return SkipCComment(aErrorCode) && Next(aErrorCode, aToken);
00766       }
00767     }
00768 
00769     // Process a url lexical token. A CSS1 url token can contain
00770     // characters beyond identifier characters (e.g. '/', ':', etc.)
00771     // Because of this the normal rules for tokenizing the input don't
00772     // apply very well. To simplify the parser and relax some of the
00773     // requirements on the scanner we parse url's here. If we find a
00774     // malformed URL then we emit a token of type "InvalidURL" so that
00775     // the CSS1 parser can ignore the invalid input. We attempt to eat
00776     // the right amount of input data when an invalid URL is presented.
00777 
00778     aToken.mType = eCSSToken_InvalidURL;
00779     nsString& ident = aToken.mIdent;
00780     ident.SetLength(0);
00781 
00782     if (ch == ')') {
00783       Pushback(ch);
00784       // empty url spec; just get out of here
00785       aToken.mType = eCSSToken_URL;
00786     } else {
00787       // start of a non-quoted url
00788       Pushback(ch);
00789       PRBool ok = PR_TRUE;
00790       for (;;) {
00791         ch = Read(aErrorCode);
00792         if (ch < 0) break;
00793         if (ch == CSS_ESCAPE) {
00794           ParseAndAppendEscape(aErrorCode, ident);
00795         } else if ((ch == '"') || (ch == '\'') || (ch == '(')) {
00796           // This is an invalid URL spec
00797           ok = PR_FALSE;
00798         } else if ((256 > ch) && ((gLexTable[ch] & IS_WHITESPACE) != 0)) {
00799           // Whitespace is allowed at the end of the URL
00800           (void) EatWhiteSpace(aErrorCode);
00801           if (LookAhead(aErrorCode, ')')) {
00802             Pushback(')');  // leave the closing symbol
00803             // done!
00804             break;
00805           }
00806           // Whitespace is followed by something other than a
00807           // ")". This is an invalid url spec.
00808           ok = PR_FALSE;
00809         } else if (ch == ')') {
00810           Unread();
00811           // All done
00812           break;
00813         } else {
00814           // A regular url character.
00815           ident.Append(PRUnichar(ch));
00816         }
00817       }
00818 
00819       // If the result of the above scanning is ok then change the token
00820       // type to a useful one.
00821       if (ok) {
00822         aToken.mType = eCSSToken_URL;
00823       }
00824     }
00825   }
00826   return PR_TRUE;
00827 }
00828 
00829 
00830 void
00831 nsCSSScanner::ParseAndAppendEscape(nsresult& aErrorCode, nsString& aOutput)
00832 {
00833   PRUint8* lexTable = gLexTable;
00834   PRInt32 ch = Peek(aErrorCode);
00835   if (ch < 0) {
00836     aOutput.Append(CSS_ESCAPE);
00837     return;
00838   }
00839   if ((ch <= 255) && ((lexTable[ch] & IS_HEX_DIGIT) != 0)) {
00840     PRInt32 rv = 0;
00841     int i;
00842     for (i = 0; i < 6; i++) { // up to six digits
00843       ch = Read(aErrorCode);
00844       if (ch < 0) {
00845         // Whoops: error or premature eof
00846         break;
00847       }
00848       if (ch >= 256 || (lexTable[ch] & (IS_HEX_DIGIT | IS_WHITESPACE)) == 0) {
00849         Unread();
00850         break;
00851       } else if ((lexTable[ch] & IS_HEX_DIGIT) != 0) {
00852         if ((lexTable[ch] & IS_DIGIT) != 0) {
00853           rv = rv * 16 + (ch - '0');
00854         } else {
00855           // Note: c&7 just keeps the low three bits which causes
00856           // upper and lower case alphabetics to both yield their
00857           // "relative to 10" value for computing the hex value.
00858           rv = rv * 16 + ((ch & 0x7) + 9);
00859         }
00860       } else {
00861         NS_ASSERTION((lexTable[ch] & IS_WHITESPACE) != 0, "bad control flow");
00862         // single space ends escape
00863         if (ch == '\r' && Peek(aErrorCode) == '\n') {
00864           // if CR/LF, eat LF too
00865           Read(aErrorCode);
00866         }
00867         break;
00868       }
00869     }
00870     if (6 == i) { // look for trailing whitespace and eat it
00871       ch = Peek(aErrorCode);
00872       if ((0 <= ch) && (ch <= 255) && 
00873           ((lexTable[ch] & IS_WHITESPACE) != 0)) {
00874         ch = Read(aErrorCode);
00875         // special case: if trailing whitespace is CR/LF, eat both chars (not part of spec, but should be)
00876         if (ch == '\r') {
00877           ch = Peek(aErrorCode);
00878           if (ch == '\n') {
00879             ch = Read(aErrorCode);
00880           }
00881         }
00882       }
00883     }
00884     NS_ASSERTION(rv >= 0, "How did rv become negative?");
00885     if (rv > 0) {
00886       AppendUCS4ToUTF16(ENSURE_VALID_CHAR(rv), aOutput);
00887     }
00888     return;
00889   } else {
00890     // "Any character except a hexidecimal digit can be escaped to
00891     // remove its special meaning by putting a backslash in front"
00892     // -- CSS1 spec section 7.1
00893     if (!EatNewline(aErrorCode)) { // skip escaped newline
00894       (void) Read(aErrorCode);
00895       if (ch > 0) {
00896         aOutput.Append(ch);
00897       }
00898     }
00899     return;
00900   }
00901 }
00902 
00910 PRBool nsCSSScanner::GatherIdent(nsresult& aErrorCode, PRInt32 aChar,
00911                                  nsString& aIdent)
00912 {
00913   if (aChar == CSS_ESCAPE) {
00914     ParseAndAppendEscape(aErrorCode, aIdent);
00915   }
00916   else if (0 < aChar) {
00917     aIdent.Append(aChar);
00918   }
00919   for (;;) {
00920     aChar = Read(aErrorCode);
00921     if (aChar < 0) break;
00922     if (aChar == CSS_ESCAPE) {
00923       ParseAndAppendEscape(aErrorCode, aIdent);
00924     } else if ((aChar > 255) || ((gLexTable[aChar] & IS_IDENT) != 0)) {
00925       aIdent.Append(PRUnichar(aChar));
00926     } else {
00927       Unread();
00928       break;
00929     }
00930   }
00931   return PR_TRUE;
00932 }
00933 
00934 PRBool nsCSSScanner::ParseRef(nsresult& aErrorCode,
00935                               PRInt32 aChar,
00936                               nsCSSToken& aToken)
00937 {
00938   aToken.mIdent.SetLength(0);
00939   aToken.mType = eCSSToken_Ref;
00940   PRInt32 ch = Read(aErrorCode);
00941   if (ch < 0) {
00942     return PR_FALSE;
00943   }
00944   if (ch > 255 || (gLexTable[ch] & IS_IDENT) || ch == CSS_ESCAPE) {
00945     // First char after the '#' is a valid ident char (or an escape),
00946     // so it makes sense to keep going
00947     if (StartsIdent(ch, Peek(aErrorCode), gLexTable)) {
00948       aToken.mType = eCSSToken_ID;
00949     }
00950     return GatherIdent(aErrorCode, ch, aToken.mIdent);
00951   }
00952 
00953   // No ident chars after the '#'.  Just unread |ch| and get out of here.
00954   Unread();
00955   return PR_TRUE;
00956 }
00957 
00958 PRBool nsCSSScanner::ParseIdent(nsresult& aErrorCode,
00959                                 PRInt32 aChar,
00960                                 nsCSSToken& aToken)
00961 {
00962   nsString& ident = aToken.mIdent;
00963   ident.SetLength(0);
00964   if (!GatherIdent(aErrorCode, aChar, ident)) {
00965     return PR_FALSE;
00966   }
00967 
00968   nsCSSTokenType tokenType = eCSSToken_Ident;
00969   // look for functions (ie: "ident(")
00970   if (PRUnichar('(') == PRUnichar(Peek(aErrorCode))) { // this is a function definition
00971     tokenType = eCSSToken_Function;
00972   }
00973 
00974   aToken.mType = tokenType;
00975   return PR_TRUE;
00976 }
00977 
00978 PRBool nsCSSScanner::ParseAtKeyword(nsresult& aErrorCode, PRInt32 aChar,
00979                                     nsCSSToken& aToken)
00980 {
00981   aToken.mIdent.SetLength(0);
00982   aToken.mType = eCSSToken_AtKeyword;
00983   return GatherIdent(aErrorCode, 0, aToken.mIdent);
00984 }
00985 
00986 PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c,
00987                                  nsCSSToken& aToken)
00988 {
00989   nsString& ident = aToken.mIdent;
00990   ident.SetLength(0);
00991   PRBool gotDot = (c == '.') ? PR_TRUE : PR_FALSE;
00992   if (c != '+') {
00993     ident.Append(PRUnichar(c));
00994   }
00995 
00996   // Gather up characters that make up the number
00997   PRUint8* lexTable = gLexTable;
00998   for (;;) {
00999     c = Read(aErrorCode);
01000     if (c < 0) break;
01001     if (!gotDot && (c == '.') &&
01002         CheckLexTable(Peek(aErrorCode), IS_DIGIT, lexTable)) {
01003       gotDot = PR_TRUE;
01004     } else if ((c > 255) || ((lexTable[c] & IS_DIGIT) == 0)) {
01005       break;
01006     }
01007     ident.Append(PRUnichar(c));
01008   }
01009 
01010   // Convert number to floating point
01011   nsCSSTokenType type = eCSSToken_Number;
01012   PRInt32 ec;
01013   float value = ident.ToFloat(&ec);
01014 
01015   // Look at character that terminated the number
01016   aToken.mIntegerValid = PR_FALSE;
01017   if (c >= 0) {
01018     if ((c <= 255) && ((lexTable[c] & START_IDENT) != 0)) {
01019       ident.SetLength(0);
01020       if (!GatherIdent(aErrorCode, c, ident)) {
01021         return PR_FALSE;
01022       }
01023       type = eCSSToken_Dimension;
01024     } else if ('%' == c) {
01025       type = eCSSToken_Percentage;
01026       value = value / 100.0f;
01027       ident.SetLength(0);
01028     } else {
01029       // Put back character that stopped numeric scan
01030       Unread();
01031       if (!gotDot) {
01032         aToken.mInteger = ident.ToInteger(&ec);
01033         aToken.mIntegerValid = PR_TRUE;
01034       }
01035       ident.SetLength(0);
01036     }
01037   }
01038   else {  // stream ended
01039     if (!gotDot) {
01040       aToken.mInteger = ident.ToInteger(&ec);
01041       aToken.mIntegerValid = PR_TRUE;
01042     }
01043     ident.SetLength(0);
01044   }
01045   aToken.mNumber = value;
01046   aToken.mType = type;
01047   return PR_TRUE;
01048 }
01049 
01050 PRBool nsCSSScanner::SkipCComment(nsresult& aErrorCode)
01051 {
01052   for (;;) {
01053     PRInt32 ch = Read(aErrorCode);
01054     if (ch < 0) break;
01055     if (ch == '*') {
01056       if (LookAhead(aErrorCode, '/')) {
01057         return PR_TRUE;
01058       }
01059     }
01060   }
01061 
01062   REPORT_UNEXPECTED_EOF(PECommentEOF);
01063   return PR_FALSE;
01064 }
01065 
01066 #if 0
01067 PRBool nsCSSScanner::ParseCComment(nsresult& aErrorCode, nsCSSToken& aToken)
01068 {
01069   nsString& ident = aToken.mIdent;
01070   for (;;) {
01071     PRInt32 ch = Read(aErrorCode);
01072     if (ch < 0) break;
01073     if (ch == '*') {
01074       if (LookAhead(aErrorCode, '/')) {
01075         ident.Append(PRUnichar(ch));
01076         ident.Append(PRUnichar('/'));
01077         break;
01078       }
01079     }
01080 #ifdef COLLECT_WHITESPACE
01081     ident.Append(PRUnichar(ch));
01082 #endif
01083   }
01084   aToken.mType = eCSSToken_WhiteSpace;
01085   return PR_TRUE;
01086 }
01087 #endif
01088 
01089 #if 0
01090 PRBool nsCSSScanner::ParseEOLComment(nsresult& aErrorCode, nsCSSToken& aToken)
01091 {
01092   nsString& ident = aToken.mIdent;
01093   ident.SetLength(0);
01094   for (;;) {
01095     if (EatNewline(aErrorCode)) {
01096       break;
01097     }
01098     PRInt32 ch = Read(aErrorCode);
01099     if (ch < 0) {
01100       break;
01101     }
01102 #ifdef COLLECT_WHITESPACE
01103     ident.Append(PRUnichar(ch));
01104 #endif
01105   }
01106   aToken.mType = eCSSToken_WhiteSpace;
01107   return PR_TRUE;
01108 }
01109 #endif // 0
01110 
01111 PRBool nsCSSScanner::ParseString(nsresult& aErrorCode, PRInt32 aStop,
01112                                  nsCSSToken& aToken)
01113 {
01114   aToken.mIdent.SetLength(0);
01115   aToken.mType = eCSSToken_String;
01116   aToken.mSymbol = PRUnichar(aStop); // remember how it's quoted
01117   for (;;) {
01118     if (EatNewline(aErrorCode)) {
01119       aToken.mType = eCSSToken_Error;
01120 #ifdef CSS_REPORT_PARSE_ERRORS
01121       ReportUnexpectedToken(aToken, "SEUnterminatedString");
01122 #endif
01123       return PR_TRUE;
01124     }
01125     PRInt32 ch = Read(aErrorCode);
01126     if (ch < 0) {
01127       return PR_FALSE;
01128     }
01129     if (ch == aStop) {
01130       break;
01131     }
01132     if (ch == CSS_ESCAPE) {
01133       ParseAndAppendEscape(aErrorCode, aToken.mIdent);
01134     }
01135     else if (0 < ch) {
01136       aToken.mIdent.Append(ch);
01137     }
01138   }
01139   return PR_TRUE;
01140 }