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
4 : *
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/
9 : *
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
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
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.
21 : *
22 : * Contributor(s):
23 : * L. David Baron <dbaron@dbaron.org>
24 : * Daniel Glazman <glazman@netscape.com>
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 :
41 : /* tokenization of CSS style sheets */
42 :
43 : #include <math.h> // must be first due to symbol conflicts
44 :
45 : #include "nsCSSScanner.h"
46 : #include "nsString.h"
47 : #include "nsCRT.h"
48 : #include "mozilla/Util.h"
49 :
50 : // for #ifdef CSS_REPORT_PARSE_ERRORS
51 : #include "nsCOMPtr.h"
52 : #include "nsIServiceManager.h"
53 : #include "nsIComponentManager.h"
54 : #include "nsReadableUtils.h"
55 : #include "nsIURI.h"
56 : #include "nsIConsoleService.h"
57 : #include "nsIScriptError.h"
58 : #include "nsIStringBundle.h"
59 : #include "nsIDocument.h"
60 : #include "mozilla/Services.h"
61 : #include "mozilla/css/Loader.h"
62 : #include "nsCSSStyleSheet.h"
63 : #include "mozilla/Preferences.h"
64 :
65 : using namespace mozilla;
66 :
67 : #ifdef CSS_REPORT_PARSE_ERRORS
68 : static bool gReportErrors = true;
69 : static nsIConsoleService *gConsoleService;
70 : static nsIFactory *gScriptErrorFactory;
71 : static nsIStringBundle *gStringBundle;
72 : #endif
73 :
74 : static const PRUint8 IS_HEX_DIGIT = 0x01;
75 : static const PRUint8 START_IDENT = 0x02;
76 : static const PRUint8 IS_IDENT = 0x04;
77 : static const PRUint8 IS_WHITESPACE = 0x08;
78 :
79 : #define W IS_WHITESPACE
80 : #define I IS_IDENT
81 : #define S START_IDENT
82 : #define SI IS_IDENT|START_IDENT
83 : #define XI IS_IDENT |IS_HEX_DIGIT
84 : #define XSI IS_IDENT|START_IDENT|IS_HEX_DIGIT
85 :
86 : static const PRUint8 gLexTable[] = {
87 : // TAB LF FF CR
88 : 0, 0, 0, 0, 0, 0, 0, 0, 0, W, W, 0, W, W, 0, 0,
89 : //
90 : 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
91 : // SPC ! " # $ % & ' ( ) * + , - . /
92 : W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, I, 0, 0,
93 : // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
94 : XI, XI, XI, XI, XI, XI, XI, XI, XI, XI, 0, 0, 0, 0, 0, 0,
95 : // @ A B C D E F G H I J K L M N O
96 : 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI,
97 : // P Q R S T U V W X Y Z [ \ ] ^ _
98 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, S, 0, 0, SI,
99 : // ` a b c d e f g h i j k l m n o
100 : 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI,
101 : // p q r s t u v w x y z { | } ~
102 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, 0, 0, 0, 0,
103 : // U+008*
104 : 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
105 : // U+009*
106 : 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
107 : // U+00A*
108 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
109 : // U+00B*
110 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
111 : // U+00C*
112 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
113 : // U+00D*
114 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
115 : // U+00E*
116 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
117 : // U+00F*
118 : SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
119 : };
120 :
121 : MOZ_STATIC_ASSERT(NS_ARRAY_LENGTH(gLexTable) == 256,
122 : "gLexTable expected to cover all 2^8 possible PRUint8s");
123 :
124 : #undef W
125 : #undef S
126 : #undef I
127 : #undef XI
128 : #undef SI
129 : #undef XSI
130 :
131 : static inline bool
132 550 : IsIdentStart(PRInt32 aChar)
133 : {
134 : return aChar >= 0 &&
135 550 : (aChar >= 256 || (gLexTable[aChar] & START_IDENT) != 0);
136 : }
137 :
138 : static inline bool
139 550 : StartsIdent(PRInt32 aFirstChar, PRInt32 aSecondChar)
140 : {
141 550 : return IsIdentStart(aFirstChar) ||
142 550 : (aFirstChar == '-' && IsIdentStart(aSecondChar));
143 : }
144 :
145 : static inline bool
146 330 : IsWhitespace(PRInt32 ch) {
147 330 : return PRUint32(ch) < 256 && (gLexTable[ch] & IS_WHITESPACE) != 0;
148 : }
149 :
150 : static inline bool
151 330 : IsDigit(PRInt32 ch) {
152 330 : return (ch >= '0') && (ch <= '9');
153 : }
154 :
155 : static inline bool
156 0 : IsHexDigit(PRInt32 ch) {
157 0 : return PRUint32(ch) < 256 && (gLexTable[ch] & IS_HEX_DIGIT) != 0;
158 : }
159 :
160 : static inline bool
161 3474 : IsIdent(PRInt32 ch) {
162 3474 : return ch >= 0 && (ch >= 256 || (gLexTable[ch] & IS_IDENT) != 0);
163 : }
164 :
165 : static inline PRUint32
166 0 : DecimalDigitValue(PRInt32 ch)
167 : {
168 0 : return ch - '0';
169 : }
170 :
171 : static inline PRUint32
172 0 : HexDigitValue(PRInt32 ch)
173 : {
174 0 : if (IsDigit(ch)) {
175 0 : return DecimalDigitValue(ch);
176 : } else {
177 : // Note: c&7 just keeps the low three bits which causes
178 : // upper and lower case alphabetics to both yield their
179 : // "relative to 10" value for computing the hex value.
180 0 : return (ch & 0x7) + 9;
181 : }
182 : }
183 :
184 12 : nsCSSToken::nsCSSToken()
185 : {
186 12 : mType = eCSSToken_Symbol;
187 12 : }
188 :
189 : void
190 0 : nsCSSToken::AppendToString(nsString& aBuffer)
191 : {
192 0 : switch (mType) {
193 : case eCSSToken_AtKeyword:
194 0 : aBuffer.Append(PRUnichar('@')); // fall through intentional
195 : case eCSSToken_Ident:
196 : case eCSSToken_WhiteSpace:
197 : case eCSSToken_Function:
198 : case eCSSToken_HTMLComment:
199 : case eCSSToken_URange:
200 0 : aBuffer.Append(mIdent);
201 0 : if (mType == eCSSToken_Function)
202 0 : aBuffer.Append(PRUnichar('('));
203 0 : break;
204 : case eCSSToken_URL:
205 : case eCSSToken_Bad_URL:
206 0 : aBuffer.AppendLiteral("url(");
207 0 : if (mSymbol != PRUnichar(0)) {
208 0 : aBuffer.Append(mSymbol);
209 : }
210 0 : aBuffer.Append(mIdent);
211 0 : if (mSymbol != PRUnichar(0)) {
212 0 : aBuffer.Append(mSymbol);
213 : }
214 0 : if (mType == eCSSToken_URL) {
215 0 : aBuffer.Append(PRUnichar(')'));
216 : }
217 0 : break;
218 : case eCSSToken_Number:
219 0 : if (mIntegerValid) {
220 0 : aBuffer.AppendInt(mInteger, 10);
221 : }
222 : else {
223 0 : aBuffer.AppendFloat(mNumber);
224 : }
225 0 : break;
226 : case eCSSToken_Percentage:
227 0 : NS_ASSERTION(!mIntegerValid, "How did a percentage token get this set?");
228 0 : aBuffer.AppendFloat(mNumber * 100.0f);
229 0 : aBuffer.Append(PRUnichar('%'));
230 0 : break;
231 : case eCSSToken_Dimension:
232 0 : if (mIntegerValid) {
233 0 : aBuffer.AppendInt(mInteger, 10);
234 : }
235 : else {
236 0 : aBuffer.AppendFloat(mNumber);
237 : }
238 0 : aBuffer.Append(mIdent);
239 0 : break;
240 : case eCSSToken_String:
241 0 : aBuffer.Append(mSymbol);
242 0 : aBuffer.Append(mIdent); // fall through intentional
243 : case eCSSToken_Symbol:
244 0 : aBuffer.Append(mSymbol);
245 0 : break;
246 : case eCSSToken_ID:
247 : case eCSSToken_Ref:
248 0 : aBuffer.Append(PRUnichar('#'));
249 0 : aBuffer.Append(mIdent);
250 0 : break;
251 : case eCSSToken_Includes:
252 0 : aBuffer.AppendLiteral("~=");
253 0 : break;
254 : case eCSSToken_Dashmatch:
255 0 : aBuffer.AppendLiteral("|=");
256 0 : break;
257 : case eCSSToken_Beginsmatch:
258 0 : aBuffer.AppendLiteral("^=");
259 0 : break;
260 : case eCSSToken_Endsmatch:
261 0 : aBuffer.AppendLiteral("$=");
262 0 : break;
263 : case eCSSToken_Containsmatch:
264 0 : aBuffer.AppendLiteral("*=");
265 0 : break;
266 : case eCSSToken_Bad_String:
267 0 : aBuffer.Append(mSymbol);
268 0 : aBuffer.Append(mIdent);
269 0 : break;
270 : default:
271 0 : NS_ERROR("invalid token type");
272 0 : break;
273 : }
274 0 : }
275 :
276 12 : nsCSSScanner::nsCSSScanner()
277 : : mReadPointer(nsnull)
278 : , mSVGMode(false)
279 : #ifdef CSS_REPORT_PARSE_ERRORS
280 : , mError(mErrorBuf, ArrayLength(mErrorBuf), 0)
281 : , mInnerWindowID(0)
282 : , mWindowIDCached(false)
283 : , mSheet(nsnull)
284 12 : , mLoader(nsnull)
285 : #endif
286 : {
287 12 : MOZ_COUNT_CTOR(nsCSSScanner);
288 12 : mPushback = mLocalPushback;
289 12 : mPushbackSize = ArrayLength(mLocalPushback);
290 : // No need to init the other members, since they represent state
291 : // which can get cleared. We'll init them every time Init() is
292 : // called.
293 12 : }
294 :
295 24 : nsCSSScanner::~nsCSSScanner()
296 : {
297 12 : MOZ_COUNT_DTOR(nsCSSScanner);
298 12 : Close();
299 12 : if (mLocalPushback != mPushback) {
300 0 : delete [] mPushback;
301 : }
302 12 : }
303 :
304 : #ifdef CSS_REPORT_PARSE_ERRORS
305 : #define CSS_ERRORS_PREF "layout.css.report_errors"
306 :
307 : static int
308 0 : CSSErrorsPrefChanged(const char *aPref, void *aClosure)
309 : {
310 0 : gReportErrors = Preferences::GetBool(CSS_ERRORS_PREF, true);
311 0 : return NS_OK;
312 : }
313 : #endif
314 :
315 : /* static */ bool
316 0 : nsCSSScanner::InitGlobals()
317 : {
318 : #ifdef CSS_REPORT_PARSE_ERRORS
319 0 : if (gConsoleService && gScriptErrorFactory)
320 0 : return true;
321 :
322 0 : nsresult rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &gConsoleService);
323 0 : NS_ENSURE_SUCCESS(rv, false);
324 :
325 0 : rv = CallGetClassObject(NS_SCRIPTERROR_CONTRACTID, &gScriptErrorFactory);
326 0 : NS_ENSURE_SUCCESS(rv, false);
327 0 : NS_ASSERTION(gConsoleService && gScriptErrorFactory,
328 : "unexpected null pointer without failure");
329 :
330 0 : Preferences::RegisterCallback(CSSErrorsPrefChanged, CSS_ERRORS_PREF);
331 0 : CSSErrorsPrefChanged(CSS_ERRORS_PREF, nsnull);
332 : #endif
333 0 : return true;
334 : }
335 :
336 : /* static */ void
337 1403 : nsCSSScanner::ReleaseGlobals()
338 : {
339 : #ifdef CSS_REPORT_PARSE_ERRORS
340 1403 : Preferences::UnregisterCallback(CSSErrorsPrefChanged, CSS_ERRORS_PREF);
341 1403 : NS_IF_RELEASE(gConsoleService);
342 1403 : NS_IF_RELEASE(gScriptErrorFactory);
343 1403 : NS_IF_RELEASE(gStringBundle);
344 : #endif
345 1403 : }
346 :
347 : void
348 110 : nsCSSScanner::Init(const nsAString& aBuffer,
349 : nsIURI* aURI, PRUint32 aLineNumber,
350 : nsCSSStyleSheet* aSheet, mozilla::css::Loader* aLoader)
351 : {
352 110 : NS_PRECONDITION(!mReadPointer, "Should not have an existing input buffer!");
353 :
354 110 : mReadPointer = aBuffer.BeginReading();
355 110 : mCount = aBuffer.Length();
356 :
357 : #ifdef CSS_REPORT_PARSE_ERRORS
358 : // If aURI is the same as mURI, no need to reget mFileName -- it
359 : // shouldn't have changed.
360 110 : if (aURI != mURI) {
361 110 : mURI = aURI;
362 110 : if (aURI) {
363 110 : aURI->GetSpec(mFileName);
364 : } else {
365 0 : mFileName.Adopt(NS_strdup("from DOM"));
366 : }
367 : }
368 : #endif // CSS_REPORT_PARSE_ERRORS
369 110 : mLineNumber = aLineNumber;
370 :
371 : // Reset variables that we use to keep track of our progress through the input
372 110 : mOffset = 0;
373 110 : mPushbackCount = 0;
374 :
375 : #ifdef CSS_REPORT_PARSE_ERRORS
376 110 : mColNumber = 0;
377 110 : mSheet = aSheet;
378 110 : mLoader = aLoader;
379 : #endif
380 110 : }
381 :
382 : #ifdef CSS_REPORT_PARSE_ERRORS
383 :
384 : // @see REPORT_UNEXPECTED_EOF in nsCSSParser.cpp
385 : #define REPORT_UNEXPECTED_EOF(lf_) \
386 : ReportUnexpectedEOF(#lf_)
387 :
388 : void
389 0 : nsCSSScanner::AddToError(const nsSubstring& aErrorText)
390 : {
391 0 : if (mError.IsEmpty()) {
392 0 : mErrorLineNumber = mLineNumber;
393 0 : mErrorColNumber = mColNumber;
394 0 : mError = aErrorText;
395 : } else {
396 0 : mError.Append(NS_LITERAL_STRING(" ") + aErrorText);
397 : }
398 0 : }
399 :
400 : void
401 110 : nsCSSScanner::ClearError()
402 : {
403 110 : mError.Truncate();
404 110 : }
405 :
406 : void
407 0 : nsCSSScanner::OutputError()
408 : {
409 0 : if (mError.IsEmpty()) return;
410 :
411 : // Log it to the Error console
412 :
413 0 : if (InitGlobals() && gReportErrors) {
414 0 : if (!mWindowIDCached) {
415 0 : if (mSheet) {
416 0 : mInnerWindowID = mSheet->FindOwningWindowInnerID();
417 : }
418 0 : if (mInnerWindowID == 0 && mLoader) {
419 0 : nsIDocument* doc = mLoader->GetDocument();
420 0 : if (doc) {
421 0 : mInnerWindowID = doc->InnerWindowID();
422 : }
423 : }
424 0 : mWindowIDCached = true;
425 : }
426 :
427 : nsresult rv;
428 : nsCOMPtr<nsIScriptError> errorObject =
429 0 : do_CreateInstance(gScriptErrorFactory, &rv);
430 :
431 0 : if (NS_SUCCEEDED(rv)) {
432 0 : rv = errorObject->InitWithWindowID(mError.get(),
433 0 : NS_ConvertUTF8toUTF16(mFileName).get(),
434 0 : EmptyString().get(),
435 : mErrorLineNumber,
436 : mErrorColNumber,
437 : nsIScriptError::warningFlag,
438 : "CSS Parser",
439 0 : mInnerWindowID);
440 0 : if (NS_SUCCEEDED(rv)) {
441 0 : gConsoleService->LogMessage(errorObject);
442 : }
443 : }
444 : }
445 0 : ClearError();
446 : }
447 :
448 : static bool
449 0 : InitStringBundle()
450 : {
451 0 : if (gStringBundle)
452 0 : return true;
453 :
454 : nsCOMPtr<nsIStringBundleService> sbs =
455 0 : mozilla::services::GetStringBundleService();
456 0 : if (!sbs)
457 0 : return false;
458 :
459 : nsresult rv =
460 0 : sbs->CreateBundle("chrome://global/locale/css.properties", &gStringBundle);
461 0 : if (NS_FAILED(rv)) {
462 0 : gStringBundle = nsnull;
463 0 : return false;
464 : }
465 :
466 0 : return true;
467 : }
468 :
469 : #define ENSURE_STRINGBUNDLE \
470 : PR_BEGIN_MACRO if (!InitStringBundle()) return; PR_END_MACRO
471 :
472 : // aMessage must take no parameters
473 0 : void nsCSSScanner::ReportUnexpected(const char* aMessage)
474 : {
475 0 : ENSURE_STRINGBUNDLE;
476 :
477 0 : nsXPIDLString str;
478 0 : gStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
479 0 : getter_Copies(str));
480 0 : AddToError(str);
481 : }
482 :
483 : void
484 0 : nsCSSScanner::ReportUnexpectedParams(const char* aMessage,
485 : const PRUnichar **aParams,
486 : PRUint32 aParamsLength)
487 : {
488 0 : NS_PRECONDITION(aParamsLength > 0, "use the non-params version");
489 0 : ENSURE_STRINGBUNDLE;
490 :
491 0 : nsXPIDLString str;
492 0 : gStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
493 : aParams, aParamsLength,
494 0 : getter_Copies(str));
495 0 : AddToError(str);
496 : }
497 :
498 : // aLookingFor is a plain string, not a format string
499 : void
500 0 : nsCSSScanner::ReportUnexpectedEOF(const char* aLookingFor)
501 : {
502 0 : ENSURE_STRINGBUNDLE;
503 :
504 0 : nsXPIDLString innerStr;
505 0 : gStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aLookingFor).get(),
506 0 : getter_Copies(innerStr));
507 :
508 : const PRUnichar *params[] = {
509 0 : innerStr.get()
510 0 : };
511 0 : nsXPIDLString str;
512 0 : gStringBundle->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
513 : params, ArrayLength(params),
514 0 : getter_Copies(str));
515 0 : AddToError(str);
516 : }
517 :
518 : // aLookingFor is a single character
519 : void
520 0 : nsCSSScanner::ReportUnexpectedEOF(PRUnichar aLookingFor)
521 : {
522 0 : ENSURE_STRINGBUNDLE;
523 :
524 : const PRUnichar lookingForStr[] = {
525 : PRUnichar('\''), aLookingFor, PRUnichar('\''), PRUnichar(0)
526 0 : };
527 0 : const PRUnichar *params[] = { lookingForStr };
528 0 : nsXPIDLString str;
529 0 : gStringBundle->FormatStringFromName(NS_LITERAL_STRING("PEUnexpEOF2").get(),
530 : params, ArrayLength(params),
531 0 : getter_Copies(str));
532 0 : AddToError(str);
533 : }
534 :
535 : // aMessage must take 1 parameter (for the string representation of the
536 : // unexpected token)
537 : void
538 0 : nsCSSScanner::ReportUnexpectedToken(nsCSSToken& tok,
539 : const char *aMessage)
540 : {
541 0 : ENSURE_STRINGBUNDLE;
542 :
543 0 : nsAutoString tokenString;
544 0 : tok.AppendToString(tokenString);
545 :
546 : const PRUnichar *params[] = {
547 0 : tokenString.get()
548 0 : };
549 :
550 0 : ReportUnexpectedParams(aMessage, params);
551 : }
552 :
553 : // aParams's first entry must be null, and we'll fill in the token
554 : void
555 0 : nsCSSScanner::ReportUnexpectedTokenParams(nsCSSToken& tok,
556 : const char* aMessage,
557 : const PRUnichar **aParams,
558 : PRUint32 aParamsLength)
559 : {
560 0 : NS_PRECONDITION(aParamsLength > 1, "use the non-params version");
561 0 : NS_PRECONDITION(aParams[0] == nsnull, "first param should be empty");
562 :
563 0 : ENSURE_STRINGBUNDLE;
564 :
565 0 : nsAutoString tokenString;
566 0 : tok.AppendToString(tokenString);
567 0 : aParams[0] = tokenString.get();
568 :
569 0 : ReportUnexpectedParams(aMessage, aParams, aParamsLength);
570 : }
571 :
572 : #else
573 :
574 : #define REPORT_UNEXPECTED_EOF(lf_)
575 :
576 : #endif // CSS_REPORT_PARSE_ERRORS
577 :
578 : void
579 122 : nsCSSScanner::Close()
580 : {
581 122 : mReadPointer = nsnull;
582 :
583 : // Clean things up so we don't hold on to memory if our parser gets recycled.
584 : #ifdef CSS_REPORT_PARSE_ERRORS
585 122 : mFileName.Truncate();
586 122 : mURI = nsnull;
587 122 : mError.Truncate();
588 122 : mInnerWindowID = 0;
589 122 : mWindowIDCached = false;
590 122 : mSheet = nsnull;
591 122 : mLoader = nsnull;
592 : #endif
593 122 : if (mPushback != mLocalPushback) {
594 0 : delete [] mPushback;
595 0 : mPushback = mLocalPushback;
596 0 : mPushbackSize = ArrayLength(mLocalPushback);
597 : }
598 122 : }
599 :
600 : // Returns -1 on error or eof
601 : PRInt32
602 2310 : nsCSSScanner::Read()
603 : {
604 : PRInt32 rv;
605 2310 : if (0 < mPushbackCount) {
606 880 : rv = PRInt32(mPushback[--mPushbackCount]);
607 : } else {
608 1430 : if (mOffset == mCount) {
609 660 : return -1;
610 : }
611 770 : rv = PRInt32(mReadPointer[mOffset++]);
612 : // There are four types of newlines in CSS: "\r", "\n", "\r\n", and "\f".
613 : // To simplify dealing with newlines, they are all normalized to "\n" here
614 770 : if (rv == '\r') {
615 0 : if (mOffset < mCount && mReadPointer[mOffset] == '\n') {
616 0 : mOffset++;
617 : }
618 0 : rv = '\n';
619 770 : } else if (rv == '\f') {
620 0 : rv = '\n';
621 : }
622 770 : if (rv == '\n') {
623 : // 0 is a magical line number meaning that we don't know (i.e., script)
624 0 : if (mLineNumber != 0)
625 0 : ++mLineNumber;
626 : #ifdef CSS_REPORT_PARSE_ERRORS
627 0 : mColNumber = 0;
628 : } else {
629 770 : mColNumber++;
630 : #endif
631 : }
632 : }
633 1650 : return rv;
634 : }
635 :
636 : PRInt32
637 770 : nsCSSScanner::Peek()
638 : {
639 770 : if (0 == mPushbackCount) {
640 660 : PRInt32 ch = Read();
641 660 : if (ch < 0) {
642 110 : return -1;
643 : }
644 550 : mPushback[0] = PRUnichar(ch);
645 550 : mPushbackCount++;
646 : }
647 660 : return PRInt32(mPushback[mPushbackCount - 1]);
648 : }
649 :
650 : void
651 330 : nsCSSScanner::Pushback(PRUnichar aChar)
652 : {
653 330 : if (mPushbackCount == mPushbackSize) { // grow buffer
654 0 : PRUnichar* newPushback = new PRUnichar[mPushbackSize + 4];
655 0 : if (nsnull == newPushback) {
656 0 : return;
657 : }
658 0 : mPushbackSize += 4;
659 0 : memcpy(newPushback, mPushback, sizeof(PRUnichar) * mPushbackCount);
660 0 : if (mPushback != mLocalPushback) {
661 0 : delete [] mPushback;
662 : }
663 0 : mPushback = newPushback;
664 : }
665 330 : mPushback[mPushbackCount++] = aChar;
666 : }
667 :
668 : bool
669 0 : nsCSSScanner::LookAhead(PRUnichar aChar)
670 : {
671 0 : PRInt32 ch = Read();
672 0 : if (ch < 0) {
673 0 : return false;
674 : }
675 0 : if (ch == aChar) {
676 0 : return true;
677 : }
678 0 : Pushback(ch);
679 0 : return false;
680 : }
681 :
682 : bool
683 0 : nsCSSScanner::LookAheadOrEOF(PRUnichar aChar)
684 : {
685 0 : PRInt32 ch = Read();
686 0 : if (ch < 0) {
687 0 : return true;
688 : }
689 0 : if (ch == aChar) {
690 0 : return true;
691 : }
692 0 : Pushback(ch);
693 0 : return false;
694 : }
695 :
696 : void
697 220 : nsCSSScanner::EatWhiteSpace()
698 : {
699 0 : for (;;) {
700 220 : PRInt32 ch = Read();
701 220 : if (ch < 0) {
702 0 : break;
703 : }
704 220 : if ((ch != ' ') && (ch != '\n') && (ch != '\t')) {
705 220 : Pushback(ch);
706 220 : break;
707 : }
708 : }
709 220 : }
710 :
711 : bool
712 990 : nsCSSScanner::Next(nsCSSToken& aToken)
713 : {
714 0 : for (;;) { // Infinite loop so we can restart after comments.
715 990 : PRInt32 ch = Read();
716 990 : if (ch < 0) {
717 440 : return false;
718 : }
719 :
720 : // UNICODE-RANGE
721 550 : if ((ch == 'u' || ch == 'U') && Peek() == '+')
722 0 : return ParseURange(ch, aToken);
723 :
724 : // IDENT
725 550 : if (StartsIdent(ch, Peek()))
726 220 : return ParseIdent(ch, aToken);
727 :
728 : // AT_KEYWORD
729 330 : if (ch == '@') {
730 0 : PRInt32 nextChar = Read();
731 0 : if (nextChar >= 0) {
732 0 : PRInt32 followingChar = Peek();
733 0 : Pushback(nextChar);
734 0 : if (StartsIdent(nextChar, followingChar))
735 0 : return ParseAtKeyword(ch, aToken);
736 : }
737 : }
738 :
739 : // NUMBER or DIM
740 330 : if ((ch == '.') || (ch == '+') || (ch == '-')) {
741 0 : PRInt32 nextChar = Peek();
742 0 : if (IsDigit(nextChar)) {
743 0 : return ParseNumber(ch, aToken);
744 : }
745 0 : else if (('.' == nextChar) && ('.' != ch)) {
746 0 : nextChar = Read();
747 0 : PRInt32 followingChar = Peek();
748 0 : Pushback(nextChar);
749 0 : if (IsDigit(followingChar))
750 0 : return ParseNumber(ch, aToken);
751 : }
752 : }
753 330 : if (IsDigit(ch)) {
754 0 : return ParseNumber(ch, aToken);
755 : }
756 :
757 : // ID
758 330 : if (ch == '#') {
759 0 : return ParseRef(ch, aToken);
760 : }
761 :
762 : // STRING
763 330 : if ((ch == '"') || (ch == '\'')) {
764 0 : return ParseString(ch, aToken);
765 : }
766 :
767 : // WS
768 330 : if (IsWhitespace(ch)) {
769 220 : aToken.mType = eCSSToken_WhiteSpace;
770 220 : aToken.mIdent.Assign(PRUnichar(ch));
771 220 : EatWhiteSpace();
772 220 : return true;
773 : }
774 110 : if (ch == '/' && !IsSVGMode()) {
775 0 : PRInt32 nextChar = Peek();
776 0 : if (nextChar == '*') {
777 0 : Read();
778 : // FIXME: Editor wants comments to be preserved (bug 60290).
779 0 : if (!SkipCComment()) {
780 0 : return false;
781 : }
782 0 : continue; // start again at the beginning
783 : }
784 : }
785 110 : if (ch == '<') { // consume HTML comment tags
786 0 : if (LookAhead('!')) {
787 0 : if (LookAhead('-')) {
788 0 : if (LookAhead('-')) {
789 0 : aToken.mType = eCSSToken_HTMLComment;
790 0 : aToken.mIdent.AssignLiteral("<!--");
791 0 : return true;
792 : }
793 0 : Pushback('-');
794 : }
795 0 : Pushback('!');
796 : }
797 : }
798 110 : if (ch == '-') { // check for HTML comment end
799 0 : if (LookAhead('-')) {
800 0 : if (LookAhead('>')) {
801 0 : aToken.mType = eCSSToken_HTMLComment;
802 0 : aToken.mIdent.AssignLiteral("-->");
803 0 : return true;
804 : }
805 0 : Pushback('-');
806 : }
807 : }
808 :
809 : // INCLUDES ("~=") and DASHMATCH ("|=")
810 110 : if (( ch == '|' ) || ( ch == '~' ) || ( ch == '^' ) ||
811 : ( ch == '$' ) || ( ch == '*' )) {
812 0 : PRInt32 nextChar = Read();
813 0 : if ( nextChar == '=' ) {
814 0 : if (ch == '~') {
815 0 : aToken.mType = eCSSToken_Includes;
816 : }
817 0 : else if (ch == '|') {
818 0 : aToken.mType = eCSSToken_Dashmatch;
819 : }
820 0 : else if (ch == '^') {
821 0 : aToken.mType = eCSSToken_Beginsmatch;
822 : }
823 0 : else if (ch == '$') {
824 0 : aToken.mType = eCSSToken_Endsmatch;
825 : }
826 0 : else if (ch == '*') {
827 0 : aToken.mType = eCSSToken_Containsmatch;
828 : }
829 0 : return true;
830 0 : } else if (nextChar >= 0) {
831 0 : Pushback(nextChar);
832 : }
833 : }
834 110 : aToken.mType = eCSSToken_Symbol;
835 110 : aToken.mSymbol = ch;
836 110 : return true;
837 : }
838 : }
839 :
840 : bool
841 0 : nsCSSScanner::NextURL(nsCSSToken& aToken)
842 : {
843 0 : EatWhiteSpace();
844 :
845 0 : PRInt32 ch = Read();
846 0 : if (ch < 0) {
847 0 : return false;
848 : }
849 :
850 : // STRING
851 0 : if ((ch == '"') || (ch == '\'')) {
852 : #ifdef DEBUG
853 : bool ok =
854 : #endif
855 0 : ParseString(ch, aToken);
856 0 : NS_ABORT_IF_FALSE(ok, "ParseString should never fail, "
857 : "since there's always something read");
858 :
859 0 : NS_ABORT_IF_FALSE(aToken.mType == eCSSToken_String ||
860 : aToken.mType == eCSSToken_Bad_String,
861 : "unexpected token type");
862 0 : if (NS_LIKELY(aToken.mType == eCSSToken_String)) {
863 0 : EatWhiteSpace();
864 0 : if (LookAheadOrEOF(')')) {
865 0 : aToken.mType = eCSSToken_URL;
866 : } else {
867 0 : aToken.mType = eCSSToken_Bad_URL;
868 : }
869 : } else {
870 0 : aToken.mType = eCSSToken_Bad_URL;
871 : }
872 0 : return true;
873 : }
874 :
875 : // Process a url lexical token. A CSS1 url token can contain
876 : // characters beyond identifier characters (e.g. '/', ':', etc.)
877 : // Because of this the normal rules for tokenizing the input don't
878 : // apply very well. To simplify the parser and relax some of the
879 : // requirements on the scanner we parse url's here. If we find a
880 : // malformed URL then we emit a token of type "Bad_URL" so that
881 : // the CSS1 parser can ignore the invalid input. The parser must
882 : // treat a Bad_URL token like a Function token, and process
883 : // tokens until a matching parenthesis.
884 :
885 0 : aToken.mType = eCSSToken_Bad_URL;
886 0 : aToken.mSymbol = PRUnichar(0);
887 0 : nsString& ident = aToken.mIdent;
888 0 : ident.SetLength(0);
889 :
890 0 : Pushback(ch);
891 :
892 : // start of a non-quoted url (which may be empty)
893 0 : bool ok = true;
894 0 : for (;;) {
895 0 : ch = Read();
896 0 : if (ch < 0) break;
897 0 : if (ch == '\\') {
898 0 : if (!ParseAndAppendEscape(ident, false)) {
899 0 : ok = false;
900 0 : Pushback(ch);
901 0 : break;
902 : }
903 0 : } else if (IsWhitespace(ch)) {
904 : // Whitespace is allowed at the end of the URL
905 0 : EatWhiteSpace();
906 : // Consume the close paren if we have it; if not we're an invalid URL.
907 0 : ok = LookAheadOrEOF(')');
908 0 : break;
909 0 : } else if (ch == '"' || ch == '\'' || ch == '(' || ch < PRUnichar(' ')) {
910 : // This is an invalid URL spec
911 0 : ok = false;
912 0 : Pushback(ch); // push it back so the parser can match tokens and
913 : // then closing parenthesis
914 0 : break;
915 0 : } else if (ch == ')') {
916 : // All done
917 0 : break;
918 : } else {
919 : // A regular url character.
920 0 : ident.Append(PRUnichar(ch));
921 : }
922 : }
923 :
924 : // If the result of the above scanning is ok then change the token
925 : // type to a useful one.
926 0 : if (ok) {
927 0 : aToken.mType = eCSSToken_URL;
928 : }
929 0 : return true;
930 : }
931 :
932 :
933 : /**
934 : * Returns whether an escape was succesfully parsed; if it was not,
935 : * the backslash needs to be its own symbol token.
936 : */
937 : bool
938 0 : nsCSSScanner::ParseAndAppendEscape(nsString& aOutput, bool aInString)
939 : {
940 0 : PRInt32 ch = Read();
941 0 : if (ch < 0) {
942 0 : return false;
943 : }
944 0 : if (IsHexDigit(ch)) {
945 0 : PRInt32 rv = 0;
946 : int i;
947 0 : Pushback(ch);
948 0 : for (i = 0; i < 6; i++) { // up to six digits
949 0 : ch = Read();
950 0 : if (ch < 0) {
951 : // Whoops: error or premature eof
952 0 : break;
953 : }
954 0 : if (!IsHexDigit(ch) && !IsWhitespace(ch)) {
955 0 : Pushback(ch);
956 0 : break;
957 0 : } else if (IsHexDigit(ch)) {
958 0 : rv = rv * 16 + HexDigitValue(ch);
959 : } else {
960 0 : NS_ASSERTION(IsWhitespace(ch), "bad control flow");
961 : // single space ends escape
962 0 : break;
963 : }
964 : }
965 0 : if (6 == i) { // look for trailing whitespace and eat it
966 0 : ch = Peek();
967 0 : if (IsWhitespace(ch)) {
968 0 : (void) Read();
969 : }
970 : }
971 0 : NS_ASSERTION(rv >= 0, "How did rv become negative?");
972 : // "[at most six hexadecimal digits following a backslash] stand
973 : // for the ISO 10646 character with that number, which must not be
974 : // zero. (It is undefined in CSS 2.1 what happens if a style sheet
975 : // does contain a character with Unicode codepoint zero.)"
976 : // -- CSS2.1 section 4.1.3
977 : //
978 : // Silently deleting \0 opens a content-filtration loophole (see
979 : // bug 228856), so what we do instead is pretend the "cancels the
980 : // meaning of special characters" rule applied.
981 0 : if (rv > 0) {
982 0 : AppendUCS4ToUTF16(ENSURE_VALID_CHAR(rv), aOutput);
983 : } else {
984 0 : while (i--)
985 0 : aOutput.Append('0');
986 0 : if (IsWhitespace(ch))
987 0 : Pushback(ch);
988 : }
989 0 : return true;
990 : }
991 : // "Any character except a hexidecimal digit can be escaped to
992 : // remove its special meaning by putting a backslash in front"
993 : // -- CSS1 spec section 7.1
994 0 : if (ch == '\n') {
995 0 : if (!aInString) {
996 : // Outside of strings (which includes url() that contains a
997 : // string), escaped newlines aren't special, and just tokenize as
998 : // eCSSToken_Symbol (DELIM).
999 0 : Pushback(ch);
1000 0 : return false;
1001 : }
1002 : // In strings (and in url() containing a string), escaped newlines
1003 : // are just dropped to allow splitting over multiple lines.
1004 : } else {
1005 0 : aOutput.Append(ch);
1006 : }
1007 :
1008 0 : return true;
1009 : }
1010 :
1011 : /**
1012 : * Gather up the characters in an identifier. The identfier was
1013 : * started by "aChar" which will be appended to aIdent. The result
1014 : * will be aIdent with all of the identifier characters appended
1015 : * until the first non-identifier character is seen. The termination
1016 : * character is unread for the future re-reading.
1017 : *
1018 : * Returns failure when the character sequence does not form an ident at
1019 : * all, in which case the caller is responsible for pushing back or
1020 : * otherwise handling aChar. (This occurs only when aChar is '\'.)
1021 : */
1022 : bool
1023 220 : nsCSSScanner::GatherIdent(PRInt32 aChar, nsString& aIdent)
1024 : {
1025 220 : if (aChar == '\\') {
1026 0 : if (!ParseAndAppendEscape(aIdent, false)) {
1027 0 : return false;
1028 : }
1029 : }
1030 220 : else if (0 < aChar) {
1031 220 : aIdent.Append(aChar);
1032 : }
1033 220 : for (;;) {
1034 : // If nothing in pushback, first try to get as much as possible in one go
1035 440 : if (!mPushbackCount && mOffset < mCount) {
1036 : // See how much we can consume and append in one go
1037 220 : PRUint32 n = mOffset;
1038 : // Count number of Ident characters that can be processed
1039 3474 : while (n < mCount && IsIdent(mReadPointer[n])) {
1040 3034 : ++n;
1041 : }
1042 : // Add to the token what we have so far
1043 220 : if (n > mOffset) {
1044 : #ifdef CSS_REPORT_PARSE_ERRORS
1045 220 : mColNumber += n - mOffset;
1046 : #endif
1047 220 : aIdent.Append(&mReadPointer[mOffset], n - mOffset);
1048 220 : mOffset = n;
1049 : }
1050 : }
1051 :
1052 440 : aChar = Read();
1053 440 : if (aChar < 0) break;
1054 330 : if (aChar == '\\') {
1055 0 : if (!ParseAndAppendEscape(aIdent, false)) {
1056 0 : Pushback(aChar);
1057 0 : break;
1058 : }
1059 330 : } else if (IsIdent(aChar)) {
1060 220 : aIdent.Append(PRUnichar(aChar));
1061 : } else {
1062 110 : Pushback(aChar);
1063 110 : break;
1064 : }
1065 : }
1066 220 : return true;
1067 : }
1068 :
1069 : bool
1070 0 : nsCSSScanner::ParseRef(PRInt32 aChar, nsCSSToken& aToken)
1071 : {
1072 : // Fall back for when we don't have name characters following:
1073 0 : aToken.mType = eCSSToken_Symbol;
1074 0 : aToken.mSymbol = aChar;
1075 :
1076 0 : PRInt32 ch = Read();
1077 0 : if (ch < 0) {
1078 0 : return true;
1079 : }
1080 0 : if (IsIdent(ch) || ch == '\\') {
1081 : // First char after the '#' is a valid ident char (or an escape),
1082 : // so it makes sense to keep going
1083 : nsCSSTokenType type =
1084 0 : StartsIdent(ch, Peek()) ? eCSSToken_ID : eCSSToken_Ref;
1085 0 : aToken.mIdent.SetLength(0);
1086 0 : if (GatherIdent(ch, aToken.mIdent)) {
1087 0 : aToken.mType = type;
1088 0 : return true;
1089 : }
1090 : }
1091 :
1092 : // No ident chars after the '#'. Just unread |ch| and get out of here.
1093 0 : Pushback(ch);
1094 0 : return true;
1095 : }
1096 :
1097 : bool
1098 220 : nsCSSScanner::ParseIdent(PRInt32 aChar, nsCSSToken& aToken)
1099 : {
1100 220 : nsString& ident = aToken.mIdent;
1101 220 : ident.SetLength(0);
1102 220 : if (!GatherIdent(aChar, ident)) {
1103 0 : aToken.mType = eCSSToken_Symbol;
1104 0 : aToken.mSymbol = aChar;
1105 0 : return true;
1106 : }
1107 :
1108 220 : nsCSSTokenType tokenType = eCSSToken_Ident;
1109 : // look for functions (ie: "ident(")
1110 220 : if (Peek() == PRUnichar('(')) {
1111 0 : Read();
1112 0 : tokenType = eCSSToken_Function;
1113 :
1114 0 : if (ident.LowerCaseEqualsLiteral("url")) {
1115 0 : NextURL(aToken); // ignore return value, since *we* read something
1116 0 : return true;
1117 : }
1118 : }
1119 :
1120 220 : aToken.mType = tokenType;
1121 220 : return true;
1122 : }
1123 :
1124 : bool
1125 0 : nsCSSScanner::ParseAtKeyword(PRInt32 aChar, nsCSSToken& aToken)
1126 : {
1127 0 : aToken.mIdent.SetLength(0);
1128 0 : aToken.mType = eCSSToken_AtKeyword;
1129 0 : if (!GatherIdent(0, aToken.mIdent)) {
1130 0 : aToken.mType = eCSSToken_Symbol;
1131 0 : aToken.mSymbol = PRUnichar('@');
1132 : }
1133 0 : return true;
1134 : }
1135 :
1136 : bool
1137 0 : nsCSSScanner::ParseNumber(PRInt32 c, nsCSSToken& aToken)
1138 : {
1139 0 : NS_PRECONDITION(c == '.' || c == '+' || c == '-' || IsDigit(c),
1140 : "Why did we get called?");
1141 0 : aToken.mHasSign = (c == '+' || c == '-');
1142 :
1143 : // Our sign.
1144 0 : PRInt32 sign = c == '-' ? -1 : 1;
1145 : // Absolute value of the integer part of the mantissa. This is a double so
1146 : // we don't run into overflow issues for consumers that only care about our
1147 : // floating-point value while still being able to express the full PRInt32
1148 : // range for consumers who want integers.
1149 0 : double intPart = 0;
1150 : // Fractional part of the mantissa. This is a double so that when we convert
1151 : // to float at the end we'll end up rounding to nearest float instead of
1152 : // truncating down (as we would if fracPart were a float and we just
1153 : // effectively lost the last several digits).
1154 0 : double fracPart = 0;
1155 : // Absolute value of the power of 10 that we should multiply by (only
1156 : // relevant for numbers in scientific notation). Has to be a signed integer,
1157 : // because multiplication of signed by unsigned converts the unsigned to
1158 : // signed, so if we plan to actually multiply by expSign...
1159 0 : PRInt32 exponent = 0;
1160 : // Sign of the exponent.
1161 0 : PRInt32 expSign = 1;
1162 :
1163 0 : if (aToken.mHasSign) {
1164 0 : NS_ASSERTION(c != '.', "How did that happen?");
1165 0 : c = Read();
1166 : }
1167 :
1168 0 : bool gotDot = (c == '.');
1169 :
1170 0 : if (!gotDot) {
1171 : // Parse the integer part of the mantisssa
1172 0 : NS_ASSERTION(IsDigit(c), "Why did we get called?");
1173 0 : do {
1174 0 : intPart = 10*intPart + DecimalDigitValue(c);
1175 0 : c = Read();
1176 : // The IsDigit check will do the right thing even if Read() returns < 0
1177 : } while (IsDigit(c));
1178 :
1179 0 : gotDot = (c == '.') && IsDigit(Peek());
1180 : }
1181 :
1182 0 : if (gotDot) {
1183 : // Parse the fractional part of the mantissa.
1184 0 : c = Read();
1185 0 : NS_ASSERTION(IsDigit(c), "How did we get here?");
1186 : // Power of ten by which we need to divide our next digit
1187 0 : float divisor = 10;
1188 0 : do {
1189 0 : fracPart += DecimalDigitValue(c) / divisor;
1190 0 : divisor *= 10;
1191 0 : c = Read();
1192 : // The IsDigit check will do the right thing even if Read() returns < 0
1193 : } while (IsDigit(c));
1194 : }
1195 :
1196 0 : bool gotE = false;
1197 0 : if (IsSVGMode() && (c == 'e' || c == 'E')) {
1198 0 : PRInt32 nextChar = Peek();
1199 0 : PRInt32 expSignChar = 0;
1200 0 : if (nextChar == '-' || nextChar == '+') {
1201 0 : expSignChar = Read();
1202 0 : nextChar = Peek();
1203 : }
1204 0 : if (IsDigit(nextChar)) {
1205 0 : gotE = true;
1206 0 : if (expSignChar == '-') {
1207 0 : expSign = -1;
1208 : }
1209 :
1210 0 : c = Read();
1211 0 : NS_ASSERTION(IsDigit(c), "Peek() must have lied");
1212 0 : do {
1213 0 : exponent = 10*exponent + DecimalDigitValue(c);
1214 0 : c = Read();
1215 : // The IsDigit check will do the right thing even if Read() returns < 0
1216 : } while (IsDigit(c));
1217 : } else {
1218 0 : if (expSignChar) {
1219 0 : Pushback(expSignChar);
1220 : }
1221 : }
1222 : }
1223 :
1224 0 : nsCSSTokenType type = eCSSToken_Number;
1225 :
1226 : // Set mIntegerValid for all cases (except %, below) because we need
1227 : // it for the "2n" in :nth-child(2n).
1228 0 : aToken.mIntegerValid = false;
1229 :
1230 : // Time to reassemble our number.
1231 0 : float value = float(sign * (intPart + fracPart));
1232 0 : if (gotE) {
1233 : // pow(), not powf(), because at least wince doesn't have the latter.
1234 : // And explicitly cast everything to doubles to avoid issues with
1235 : // overloaded pow() on Windows.
1236 0 : value *= pow(10.0, double(expSign * exponent));
1237 0 : } else if (!gotDot) {
1238 : // Clamp values outside of integer range.
1239 0 : if (sign > 0) {
1240 0 : aToken.mInteger = PRInt32(NS_MIN(intPart, double(PR_INT32_MAX)));
1241 : } else {
1242 0 : aToken.mInteger = PRInt32(NS_MAX(-intPart, double(PR_INT32_MIN)));
1243 : }
1244 0 : aToken.mIntegerValid = true;
1245 : }
1246 :
1247 0 : nsString& ident = aToken.mIdent;
1248 0 : ident.Truncate();
1249 :
1250 : // Look at character that terminated the number
1251 0 : if (c >= 0) {
1252 0 : if (StartsIdent(c, Peek())) {
1253 0 : if (GatherIdent(c, ident)) {
1254 0 : type = eCSSToken_Dimension;
1255 : }
1256 0 : } else if ('%' == c) {
1257 0 : type = eCSSToken_Percentage;
1258 0 : value = value / 100.0f;
1259 0 : aToken.mIntegerValid = false;
1260 : } else {
1261 : // Put back character that stopped numeric scan
1262 0 : Pushback(c);
1263 : }
1264 : }
1265 0 : aToken.mNumber = value;
1266 0 : aToken.mType = type;
1267 0 : return true;
1268 : }
1269 :
1270 : bool
1271 0 : nsCSSScanner::SkipCComment()
1272 : {
1273 0 : for (;;) {
1274 0 : PRInt32 ch = Read();
1275 0 : if (ch < 0) break;
1276 0 : if (ch == '*') {
1277 0 : if (LookAhead('/')) {
1278 0 : return true;
1279 : }
1280 : }
1281 : }
1282 :
1283 0 : REPORT_UNEXPECTED_EOF(PECommentEOF);
1284 0 : return false;
1285 : }
1286 :
1287 : bool
1288 0 : nsCSSScanner::ParseString(PRInt32 aStop, nsCSSToken& aToken)
1289 : {
1290 0 : aToken.mIdent.SetLength(0);
1291 0 : aToken.mType = eCSSToken_String;
1292 0 : aToken.mSymbol = PRUnichar(aStop); // remember how it's quoted
1293 0 : for (;;) {
1294 : // If nothing in pushback, first try to get as much as possible in one go
1295 0 : if (!mPushbackCount && mOffset < mCount) {
1296 : // See how much we can consume and append in one go
1297 0 : PRUint32 n = mOffset;
1298 : // Count number of characters that can be processed
1299 0 : for (;n < mCount; ++n) {
1300 0 : PRUnichar nextChar = mReadPointer[n];
1301 0 : if ((nextChar == aStop) || (nextChar == '\\') ||
1302 : (nextChar == '\n') || (nextChar == '\r') || (nextChar == '\f')) {
1303 0 : break;
1304 : }
1305 : #ifdef CSS_REPORT_PARSE_ERRORS
1306 0 : ++mColNumber;
1307 : #endif
1308 : }
1309 : // Add to the token what we have so far
1310 0 : if (n > mOffset) {
1311 0 : aToken.mIdent.Append(&mReadPointer[mOffset], n - mOffset);
1312 0 : mOffset = n;
1313 : }
1314 : }
1315 0 : PRInt32 ch = Read();
1316 0 : if (ch < 0 || ch == aStop) {
1317 0 : break;
1318 : }
1319 0 : if (ch == '\n') {
1320 0 : aToken.mType = eCSSToken_Bad_String;
1321 : #ifdef CSS_REPORT_PARSE_ERRORS
1322 0 : ReportUnexpectedToken(aToken, "SEUnterminatedString");
1323 : #endif
1324 0 : break;
1325 : }
1326 0 : if (ch == '\\') {
1327 0 : if (!ParseAndAppendEscape(aToken.mIdent, true)) {
1328 0 : aToken.mType = eCSSToken_Bad_String;
1329 0 : Pushback(ch);
1330 : #ifdef CSS_REPORT_PARSE_ERRORS
1331 : // For strings, the only case where ParseAndAppendEscape will
1332 : // return false is when there's a backslash to start an escape
1333 : // immediately followed by end-of-stream. In that case, the
1334 : // correct tokenization is badstring *followed* by a DELIM for
1335 : // the backslash, but as far as the author is concerned, it
1336 : // works pretty much the same as an unterminated string, so we
1337 : // use the same error message.
1338 0 : ReportUnexpectedToken(aToken, "SEUnterminatedString");
1339 : #endif
1340 0 : break;
1341 : }
1342 : } else {
1343 0 : aToken.mIdent.Append(ch);
1344 : }
1345 : }
1346 0 : return true;
1347 : }
1348 :
1349 : // UNICODE-RANGE tokens match the regular expression
1350 : //
1351 : // u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
1352 : //
1353 : // However, some such tokens are "invalid". There are three valid forms:
1354 : //
1355 : // u+[0-9a-f]{x} 1 <= x <= 6
1356 : // u+[0-9a-f]{x}\?{y} 1 <= x+y <= 6
1357 : // u+[0-9a-f]{x}-[0-9a-f]{y} 1 <= x <= 6, 1 <= y <= 6
1358 : //
1359 : // All unicode-range tokens have their text recorded in mIdent; valid ones
1360 : // are also decoded into mInteger and mInteger2, and mIntegerValid is set.
1361 :
1362 : bool
1363 0 : nsCSSScanner::ParseURange(PRInt32 aChar, nsCSSToken& aResult)
1364 : {
1365 0 : PRInt32 intro2 = Read();
1366 0 : PRInt32 ch = Peek();
1367 :
1368 : // We should only ever be called if these things are true.
1369 0 : NS_ASSERTION(aChar == 'u' || aChar == 'U',
1370 : "unicode-range called with improper introducer (U)");
1371 0 : NS_ASSERTION(intro2 == '+',
1372 : "unicode-range called with improper introducer (+)");
1373 :
1374 : // If the character immediately after the '+' is not a hex digit or
1375 : // '?', this is not really a unicode-range token; push everything
1376 : // back and scan the U as an ident.
1377 0 : if (!IsHexDigit(ch) && ch != '?') {
1378 0 : Pushback(intro2);
1379 0 : Pushback(aChar);
1380 0 : return ParseIdent(aChar, aResult);
1381 : }
1382 :
1383 0 : aResult.mIdent.Truncate();
1384 0 : aResult.mIdent.Append(aChar);
1385 0 : aResult.mIdent.Append(intro2);
1386 :
1387 0 : bool valid = true;
1388 0 : bool haveQues = false;
1389 0 : PRUint32 low = 0;
1390 0 : PRUint32 high = 0;
1391 0 : int i = 0;
1392 :
1393 0 : for (;;) {
1394 0 : ch = Read();
1395 0 : i++;
1396 0 : if (i == 7 || !(IsHexDigit(ch) || ch == '?')) {
1397 : break;
1398 : }
1399 :
1400 0 : aResult.mIdent.Append(ch);
1401 0 : if (IsHexDigit(ch)) {
1402 0 : if (haveQues) {
1403 0 : valid = false; // all question marks should be at the end
1404 : }
1405 0 : low = low*16 + HexDigitValue(ch);
1406 0 : high = high*16 + HexDigitValue(ch);
1407 : } else {
1408 0 : haveQues = true;
1409 0 : low = low*16 + 0x0;
1410 0 : high = high*16 + 0xF;
1411 : }
1412 : }
1413 :
1414 0 : if (ch == '-' && IsHexDigit(Peek())) {
1415 0 : if (haveQues) {
1416 0 : valid = false;
1417 : }
1418 :
1419 0 : aResult.mIdent.Append(ch);
1420 0 : high = 0;
1421 0 : i = 0;
1422 0 : for (;;) {
1423 0 : ch = Read();
1424 0 : i++;
1425 0 : if (i == 7 || !IsHexDigit(ch)) {
1426 : break;
1427 : }
1428 0 : aResult.mIdent.Append(ch);
1429 0 : high = high*16 + HexDigitValue(ch);
1430 : }
1431 : }
1432 0 : Pushback(ch);
1433 :
1434 0 : aResult.mInteger = low;
1435 0 : aResult.mInteger2 = high;
1436 0 : aResult.mIntegerValid = valid;
1437 0 : aResult.mType = eCSSToken_URange;
1438 0 : return true;
1439 : }
|