gecko-dev/layout/style/nsCSSScanner.cpp

585 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsCSSScanner.h"
#include "nsIInputStream.h"
#include "nsIUnicharInputStream.h"
#include "nsString.h"
#include "nsCRT.h"
#ifdef NS_DEBUG
static char* kNullPointer = "null pointer";
#endif
// Don't bother collecting whitespace characters in token's mIdent buffer
#undef COLLECT_WHITESPACE
#define BUFFER_SIZE 256
static const PRUnichar CSS_ESCAPE = PRUnichar('\\');
static const PRUint8 IS_LATIN1 = 0x01;
static const PRUint8 IS_DIGIT = 0x02;
static const PRUint8 IS_HEX_DIGIT = 0x04;
static const PRUint8 IS_ALPHA = 0x08;
static const PRUint8 START_IDENT = 0x10;
static const PRUint8 IS_IDENT = 0x20;
static const PRUint8 IS_WHITESPACE = 0x40;
static PRUint8* gLexTable;
static void BuildLexTable()
{
PRUint8* lt = new PRUint8[256];
nsCRT::zero(lt, 256);
gLexTable = lt;
int i;
lt[CSS_ESCAPE] = START_IDENT;
lt['-'] |= IS_IDENT;
// XXX add in other whitespace chars
lt[' '] |= IS_WHITESPACE;
lt['\t'] |= IS_WHITESPACE;
lt['\r'] |= IS_WHITESPACE;
lt['\n'] |= IS_WHITESPACE;
for (i = 161; i <= 255; i++) {
lt[i] |= IS_LATIN1 | IS_IDENT | START_IDENT;
}
for (i = '0'; i <= '9'; i++) {
lt[i] |= IS_DIGIT | IS_HEX_DIGIT | IS_IDENT;
}
for (i = 'A'; i <= 'Z'; i++) {
if ((i >= 'A') && (i <= 'F')) {
lt[i] |= IS_HEX_DIGIT;
lt[i+32] |= IS_HEX_DIGIT;
}
lt[i] |= IS_ALPHA | IS_IDENT | START_IDENT;
lt[i+32] |= IS_ALPHA | IS_IDENT | START_IDENT;
}
}
nsCSSToken::nsCSSToken()
{
mType = eCSSToken_Symbol;
}
nsCSSScanner::nsCSSScanner()
{
if (nsnull == gLexTable) {
// XXX need a monitor
BuildLexTable();
}
mInput = nsnull;
mBuffer = new PRUnichar[BUFFER_SIZE];
mOffset = 0;
mCount = 0;
mLookAhead = -1;
}
nsCSSScanner::~nsCSSScanner()
{
Close();
if (nsnull != mBuffer) {
delete mBuffer;
mBuffer = nsnull;
}
}
void nsCSSScanner::Init(nsIUnicharInputStream* aInput)
{
NS_PRECONDITION(nsnull != aInput, kNullPointer);
Close();
mInput = aInput;
if (nsnull != aInput) {
aInput->AddRef();
}
}
void nsCSSScanner::Close()
{
NS_IF_RELEASE(mInput);
}
// Returns -1 on error or eof
PRInt32 nsCSSScanner::Read(PRInt32* aErrorCode)
{
PRInt32 rv;
if (mLookAhead >= 0) {
rv = mLookAhead;
mLookAhead = -1;
} else {
if (mCount < 0) {
return -1;
}
if (mOffset == mCount) {
mOffset = 0;
mCount = mInput->Read(aErrorCode, mBuffer, 0, BUFFER_SIZE);
if (mCount <= 0) {
return -1;
}
}
rv = PRInt32(mBuffer[mOffset++]);
}
mLastRead = rv;
//printf("Read => %x\n", rv);
return rv;
}
PRInt32 nsCSSScanner::Peek(PRInt32* aErrorCode)
{
if (mLookAhead < 0) {
mLookAhead = Read(aErrorCode);
if (mLookAhead < 0) {
return -1;
}
}
//printf("Peek => %x\n", mLookAhead);
return mLookAhead;
}
void nsCSSScanner::Unread()
{
NS_PRECONDITION((mLastRead >= 0) && (mLookAhead < 0), "double pushback");
mLookAhead = mLastRead;
mLastRead = -1;
}
PRBool nsCSSScanner::LookAhead(PRInt32* aErrorCode, PRUnichar aChar)
{
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
return PR_FALSE;
}
if (ch == aChar) {
return PR_TRUE;
}
Unread();
return PR_FALSE;
}
PRBool nsCSSScanner::EatWhiteSpace(PRInt32* aErrorCode)
{
PRBool eaten = PR_FALSE;
for (;;) {
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
break;
}
if ((ch == ' ') || (ch == '\n') || (ch == '\r') || (ch == '\t')) {
eaten = PR_TRUE;
continue;
}
Unread();
break;
}
return eaten;
}
PRBool nsCSSScanner::EatNewline(PRInt32* aErrorCode)
{
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
return PR_FALSE;
}
PRBool eaten = PR_FALSE;
if (ch == '\r') {
eaten = PR_TRUE;
ch = Peek(aErrorCode);
if (ch == '\n') {
(void) Read(aErrorCode);
}
} else if (ch == '\n') {
eaten = PR_TRUE;
} else {
Unread();
}
return eaten;
}
PRBool nsCSSScanner::Next(PRInt32* aErrorCode, nsCSSToken* aToken)
{
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
return PR_FALSE;
}
if (ch < 256) {
PRUint8* lexTable = gLexTable;
// IDENT
if ((lexTable[ch] & START_IDENT) != 0) {
return ParseIdent(aErrorCode, ch, aToken);
}
// AT_KEYWORD
if (ch == '@') {
PRInt32 nextChar = Peek(aErrorCode);
if ((nextChar >= 0) && (nextChar <= 255)) {
if ((lexTable[nextChar] & START_IDENT) != 0) {
return ParseAtKeyword(aErrorCode, ch, aToken);
}
}
}
// NUMBER or DIM
if ((ch == '.') || (ch == '+') || (ch == '-')) {
PRInt32 nextChar = Peek(aErrorCode);
if ((nextChar >= 0) && (nextChar <= 255)) {
if ((lexTable[nextChar] & IS_DIGIT) != 0) {
return ParseNumber(aErrorCode, ch, aToken);
}
}
}
if ((lexTable[ch] & IS_DIGIT) != 0) {
return ParseNumber(aErrorCode, ch, aToken);
}
// ID
if (ch == '#') {
return ParseID(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.SetLength(0);
aToken->mIdent.Append(PRUnichar(ch));
(void) EatWhiteSpace(aErrorCode);
return PR_TRUE;
}
if (ch == '/') {
PRInt32 nextChar = Peek(aErrorCode);
if (nextChar == '/') {
(void) Read(aErrorCode);
aToken->mIdent.SetLength(0);
aToken->mIdent.Append(PRUnichar(ch));
aToken->mIdent.Append(PRUnichar(ch));
return ParseEOLComment(aErrorCode, aToken);
} else if (nextChar == '*') {
(void) Read(aErrorCode);
aToken->mIdent.SetLength(0);
aToken->mIdent.Append(PRUnichar(ch));
aToken->mIdent.Append(PRUnichar(nextChar));
return ParseCComment(aErrorCode, aToken);
}
}
}
aToken->mType = eCSSToken_Symbol;
aToken->mSymbol = ch;
return PR_TRUE;
}
PRInt32 nsCSSScanner::ParseEscape(PRInt32* aErrorCode)
{
PRUint8* lexTable = gLexTable;
PRInt32 ch = Peek(aErrorCode);
if (ch < 0) {
return CSS_ESCAPE;
}
if ((ch <= 255) && ((lexTable[ch] & IS_HEX_DIGIT) != 0)) {
PRInt32 rv = 0;
for (int i = 0; i < 4; i++) {
ch = Read(aErrorCode);
if (ch < 0) {
// Whoops: error or premature eof
break;
}
if ((lexTable[ch] & IS_HEX_DIGIT) != 0) {
if ((lexTable[ch] & IS_DIGIT) != 0) {
rv = rv * 16 + (ch - '0');
} else {
// Note: c&7 just keeps the low three bits which causes
// upper and lower case alphabetics to both yield their
// "relative to 10" value for computing the hex value.
rv = rv * 16 + (ch & 0x7) + 10;
}
} else {
Unread();
break;
}
}
return rv;
} else {
// "Any character except a hexidecimal digit can be escaped to
// remove its special meaning by putting a backslash in front"
// -- CSS1 spec section 7.1
(void) Read(aErrorCode);
return ch;
}
}
/**
* Gather up the characters in an identifier. The identfier was
* started by "aChar" which will be appended to aIdent. The result
* will be aIdent with all of the identifier characters appended
* until the first non-identifier character is seen. The termination
* character is unread for the future re-reading.
*/
PRBool nsCSSScanner::GatherIdent(PRInt32* aErrorCode, PRInt32 aChar,
nsString& aIdent)
{
if (aChar == CSS_ESCAPE) {
aChar = ParseEscape(aErrorCode);
}
for (;;) {
aChar = Read(aErrorCode);
if (aChar < 0) break;
if (aChar == CSS_ESCAPE) {
aChar = ParseEscape(aErrorCode);
aIdent.Append(PRUnichar(aChar));
} else if ((aChar <= 255) && ((gLexTable[aChar] & IS_IDENT) != 0)) {
aIdent.Append(PRUnichar(aChar));
} else {
Unread();
break;
}
}
return PR_TRUE;
}
PRBool nsCSSScanner::ParseID(PRInt32* aErrorCode,
PRInt32 aChar,
nsCSSToken* aToken)
{
aToken->mIdent.SetLength(0);
aToken->mType = eCSSToken_ID;
return GatherIdent(aErrorCode, aChar, aToken->mIdent);
}
PRBool nsCSSScanner::ParseIdent(PRInt32* aErrorCode,
PRInt32 aChar,
nsCSSToken* aToken)
{
nsString& ident = aToken->mIdent;
ident.SetLength(0);
ident.Append(PRUnichar(aChar));
if (!GatherIdent(aErrorCode, aChar, ident)) {
return PR_FALSE;
}
// Process a url lexical token. A CSS1 url token can contain
// characters beyond identifier characters (e.g. '/', ':', etc.)
// Because of this the normal rules for tokenizing the input don't
// apply very well. To simplify the parser and relax some of the
// requirements on the scanner we parse url's here. If we find a
// malformed URL then we emit a token of type "InvalidURL" so that
// the CSS1 parser can ignore the invalid input. We attempt to eat
// the right amount of input data when an invalid URL is presented.
nsCSSTokenType tokenType = eCSSToken_Ident;
if (ident.EqualsIgnoreCase("url")) {
tokenType = eCSSToken_InvalidURL;
if (LookAhead(aErrorCode, '(')) {
// Skip leading whitespace
(void) EatWhiteSpace(aErrorCode);
ident.SetLength(0);
PRInt32 c = Read(aErrorCode);
if (c == ')') {
// empty url spec: this is invalid
} else if ((c == '"') || (c == '\'')) {
// start of a quoted url
if (!GatherString(aErrorCode, c, ident)) {
return PR_FALSE;
}
// Whitespace is allowed at the end of the URL before the right paren
(void) EatWhiteSpace(aErrorCode);
if (LookAhead(aErrorCode, ')')) {
tokenType = eCSSToken_URL;
}
} else {
// start of a non-quoted url
Unread();
PRBool ok = PR_TRUE;
for (;;) {
c = Read(aErrorCode);
if (c < 0) break;
if (c == CSS_ESCAPE) {
c = ParseEscape(aErrorCode);
ident.Append(PRUnichar(c));
} else if ((c == '"') || (c == '\'') || (c == '(')) {
// This is an invalid URL spec
ok = PR_FALSE;
} else if ((256 >= c) && ((gLexTable[c] & IS_WHITESPACE) != 0)) {
// Whitespace is allowed at the end of the URL
(void) EatWhiteSpace(aErrorCode);
if (LookAhead(aErrorCode, ')')) {
// done!
break;
}
// Whitespace is followed by something other than a
// ")". This is an invalid url spec.
ok = PR_FALSE;
} else if (c == ')') {
// All done
break;
} else {
// A regular url character.
ident.Append(PRUnichar(c));
}
}
// If the result of the above scanning is ok then change the token
// type to a useful one.
if (ok) {
tokenType = eCSSToken_URL;
}
}
}
}
aToken->mType = tokenType;
return PR_TRUE;
}
PRBool nsCSSScanner::ParseAtKeyword(PRInt32* aErrorCode, PRInt32 aChar,
nsCSSToken* aToken)
{
aToken->mIdent.SetLength(0);
aToken->mType = eCSSToken_AtKeyword;
return GatherIdent(aErrorCode, aChar, aToken->mIdent);
}
PRBool nsCSSScanner::ParseNumber(PRInt32* 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
PRUint8* lexTable = gLexTable;
for (;;) {
c = Read(aErrorCode);
if (c < 0) break;
if (!gotDot && (c == '.')) {
gotDot = PR_TRUE;
} else if ((c > 255) || ((lexTable[c] & IS_DIGIT) == 0)) {
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 ((c <= 255) && ((lexTable[c] & START_IDENT) != 0)) {
ident.SetLength(0);
ident.Append(PRUnichar(c));
if (!GatherIdent(aErrorCode, c, ident)) {
return PR_FALSE;
}
type = eCSSToken_Dimension;
} else if ('%' == c) {
type = eCSSToken_Percentage;
value = value / 100.0f;
} else {
// Put back character that stopped numeric scan
Unread();
if (!gotDot) {
aToken->mInteger = ident.ToInteger(&ec);
aToken->mIntegerValid = PR_TRUE;
}
ident.SetLength(0);
}
}
aToken->mNumber = value;
aToken->mType = type;
return PR_TRUE;
}
PRBool nsCSSScanner::ParseCComment(PRInt32* aErrorCode, nsCSSToken* aToken)
{
nsString& ident = aToken->mIdent;
for (;;) {
PRInt32 ch = Read(aErrorCode);
if (ch < 0) break;
if (ch == '*') {
if (LookAhead(aErrorCode, '/')) {
ident.Append(PRUnichar(ch));
ident.Append('/');
break;
}
}
#ifdef COLLECT_WHITESPACE
ident.Append(PRUnichar(ch));
#endif
}
aToken->mType = eCSSToken_WhiteSpace;
return PR_TRUE;
}
PRBool nsCSSScanner::ParseEOLComment(PRInt32* aErrorCode, nsCSSToken* aToken)
{
nsString& ident = aToken->mIdent;
ident.SetLength(0);
for (;;) {
if (EatNewline(aErrorCode)) {
break;
}
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
break;
}
#ifdef COLLECT_WHITESPACE
ident.Append(PRUnichar(ch));
#endif
}
aToken->mType = eCSSToken_WhiteSpace;
return PR_TRUE;
}
PRBool nsCSSScanner::GatherString(PRInt32* aErrorCode, PRInt32 aStop,
nsString& aBuffer)
{
for (;;) {
PRInt32 ch = Read(aErrorCode);
if (ch < 0) {
return PR_FALSE;
}
if (ch == aStop) {
break;
}
if (ch == CSS_ESCAPE) {
ch = ParseEscape(aErrorCode);
if (ch < 0) {
return PR_FALSE;
}
}
aBuffer.Append(PRUnichar(ch));
}
return PR_TRUE;
}
PRBool nsCSSScanner::ParseString(PRInt32* aErrorCode, PRInt32 aStop,
nsCSSToken* aToken)
{
aToken->mIdent.SetLength(0);
aToken->mType = eCSSToken_String;
return GatherString(aErrorCode, aStop, aToken->mIdent);
}