/** * Scan a Hash token. Handles the distinction between eCSSToken_ID * and eCSSToken_Hash, and handles production of Symbol when a '#' * is not followed by identifier characters. */ bool nsCSSScanner::ScanHash(nsCSSToken& aToken) { MOZ_ASSERT(Peek() == '#', "should not have been called"); // Fall back for when '#' isn't followed by identifier characters. aToken.mSymbol = '#'; Advance(); int32_t ch = Peek(); if (IsIdentChar(ch) || ch == '\\') { nsCSSTokenType type = StartsIdent(ch, Peek(1)) ? eCSSToken_ID : eCSSToken_Hash; aToken.mIdent.SetLength(0); if (GatherText(IS_IDCHAR, aToken.mIdent)) { aToken.mType = type; } } return true; }
PRBool nsCSSScanner::ParseRef(PRInt32 aChar, nsCSSToken& aToken) { aToken.mIdent.SetLength(0); aToken.mType = eCSSToken_Ref; PRInt32 ch = Read(); if (ch < 0) { return PR_FALSE; } if (IsIdent(ch) || ch == CSS_ESCAPE) { // First char after the '#' is a valid ident char (or an escape), // so it makes sense to keep going if (StartsIdent(ch, Peek())) { aToken.mType = eCSSToken_ID; } return GatherIdent(ch, aToken.mIdent); } // No ident chars after the '#'. Just unread |ch| and get out of here. Pushback(ch); return PR_TRUE; }
PRBool nsCSSScanner::ParseRef(nsresult& aErrorCode, PRInt32 aChar, nsCSSToken& aToken) { aToken.mIdent.SetLength(0); aToken.mType = eCSSToken_Ref; PRInt32 ch = Read(aErrorCode); if (ch < 0) { return PR_FALSE; } if (ch > 255 || (gLexTable[ch] & IS_IDENT) || ch == CSS_ESCAPE) { // First char after the '#' is a valid ident char (or an escape), // so it makes sense to keep going if (StartsIdent(ch, Peek(aErrorCode), gLexTable)) { aToken.mType = eCSSToken_ID; } return GatherIdent(aErrorCode, ch, aToken.mIdent); } // No ident chars after the '#'. Just unread |ch| and get out of here. Unread(); return PR_TRUE; }
PRBool nsCSSScanner::Next(nsCSSToken& aToken) { for (;;) { // Infinite loop so we can restart after comments. PRInt32 ch = Read(); if (ch < 0) { return PR_FALSE; } // UNICODE-RANGE if ((ch == 'u' || ch == 'U') && Peek() == '+') return ParseURange(ch, aToken); // IDENT if (StartsIdent(ch, Peek())) return ParseIdent(ch, aToken); // AT_KEYWORD if (ch == '@') { PRInt32 nextChar = Read(); if (nextChar >= 0) { PRInt32 followingChar = Peek(); Pushback(nextChar); if (StartsIdent(nextChar, followingChar)) return ParseAtKeyword(ch, aToken); } } // NUMBER or DIM if ((ch == '.') || (ch == '+') || (ch == '-')) { PRInt32 nextChar = Peek(); if (IsDigit(nextChar)) { return ParseNumber(ch, aToken); } else if (('.' == nextChar) && ('.' != ch)) { nextChar = Read(); PRInt32 followingChar = Peek(); Pushback(nextChar); if (IsDigit(followingChar)) return ParseNumber(ch, aToken); } } if (IsDigit(ch)) { return ParseNumber(ch, aToken); } // ID if (ch == '#') { return ParseRef(ch, aToken); } // STRING if ((ch == '"') || (ch == '\'')) { return ParseString(ch, aToken); } // WS if (IsWhitespace(ch)) { aToken.mType = eCSSToken_WhiteSpace; aToken.mIdent.Assign(PRUnichar(ch)); EatWhiteSpace(); return PR_TRUE; } if (ch == '/' && !IsSVGMode()) { PRInt32 nextChar = Peek(); if (nextChar == '*') { (void) Read(); #if 0 // If we change our storage data structures such that comments are // stored (for Editor), we should reenable this code, condition it // on being in editor mode, and apply glazou's patch from bug // 60290. aToken.mIdent.SetCapacity(2); aToken.mIdent.Assign(PRUnichar(ch)); aToken.mIdent.Append(PRUnichar(nextChar)); return ParseCComment(aToken); #endif if (!SkipCComment()) { return PR_FALSE; } continue; // start again at the beginning } } if (ch == '<') { // consume HTML comment tags if (LookAhead('!')) { if (LookAhead('-')) { if (LookAhead('-')) { aToken.mType = eCSSToken_HTMLComment; aToken.mIdent.AssignLiteral("<!--"); return PR_TRUE; } Pushback('-'); } Pushback('!'); } } if (ch == '-') { // check for HTML comment end if (LookAhead('-')) { if (LookAhead('>')) { aToken.mType = eCSSToken_HTMLComment; aToken.mIdent.AssignLiteral("-->"); return PR_TRUE; } Pushback('-'); } } // INCLUDES ("~=") and DASHMATCH ("|=") if (( ch == '|' ) || ( ch == '~' ) || ( ch == '^' ) || ( ch == '$' ) || ( ch == '*' )) { PRInt32 nextChar = Read(); if ( nextChar == '=' ) { if (ch == '~') { aToken.mType = eCSSToken_Includes; } else if (ch == '|') { aToken.mType = eCSSToken_Dashmatch; } else if (ch == '^') { aToken.mType = eCSSToken_Beginsmatch; } else if (ch == '$') { aToken.mType = eCSSToken_Endsmatch; } else if (ch == '*') { aToken.mType = eCSSToken_Containsmatch; } return PR_TRUE; } else if (nextChar >= 0) { Pushback(nextChar); } } aToken.mType = eCSSToken_Symbol; aToken.mSymbol = ch; return PR_TRUE; } }
PRBool nsCSSScanner::ParseNumber(PRInt32 c, nsCSSToken& aToken) { NS_PRECONDITION(c == '.' || c == '+' || c == '-' || IsDigit(c), "Why did we get called?"); aToken.mHasSign = (c == '+' || c == '-'); // Our sign. PRInt32 sign = c == '-' ? -1 : 1; // Absolute value of the integer part of the mantissa. This is a double so // we don't run into overflow issues for consumers that only care about our // floating-point value while still being able to express the full PRInt32 // range for consumers who want integers. double intPart = 0; // Fractional part of the mantissa. This is a double so that when we convert // to float at the end we'll end up rounding to nearest float instead of // truncating down (as we would if fracPart were a float and we just // effectively lost the last several digits). double fracPart = 0; // Absolute value of the power of 10 that we should multiply by (only // relevant for numbers in scientific notation). Has to be a signed integer, // because multiplication of signed by unsigned converts the unsigned to // signed, so if we plan to actually multiply by expSign... PRInt32 exponent = 0; // Sign of the exponent. PRInt32 expSign = 1; if (aToken.mHasSign) { NS_ASSERTION(c != '.', "How did that happen?"); c = Read(); } PRBool gotDot = (c == '.'); if (!gotDot) { // Parse the integer part of the mantisssa NS_ASSERTION(IsDigit(c), "Why did we get called?"); do { intPart = 10*intPart + DecimalDigitValue(c); c = Read(); // The IsDigit check will do the right thing even if Read() returns < 0 } while (IsDigit(c)); gotDot = (c == '.') && IsDigit(Peek()); } if (gotDot) { // Parse the fractional part of the mantissa. c = Read(); NS_ASSERTION(IsDigit(c), "How did we get here?"); // Power of ten by which we need to divide our next digit float divisor = 10; do { fracPart += DecimalDigitValue(c) / divisor; divisor *= 10; c = Read(); // The IsDigit check will do the right thing even if Read() returns < 0 } while (IsDigit(c)); } PRBool gotE = PR_FALSE; if (IsSVGMode() && (c == 'e' || c == 'E')) { PRInt32 nextChar = Peek(); PRInt32 expSignChar = 0; if (nextChar == '-' || nextChar == '+') { expSignChar = Read(); nextChar = Peek(); } if (IsDigit(nextChar)) { gotE = PR_TRUE; if (expSignChar == '-') { expSign = -1; } c = Read(); NS_ASSERTION(IsDigit(c), "Peek() must have lied"); do { exponent = 10*exponent + DecimalDigitValue(c); c = Read(); // The IsDigit check will do the right thing even if Read() returns < 0 } while (IsDigit(c)); } else { if (expSignChar) { Pushback(expSignChar); } } } nsCSSTokenType type = eCSSToken_Number; // Set mIntegerValid for all cases (except %, below) because we need // it for the "2n" in :nth-child(2n). aToken.mIntegerValid = PR_FALSE; // Time to reassemble our number. float value = float(sign * (intPart + fracPart)); if (gotE) { // pow(), not powf(), because at least wince doesn't have the latter. // And explicitly cast everything to doubles to avoid issues with // overloaded pow() on Windows. value *= pow(10.0, double(expSign * exponent)); } else if (!gotDot) { // Clamp values outside of integer range. if (sign > 0) { aToken.mInteger = PRInt32(NS_MIN(intPart, double(PR_INT32_MAX))); } else { aToken.mInteger = PRInt32(NS_MAX(-intPart, double(PR_INT32_MIN))); } aToken.mIntegerValid = PR_TRUE; } nsString& ident = aToken.mIdent; ident.Truncate(); // Look at character that terminated the number if (c >= 0) { if (StartsIdent(c, Peek())) { if (!GatherIdent(c, ident)) { return PR_FALSE; } type = eCSSToken_Dimension; } else if ('%' == c) { type = eCSSToken_Percentage; value = value / 100.0f; aToken.mIntegerValid = PR_FALSE; } else { // Put back character that stopped numeric scan Pushback(c); } } aToken.mNumber = value; aToken.mType = type; return PR_TRUE; }
PRBool nsCSSScanner::Next(nsresult& aErrorCode, nsCSSToken& aToken) { PRInt32 ch = Read(aErrorCode); if (ch < 0) { return PR_FALSE; } PRUint8* lexTable = gLexTable; // IDENT if (StartsIdent(ch, Peek(aErrorCode), lexTable)) return ParseIdent(aErrorCode, ch, aToken); // From this point on, 0 <= ch < 256. // AT_KEYWORD if (ch == '@') { PRInt32 nextChar = Read(aErrorCode); PRInt32 followingChar = Peek(aErrorCode); Pushback(nextChar); if (StartsIdent(nextChar, followingChar, lexTable)) return ParseAtKeyword(aErrorCode, ch, aToken); } // NUMBER or DIM if ((ch == '.') || (ch == '+') || (ch == '-')) { PRInt32 nextChar = Peek(aErrorCode); if (CheckLexTable(nextChar, IS_DIGIT, lexTable)) { return ParseNumber(aErrorCode, ch, aToken); } else if (('.' == nextChar) && ('.' != ch)) { nextChar = Read(aErrorCode); PRInt32 followingChar = Peek(aErrorCode); Pushback(nextChar); if (CheckLexTable(followingChar, IS_DIGIT, lexTable)) return ParseNumber(aErrorCode, ch, aToken); } } if ((lexTable[ch] & IS_DIGIT) != 0) { return ParseNumber(aErrorCode, ch, aToken); } // ID if (ch == '#') { return ParseRef(aErrorCode, ch, aToken); } // STRING if ((ch == '"') || (ch == '\'')) { return ParseString(aErrorCode, ch, aToken); } // WS if ((lexTable[ch] & IS_WHITESPACE) != 0) { aToken.mType = eCSSToken_WhiteSpace; aToken.mIdent.Assign(PRUnichar(ch)); (void) EatWhiteSpace(aErrorCode); return PR_TRUE; } if (ch == '/') { PRInt32 nextChar = Peek(aErrorCode); if (nextChar == '*') { (void) Read(aErrorCode); #if 0 // If we change our storage data structures such that comments are // stored (for Editor), we should reenable this code, condition it // on being in editor mode, and apply glazou's patch from bug // 60290. aToken.mIdent.SetCapacity(2); aToken.mIdent.Assign(PRUnichar(ch)); aToken.mIdent.Append(PRUnichar(nextChar)); return ParseCComment(aErrorCode, aToken); #endif return SkipCComment(aErrorCode) && Next(aErrorCode, aToken); } } if (ch == '<') { // consume HTML comment tags if (LookAhead(aErrorCode, '!')) { if (LookAhead(aErrorCode, '-')) { if (LookAhead(aErrorCode, '-')) { aToken.mType = eCSSToken_HTMLComment; aToken.mIdent.AssignLiteral("<!--"); return PR_TRUE; } Pushback('-'); } Pushback('!'); } } if (ch == '-') { // check for HTML comment end if (LookAhead(aErrorCode, '-')) { if (LookAhead(aErrorCode, '>')) { aToken.mType = eCSSToken_HTMLComment; aToken.mIdent.AssignLiteral("-->"); return PR_TRUE; } Pushback('-'); } } // INCLUDES ("~=") and DASHMATCH ("|=") if (( ch == '|' ) || ( ch == '~' ) || ( ch == '^' ) || ( ch == '$' ) || ( ch == '*' )) { PRInt32 nextChar = Read(aErrorCode); if ( nextChar == '=' ) { if (ch == '~') { aToken.mType = eCSSToken_Includes; } else if (ch == '|') { aToken.mType = eCSSToken_Dashmatch; } else if (ch == '^') { aToken.mType = eCSSToken_Beginsmatch; } else if (ch == '$') { aToken.mType = eCSSToken_Endsmatch; } else if (ch == '*') { aToken.mType = eCSSToken_Containsmatch; } return PR_TRUE; } else { Pushback(nextChar); } } aToken.mType = eCSSToken_Symbol; aToken.mSymbol = ch; return PR_TRUE; }
/** * Scan a Number, Percentage, or Dimension token (all of which begin * like a Number). Can produce a Symbol when a '.' is not followed by * digits, or when '+' or '-' are not followed by either a digit or a * '.' and then a digit. Can also produce a HTMLComment when it * encounters '-->'. */ bool nsCSSScanner::ScanNumber(nsCSSToken& aToken) { int32_t c = Peek(); #ifdef DEBUG { int32_t c2 = Peek(1); int32_t c3 = Peek(2); MOZ_ASSERT(IsDigit(c) || (IsDigit(c2) && (c == '.' || c == '+' || c == '-')) || (IsDigit(c3) && (c == '+' || c == '-') && c2 == '.'), "should not have been called"); } #endif // Sign of the mantissa (-1 or 1). int32_t sign = c == '-' ? -1 : 1; // Absolute value of the integer part of the mantissa. This is a double so // we don't run into overflow issues for consumers that only care about our // floating-point value while still being able to express the full int32_t // range for consumers who want integers. double intPart = 0; // Fractional part of the mantissa. This is a double so that when we convert // to float at the end we'll end up rounding to nearest float instead of // truncating down (as we would if fracPart were a float and we just // effectively lost the last several digits). double fracPart = 0; // Absolute value of the power of 10 that we should multiply by (only // relevant for numbers in scientific notation). Has to be a signed integer, // because multiplication of signed by unsigned converts the unsigned to // signed, so if we plan to actually multiply by expSign... int32_t exponent = 0; // Sign of the exponent. int32_t expSign = 1; aToken.mHasSign = (c == '+' || c == '-'); if (aToken.mHasSign) { Advance(); c = Peek(); } bool gotDot = (c == '.'); if (!gotDot) { // Scan the integer part of the mantissa. MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above"); do { intPart = 10*intPart + DecimalDigitValue(c); Advance(); c = Peek(); } while (IsDigit(c)); gotDot = (c == '.') && IsDigit(Peek(1)); } if (gotDot) { // Scan the fractional part of the mantissa. Advance(); c = Peek(); MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above"); // Power of ten by which we need to divide our next digit double divisor = 10; do { fracPart += DecimalDigitValue(c) / divisor; divisor *= 10; Advance(); c = Peek(); } while (IsDigit(c)); } bool gotE = false; if (c == 'e' || c == 'E') { int32_t expSignChar = Peek(1); int32_t nextChar = Peek(2); if (IsDigit(expSignChar) || ((expSignChar == '-' || expSignChar == '+') && IsDigit(nextChar))) { gotE = true; if (expSignChar == '-') { expSign = -1; } Advance(); // consumes the E if (expSignChar == '-' || expSignChar == '+') { Advance(); c = nextChar; } else { c = expSignChar; } MOZ_ASSERT(IsDigit(c), "should have been excluded by logic above"); do { exponent = 10*exponent + DecimalDigitValue(c); Advance(); c = Peek(); } while (IsDigit(c)); } } nsCSSTokenType type = eCSSToken_Number; // Set mIntegerValid for all cases (except %, below) because we need // it for the "2n" in :nth-child(2n). aToken.mIntegerValid = false; // Time to reassemble our number. // Do all the math in double precision so it's truncated only once. double value = sign * (intPart + fracPart); if (gotE) { // Explicitly cast expSign*exponent to double to avoid issues with // overloaded pow() on Windows. value *= pow(10.0, double(expSign * exponent)); } else if (!gotDot) { // Clamp values outside of integer range. if (sign > 0) { aToken.mInteger = int32_t(std::min(intPart, double(INT32_MAX))); } else { aToken.mInteger = int32_t(std::max(-intPart, double(INT32_MIN))); } aToken.mIntegerValid = true; } nsString& ident = aToken.mIdent; // Check for Dimension and Percentage tokens. if (c >= 0) { if (StartsIdent(c, Peek(1))) { if (GatherText(IS_IDCHAR, ident)) { type = eCSSToken_Dimension; } } else if (c == '%') { Advance(); type = eCSSToken_Percentage; value = value / 100.0f; aToken.mIntegerValid = false; } } aToken.mNumber = value; aToken.mType = type; return true; }
PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c, nsCSSToken& aToken) { nsString& ident = aToken.mIdent; ident.SetLength(0); PRBool gotDot = (c == '.') ? PR_TRUE : PR_FALSE; if (c != '+') { ident.Append(PRUnichar(c)); } // Gather up characters that make up the number for (;;) { c = Read(aErrorCode); if (c < 0) break; if (!gotDot && (c == '.') && IsDigit(Peek(aErrorCode))) { gotDot = PR_TRUE; } else if (!IsDigit(c)) { break; } ident.Append(PRUnichar(c)); } // Convert number to floating point nsCSSTokenType type = eCSSToken_Number; PRInt32 ec; float value = ident.ToFloat(&ec); // Look at character that terminated the number aToken.mIntegerValid = PR_FALSE; if (c >= 0) { if (StartsIdent(c, Peek(aErrorCode))) { ident.SetLength(0); if (!GatherIdent(aErrorCode, c, ident)) { return PR_FALSE; } type = eCSSToken_Dimension; } else if ('%' == c) { type = eCSSToken_Percentage; value = value / 100.0f; ident.SetLength(0); } else { // Put back character that stopped numeric scan Pushback(c); if (!gotDot) { aToken.mInteger = ident.ToInteger(&ec); aToken.mIntegerValid = PR_TRUE; } ident.SetLength(0); } } else { // stream ended if (!gotDot) { aToken.mInteger = ident.ToInteger(&ec); aToken.mIntegerValid = PR_TRUE; } ident.SetLength(0); } aToken.mNumber = value; aToken.mType = type; return PR_TRUE; }