/* -*- 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 "nsICSSParser.h"
#include "nsCSSProps.h"
#include "nsCSSKeywords.h"
#include "nsCSSScanner.h"
#include "nsICSSStyleRule.h"
#include "nsIUnicharInputStream.h"
#include "nsIStyleSet.h"
#include "nsICSSStyleSheet.h"
#include "nsICSSDeclaration.h"
#include "nsStyleConsts.h"
#include "nsIURL.h"
#include "nsString.h"
#include "nsIAtom.h"
#include "nsVoidArray.h"
#include "nsColor.h"
#include "nsStyleConsts.h"
#include "nsCSSAtoms.h"
#include "nsINameSpaceManager.h"
#include "nsINameSpace.h"

// XXX TODO:
// - rework aErrorCode stuff: switch over to nsresult
// - bug: "color: red {fish};" emits a property for color instead of zapping it

static NS_DEFINE_IID(kICSSParserIID, NS_ICSS_PARSER_IID);
static NS_DEFINE_IID(kICSSStyleSheetIID, NS_ICSS_STYLE_SHEET_IID);
static NS_DEFINE_IID(kIStyleSheetIID, NS_ISTYLE_SHEET_IID);

static NS_DEFINE_IID(kCSSColorSID, NS_CSS_COLOR_SID);
static NS_DEFINE_IID(kCSSContentSID, NS_CSS_CONTENT_SID);
static NS_DEFINE_IID(kCSSTextSID, NS_CSS_TEXT_SID);

#define KEYWORD_BUFFER_SIZE 50    // big enough for any keyword

// e.g. "P B, H1 B { ... }" has a selector list with two elements,
// each of which has two selectors.
struct SelectorList {
  SelectorList(void);
  ~SelectorList(void);

  void AddSelector(const nsCSSSelector& aSelector);

#ifdef NS_DEBUG
  void Dump(void);
#endif

  nsCSSSelector* mSelectors;
  SelectorList* mNext;
};

SelectorList::SelectorList(void)
  : mSelectors(nsnull),
    mNext(nsnull)
{
}

SelectorList::~SelectorList()
{
  nsCSSSelector*  sel = mSelectors;
  while (nsnull != sel) {
    nsCSSSelector* dead = sel;
    sel = sel->mNext;
    delete dead;
  }
  if (nsnull != mNext) {
    delete mNext;
  }
}

void SelectorList::AddSelector(const nsCSSSelector& aSelector)
{ // prepend to list
  nsCSSSelector* newSel = new nsCSSSelector(aSelector);
  newSel->mNext = mSelectors;
  mSelectors = newSel;
}


#ifdef NS_DEBUG
void SelectorList::Dump()
{
}
#endif

//----------------------------------------------------------------------

// Your basic top-down recursive descent style parser
class CSSParserImpl : public nsICSSParser {
public:
  CSSParserImpl();
  CSSParserImpl(nsICSSStyleSheet* aSheet);
  ~CSSParserImpl();

  NS_DECL_ISUPPORTS

  NS_IMETHOD GetInfoMask(PRUint32& aResult);

  NS_IMETHOD SetStyleSheet(nsICSSStyleSheet* aSheet);

  NS_IMETHOD SetCaseSensative(PRBool aCaseSensative);

  NS_IMETHOD Parse(nsIUnicharInputStream* aInput,
                   nsIURL*                aInputURL,
                   nsICSSStyleSheet*&     aResult);

  NS_IMETHOD ParseDeclarations(const nsString& aDeclaration,
                               nsIURL*         aBaseURL,
                               nsIStyleRule*&  aResult);
  
  NS_IMETHOD ParseAndAppendDeclaration(const nsString&    aBuffer,
                                       nsIURL*            aBaseURL,
                                       nsICSSDeclaration* aDeclaration,
                                       PRInt32*           aHint);

protected:
  PRBool GetToken(PRInt32& aErrorCode, PRBool aSkipWS);
  PRBool GetURLToken(PRInt32& aErrorCode, PRBool aSkipWS);
  void UngetToken();

  PRBool ExpectSymbol(PRInt32& aErrorCode, char aSymbol, PRBool aSkipWS);
  PRBool ExpectEndProperty(PRInt32& aErrorCode, PRBool aSkipWS);
  nsString* NextIdent(PRInt32& aErrorCode);
  void SkipUntil(PRInt32& aErrorCode, PRUnichar aStopSymbol);
  void SkipRuleSet(PRInt32& aErrorCode);
  PRBool SkipDeclaration(PRInt32& aErrorCode, PRBool aCheckForBraces);

  PRBool ParseRuleSet(PRInt32& aErrorCode);
  PRBool ParseAtRule(PRInt32& aErrorCode);
  PRBool ParseImportRule(PRInt32& aErrorCode);
  PRBool GatherMedia(PRInt32& aErrorCode, nsString& aMedia);
  PRBool ProcessImport(PRInt32& aErrorCode, const nsString& aURLSpec, const nsString& aMedia);

  PRBool ParseSelectorList(PRInt32& aErrorCode, SelectorList*& aListHead);
  PRBool ParseSelectorGroup(PRInt32& aErrorCode, SelectorList*& aListHead);
  PRBool ParseSelector(PRInt32& aErrorCode, nsCSSSelector& aSelectorResult);
  nsICSSDeclaration* ParseDeclarationBlock(PRInt32& aErrorCode,
                                           PRBool aCheckForBraces);
  PRBool ParseDeclaration(PRInt32& aErrorCode,
                          nsICSSDeclaration* aDeclaration,
                          PRBool aCheckForBraces,
                          PRInt32& aChangeHint);
  PRBool ParseProperty(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration, 
                       PRInt32 aPropID, PRInt32& aChangeHint);
  PRBool ParseProperty(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration, 
                       PRInt32 aPropID);

  // Property specific parsing routines
  PRBool ParseAzimuth(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBackground(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBackgroundFilter(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseForegroundFilter(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBackgroundPosition(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBorder(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBorderColor(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBorderSpacing(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBorderSide(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                         const PRInt32 aPropIDs[], PRInt32 aWhichSide);
  PRBool ParseBorderStyle(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseBorderWidth(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseClip(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseContent(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseCounterData(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                          PRInt32 aPropID);
  PRBool ParseCue(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseCursor(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseFont(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseFamily(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration, PRInt32 aPropID);
  PRBool ParseListStyle(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseMargin(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseMarks(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseOutline(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParsePadding(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParsePause(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParsePlayDuring(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseQuotes(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseSize(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseTextDecoration(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);
  PRBool ParseTextShadow(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration);

  // Reused utility parsing routines
  PRBool ParseBoxProperties(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                            const PRInt32 aPropIDs[]);
  PRInt32 ParseChoice(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                      const PRInt32 aPropIDs[], PRInt32 aNumIDs);
  PRBool ParseColor(PRInt32& aErrorCode, nsCSSValue& aValue);
  PRBool ParseColorComponent(PRInt32& aErrorCode, PRUint8& aComponent,
                             char aStop);
  PRBool ParseEnum(PRInt32& aErrorCode, nsCSSValue& aValue, const PRInt32 aKeywordTable[]);
  PRInt32 SearchKeywordTable(PRInt32 aID, const PRInt32 aTable[]);
  PRBool ParseVariant(PRInt32& aErrorCode, nsCSSValue& aValue,
                      PRInt32 aVariantMask,
                      const PRInt32 aKeywordTable[],
                      PRInt32& aParsedVariant);
  PRBool ParseVariant(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                      PRInt32 aPropID, PRInt32 aVariantMask,
                      const PRInt32 aKeywordTable[],
                      PRInt32& aParsedVariant);
  PRBool ParseCounter(PRInt32& aErrorCode, nsCSSValue& aValue);
  PRBool ParseAttr(PRInt32& aErrorCode, nsCSSValue& aValue);
  PRBool ParseURL(PRInt32& aErrorCode, nsCSSValue& aValue);
  PRBool TranslateDimension(nsCSSValue& aValue, PRInt32 aVariantMask,
                            float aNumber, const nsString& aUnit,
                            PRInt32& aParsedVariant);

  // Current token. The value is valid after calling GetToken
  nsCSSToken mToken;

  // After an UngetToken is done this flag is true. The next call to
  // GetToken clears the flag.
  PRBool mHavePushBack;

  nsCSSScanner* mScanner;
  nsIURL* mURL;
  nsICSSStyleSheet* mSheet;

  PRBool mInHead;

  PRBool  mNavQuirkMode;
  PRBool  mCaseSensative;
};

NS_HTML nsresult
NS_NewCSSParser(nsICSSParser** aInstancePtrResult)
{
  CSSParserImpl *it = new CSSParserImpl();

  if (it == nsnull) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return it->QueryInterface(kICSSParserIID, (void **) aInstancePtrResult);
}

CSSParserImpl::CSSParserImpl()
{
  NS_INIT_REFCNT();
  nsCSSAtoms::AddrefAtoms();
  mScanner = nsnull;
  mSheet = nsnull;
  mHavePushBack = PR_FALSE;
  mNavQuirkMode = PR_TRUE;
  mCaseSensative = PR_FALSE;
}

CSSParserImpl::CSSParserImpl(nsICSSStyleSheet* aSheet)
{
  NS_INIT_REFCNT();
  nsCSSAtoms::AddrefAtoms();
  mScanner = nsnull;
  mSheet = aSheet; NS_ADDREF(aSheet);
  mHavePushBack = PR_FALSE;
  mNavQuirkMode = PR_TRUE;
  mCaseSensative = PR_FALSE;
}

NS_IMPL_ISUPPORTS(CSSParserImpl,kICSSParserIID)

CSSParserImpl::~CSSParserImpl()
{
  NS_IF_RELEASE(mSheet);
  nsCSSAtoms::ReleaseAtoms();
}

NS_IMETHODIMP
CSSParserImpl::GetInfoMask(PRUint32& aResult)
{
  aResult = NS_CSS_GETINFO_CSS1 | NS_CSS_GETINFO_CSSP;
  return NS_OK;
}

NS_IMETHODIMP
CSSParserImpl::SetStyleSheet(nsICSSStyleSheet* aSheet)
{
  NS_PRECONDITION(nsnull != aSheet, "null ptr");
  if (nsnull == aSheet) {
    return NS_ERROR_NULL_POINTER;
  }

  if (aSheet != mSheet) {
    // Switch to using the new sheet
    NS_IF_RELEASE(mSheet);
    mSheet = aSheet;
    NS_ADDREF(mSheet);
  }

  return NS_OK;
}

NS_IMETHODIMP
CSSParserImpl::SetCaseSensative(PRBool aCaseSensative)
{
  mCaseSensative = aCaseSensative;
  return NS_OK;
}


NS_IMETHODIMP
CSSParserImpl::Parse(nsIUnicharInputStream* aInput,
                     nsIURL*                aInputURL,
                     nsICSSStyleSheet*&     aResult)
{
  if (nsnull == mSheet) {
    NS_NewCSSStyleSheet(&mSheet, aInputURL);
  }

  PRInt32 errorCode = NS_OK;
  mScanner = new nsCSSScanner();
  mScanner->Init(aInput);
  mURL = aInputURL;
  NS_IF_ADDREF(aInputURL);
  mInHead = PR_TRUE;
  nsCSSToken* tk = &mToken;
  for (;;) {
    // Get next non-whitespace token
    if (!GetToken(errorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_AtKeyword == tk->mType) {
      ParseAtRule(errorCode);
      continue;
    } else if (eCSSToken_Symbol == tk->mType) {
      // Discard dangling semicolons. This is not part of the CSS1
      // forward compatible parsing spec, but it makes alot of sense.
      if (';' == tk->mSymbol) {
        continue;
      }
    }
    mInHead = PR_FALSE;
    UngetToken();
    ParseRuleSet(errorCode);
  }
  delete mScanner;
  mScanner = nsnull;
  NS_IF_RELEASE(mURL);

  aResult = mSheet;
  NS_ADDREF(aResult);

  return NS_OK;
}

NS_IMETHODIMP
CSSParserImpl::ParseDeclarations(const nsString& aDeclaration,
                                 nsIURL*         aBaseURL,
                                 nsIStyleRule*&  aResult)
{
  nsString* str = new nsString(aDeclaration);
  if (nsnull == str) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  nsIUnicharInputStream* input = nsnull;
  nsresult rv = NS_NewStringUnicharInputStream(&input, str);
  if (NS_OK != rv) {
    return rv;
  }

  mScanner = new nsCSSScanner();
  mScanner->Init(input);
  NS_RELEASE(input);

  mURL = aBaseURL;
  NS_IF_ADDREF(mURL);

  mInHead = PR_FALSE;
  PRInt32 errorCode = NS_OK;

  nsICSSDeclaration* declaration = ParseDeclarationBlock(errorCode, PR_FALSE);
  if (nsnull != declaration) {
    // Create a style rule for the delcaration
    nsICSSStyleRule* rule = nsnull;
    NS_NewCSSStyleRule(&rule, nsCSSSelector());
    rule->SetDeclaration(declaration);
    aResult = rule;
    NS_RELEASE(declaration);
  }
  else {
    aResult = nsnull;
  }

  delete mScanner;
  mScanner = nsnull;
  NS_IF_RELEASE(mURL);

  return NS_OK;
}

NS_IMETHODIMP
CSSParserImpl::ParseAndAppendDeclaration(const nsString&    aBuffer,
                                         nsIURL*            aBaseURL,
                                         nsICSSDeclaration* aDeclaration,
                                         PRInt32*           aHint)
{
  nsString* str = new nsString(aBuffer);
  if (nsnull == str) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  nsIUnicharInputStream* input = nsnull;
  nsresult rv = NS_NewStringUnicharInputStream(&input, str);
  if (NS_OK != rv) {
    return rv;
  }

  mScanner = new nsCSSScanner();
  mScanner->Init(input);
  NS_RELEASE(input);

  mURL = aBaseURL;
  NS_IF_ADDREF(mURL);

  mInHead = PR_FALSE;
  PRInt32 errorCode = NS_OK;

  PRInt32 hint = NS_STYLE_HINT_UNKNOWN;

  ParseDeclaration(errorCode, aDeclaration, PR_FALSE, hint);  
  if (nsnull != aHint) {
    *aHint = hint;
  }

  delete mScanner;
  mScanner = nsnull;
  NS_IF_RELEASE(mURL);

  return NS_OK;
}

//----------------------------------------------------------------------

PRBool CSSParserImpl::GetToken(PRInt32& aErrorCode, PRBool aSkipWS)
{
  for (;;) {
    if (!mHavePushBack) {
      if (!mScanner->Next(aErrorCode, mToken)) {
        break;
      }
    }
    mHavePushBack = PR_FALSE;
    if (aSkipWS && (eCSSToken_WhiteSpace == mToken.mType)) {
      continue;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::GetURLToken(PRInt32& aErrorCode, PRBool aSkipWS)
{
  for (;;) {
    if (! mHavePushBack) {
      if (! mScanner->NextURL(aErrorCode, mToken)) {
        break;
      }
    }
    mHavePushBack = PR_FALSE;
    if (aSkipWS && (eCSSToken_WhiteSpace == mToken.mType)) {
      continue;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

void CSSParserImpl::UngetToken()
{
  NS_PRECONDITION(mHavePushBack == PR_FALSE, "double pushback");
  mHavePushBack = PR_TRUE;
}

PRBool CSSParserImpl::ExpectSymbol(PRInt32& aErrorCode,
                                   char aSymbol,
                                   PRBool aSkipWS)
{
  if (!GetToken(aErrorCode, aSkipWS)) {
    return PR_FALSE;
  }
  nsCSSToken* tk = &mToken;
  if ((eCSSToken_Symbol == tk->mType) && (aSymbol == tk->mSymbol)) {
    return PR_TRUE;
  }
  UngetToken();
  return PR_FALSE;
}

PRBool CSSParserImpl::ExpectEndProperty(PRInt32& aErrorCode, PRBool aSkipWS)
{
  if (!GetToken(aErrorCode, aSkipWS)) {
    return PR_FALSE;
  }
  if ((eCSSToken_Symbol == mToken.mType) &&
      ((';' == mToken.mSymbol) || ('!' == mToken.mSymbol))) {
    UngetToken();
    return PR_TRUE;
  }
  UngetToken();
  return PR_FALSE;
}


nsString* CSSParserImpl::NextIdent(PRInt32& aErrorCode)
{
  if (!GetToken(aErrorCode, PR_TRUE)) {
    return nsnull;
  }
  if (eCSSToken_Ident != mToken.mType) {
    UngetToken();
    return nsnull;
  }
  return &mToken.mIdent;
}

// XXX @media type {, types } '{' rules '}'
// XXX @font-face
// XXX @page { :left | :right } '{' declarations '}'
PRBool CSSParserImpl::ParseAtRule(PRInt32& aErrorCode)
{
  nsCSSToken* tk = &mToken;
  if (mInHead && tk->mIdent.EqualsIgnoreCase("import")) {
    ParseImportRule(aErrorCode);
    return PR_TRUE;
  }

  // Skip over unsupported at rule
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      return PR_FALSE;
    }
    if (eCSSToken_Symbol == tk->mType) {
      PRUnichar symbol = tk->mSymbol;
      if (symbol == ';') {
        break;
      }
      if (symbol == '{') {
        SkipUntil(aErrorCode, '}');
        break;
      } else if (symbol == '(') {
        SkipUntil(aErrorCode, ')');
      } else if (symbol == '[') {
        SkipUntil(aErrorCode, ']');
      }
    }
  }
  mInHead = PR_FALSE;
  return PR_TRUE;
}

PRBool CSSParserImpl::GatherMedia(PRInt32& aErrorCode, nsString& aMedia)
{
  PRBool first = PR_TRUE;
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_Symbol == mToken.mType) {
      PRUnichar symbol = mToken.mSymbol;
      if (';' == symbol) {
        UngetToken();
        aMedia.ToUpperCase();
        return PR_TRUE;
      } else if (',' != symbol) {
        UngetToken();
        return PR_FALSE;
      }
    }
    else if (eCSSToken_Ident == mToken.mType) {
      if (! first) {
        aMedia.Append(',');
      }
      aMedia.Append(mToken.mIdent);
      first = PR_FALSE;
    }
    else {
      break;
    }
  }
  aMedia.Truncate();
  return PR_FALSE;
}

// Parse a CSS1 import rule: "@import STRING | URL [medium [, mdeium]]"
PRBool CSSParserImpl::ParseImportRule(PRInt32& aErrorCode)
{
  if (!GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }
  nsAutoString url;
  nsAutoString media;

  if (eCSSToken_String == mToken.mType) {
    url = mToken.mIdent;
    if (GatherMedia(aErrorCode, media)) {
      if (ExpectSymbol(aErrorCode, ';', PR_TRUE)) {
        ProcessImport(aErrorCode, url, media);
        return PR_TRUE;
      }
    }
    if ((eCSSToken_Symbol != mToken.mType) || (';' != mToken.mSymbol)) {
      SkipUntil(aErrorCode, ';');
    }
  }
  else if (eCSSToken_Function == mToken.mType) {
    if (ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
      if (GetURLToken(aErrorCode, PR_TRUE)) {
        if ((eCSSToken_String == mToken.mType) || (eCSSToken_URL == mToken.mType)) {
          url = mToken.mIdent;
          if (ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
            if (GatherMedia(aErrorCode, media)) {
              if (ExpectSymbol(aErrorCode, ';', PR_TRUE)) {
                ProcessImport(aErrorCode, url, media);
                return PR_TRUE;
              }
            }
          }
        }
      }
    }
    if ((eCSSToken_Symbol != mToken.mType) || (';' != mToken.mSymbol)) {
      SkipUntil(aErrorCode, ';');
    }
  }

  mInHead = PR_FALSE;
  return PR_TRUE;
}


typedef PRBool (*nsStringEnumFunc)(const nsString& aSubString, void *aData);

const PRUnichar kNullCh       = PRUnichar('\0');
const PRUnichar kSingleQuote  = PRUnichar('\'');
const PRUnichar kDoubleQuote  = PRUnichar('\"');
const PRUnichar kComma        = PRUnichar(',');

static PRBool EnumerateString(const nsString& aStringList, nsStringEnumFunc aFunc, void* aData)
{
  PRBool    running = PR_TRUE;

  nsAutoString  stringList(aStringList); // copy to work buffer
  nsAutoString  subStr;

  stringList.Append(kNullCh);  // put an extra null at the end

  PRUnichar* start = (PRUnichar*)stringList;
  PRUnichar* end   = start;

  while (running && (kNullCh != *start)) {
    PRBool  quoted = PR_FALSE;

    while ((kNullCh != *start) && nsString::IsSpace(*start)) {  // skip leading space
      start++;
    }

    if ((kSingleQuote == *start) || (kDoubleQuote == *start)) { // quoted string
      PRUnichar quote = *start++;
      quoted = PR_TRUE;
      end = start;
      while (kNullCh != *end) {
        if (quote == *end) {  // found closing quote
          *end++ = kNullCh;     // end string here
          while ((kNullCh != *end) && (kComma != *end)) { // keep going until comma
            end++;
          }
          break;
        }
        end++;
      }
    }
    else {  // non-quoted string or ended
      end = start;

      while ((kNullCh != *end) && (kComma != *end)) { // look for comma
        end++;
      }
      *end = kNullCh; // end string here
    }

    subStr = start;

    if (PR_FALSE == quoted) {
      subStr.CompressWhitespace(PR_FALSE, PR_TRUE);
    }

    if (0 < subStr.Length()) {
      running = (*aFunc)(subStr, aData);
    }

    start = ++end;
  }

  return running;
}

static PRBool MediumEnumFunc(const nsString& aSubString, void *aData)
{
  nsIAtom*  medium = NS_NewAtom(aSubString);
  ((nsICSSStyleSheet*)aData)->AppendMedium(medium);
  return PR_TRUE;
}

PRBool CSSParserImpl::ProcessImport(PRInt32& aErrorCode, const nsString& aURLSpec, const nsString& aMedia)
{
  PRBool result = PR_FALSE;

  // XXX probably need a way to encode unicode junk for the part of
  // the url that follows a "?"
  char* cp = aURLSpec.ToNewCString();
  nsIURL* url;
  aErrorCode = NS_NewURL(&url, mURL, cp);
  delete [] cp;
  if (NS_FAILED(aErrorCode)) {
    // import url is bad
    // XXX log this somewhere for easier web page debugging
    return PR_FALSE;
  }

  if (PR_FALSE == mSheet->ContainsStyleSheet(url)) { // don't allow circular references

    PRInt32 ec;
    nsIInputStream* in = url->Open(&ec);
    if (nsnull == in) {
      // failure to make connection
      // XXX log this somewhere for easier web page debugging
    }
    else {

      nsIUnicharInputStream* uin;
      aErrorCode = NS_NewConverterStream(&uin, nsnull, in);
      if (NS_FAILED(aErrorCode)) {
        // XXX no iso-latin-1 converter? out of memory?
        NS_RELEASE(in);
      }
      else {

        NS_RELEASE(in);

        // Create a new parse to parse the import. 

        nsICSSParser* parser;
        aErrorCode = NS_NewCSSParser(&parser);
        if (NS_SUCCEEDED(aErrorCode)) {
          nsICSSStyleSheet* childSheet = nsnull;
          aErrorCode = parser->Parse(uin, url, childSheet);
          NS_RELEASE(parser);
          if (NS_SUCCEEDED(aErrorCode) && (nsnull != childSheet)) {
            if (0 < aMedia.Length()) {
              EnumerateString(aMedia, MediumEnumFunc, childSheet);
            }
            mSheet->AppendStyleSheet(childSheet);
            result = PR_TRUE;
          }
          NS_IF_RELEASE(childSheet);
        }
        NS_RELEASE(uin);
      }
    }
  }
  NS_RELEASE(url);
  
  return result;
}

void CSSParserImpl::SkipUntil(PRInt32& aErrorCode, PRUnichar aStopSymbol)
{
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_Symbol == tk->mType) {
      PRUnichar symbol = tk->mSymbol;
      if (symbol == aStopSymbol) {
        break;
      } else if ('{' == symbol) {
        SkipUntil(aErrorCode, '}');
      } else if ('[' == symbol) {
        SkipUntil(aErrorCode, ']');
      } else if ('(' == symbol) {
        SkipUntil(aErrorCode, ')');
      }
    }
  }
}

PRBool
CSSParserImpl::SkipDeclaration(PRInt32& aErrorCode, PRBool aCheckForBraces)
{
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      return PR_FALSE;
    }
    if (eCSSToken_Symbol == tk->mType) {
      PRUnichar symbol = tk->mSymbol;
      if (';' == symbol) {
        break;
      }
      if (aCheckForBraces) {
        if ('}' == symbol) {
          UngetToken();
          break;
        }
      }
      if ('{' == symbol) {
        SkipUntil(aErrorCode, '}');
      } else if ('(' == symbol) {
        SkipUntil(aErrorCode, ')');
      } else if ('[' == symbol) {
        SkipUntil(aErrorCode, ']');
      }
    }
  }
  return PR_TRUE;
}

void CSSParserImpl::SkipRuleSet(PRInt32& aErrorCode)
{
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_Symbol == tk->mType) {
      PRUnichar symbol = tk->mSymbol;
      if ('{' == symbol) {
        SkipUntil(aErrorCode, '}');
        break;
      }
      if ('(' == symbol) {
        SkipUntil(aErrorCode, ')');
      } else if ('[' == symbol) {
        SkipUntil(aErrorCode, ']');
      }
    }
  }
}

PRBool CSSParserImpl::ParseRuleSet(PRInt32& aErrorCode)
{
  nsCSSToken* tk = &mToken;

  // First get the list of selectors for the rule
  SelectorList* slist = nsnull;
  if (! ParseSelectorList(aErrorCode, slist)) {
    SkipRuleSet(aErrorCode);
    return PR_FALSE;
  }
  NS_ASSERTION(nsnull != slist, "null selector list");

  // Next parse the declaration block
  nsICSSDeclaration* declaration = ParseDeclarationBlock(aErrorCode, PR_TRUE);
  if (nsnull == declaration) {
    // XXX skip something here
    delete slist;
    return PR_FALSE;
  }

#if 0
  slist->Dump();
  fputs("{\n", stdout);
  declaration->List();
  fputs("}\n", stdout);
#endif

  // Translate the selector list and declaration block into style data

  SelectorList* list = slist;

  while (nsnull != list) {
    PRInt32 weight = list->mSelectors->CalcWeight();
    nsICSSStyleRule* rule = nsnull;
    NS_NewCSSStyleRule(&rule, *(list->mSelectors));
    if (nsnull != rule) {
      if (nsnull != list->mSelectors->mNext) { // hand off other selectors to new rule
        nsCSSSelector* ruleFirst = rule->FirstSelector();
        ruleFirst->mNext = list->mSelectors->mNext;
        list->mSelectors->mNext = nsnull;
      }
      rule->SetDeclaration(declaration);
      rule->SetWeight(weight);
//      rule->List();
      mSheet->AppendStyleRule(rule);
      NS_RELEASE(rule);
    }

    list = list->mNext;
  }

  // Release temporary storage
  delete slist;
  NS_RELEASE(declaration);
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseSelectorList(PRInt32& aErrorCode,
                                        SelectorList*& aListHead)
{
  SelectorList* list = nsnull;
  if (! ParseSelectorGroup(aErrorCode, list)) {
    // must have at least one selector group
    aListHead = nsnull;
    return PR_FALSE;
  }
  NS_ASSERTION(nsnull != list, "no selector list");
  aListHead = list;

  // After that there must either be a "," or a "{"
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (! GetToken(aErrorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_Symbol != tk->mType) {
      UngetToken();
      break;
    }
    if (',' == tk->mSymbol) {
      SelectorList* newList = nsnull;
      // Another selector group must follow
      if (! ParseSelectorGroup(aErrorCode, newList)) {
        break;
      }
      // add new list to the end of the selector list
      list->mNext = newList;
      list = newList;
      continue;
    } else if ('{' == tk->mSymbol) {
      UngetToken();
      return PR_TRUE;
    } else {
      UngetToken();
      break;
    }
  }

  delete aListHead;
  aListHead = nsnull;
  return PR_FALSE;
}

static PRBool IsPseudoClass(const nsIAtom* aAtom)
{
  return PRBool((nsCSSAtoms::activePseudo == aAtom) || 
                (nsCSSAtoms::firstChildPseudo == aAtom) ||
                (nsCSSAtoms::focusPseudo == aAtom) ||
                (nsCSSAtoms::hoverPseudo == aAtom) ||
                (nsCSSAtoms::langPseudo == aAtom) ||
                (nsCSSAtoms::linkPseudo == aAtom) ||
                (nsCSSAtoms::outOfDatePseudo == aAtom) ||
                (nsCSSAtoms::visitedPseudo == aAtom));
}

static PRBool IsSinglePseudoClass(const nsCSSSelector& aSelector)
{
  return PRBool((aSelector.mNameSpace == kNameSpaceID_Unknown) && 
                (aSelector.mTag == nsnull) && 
                (aSelector.mID == nsnull) &&
                (aSelector.mClassList == nsnull) &&
                (aSelector.mAttrList == nsnull) &&
                (aSelector.mPseudoClassList != nsnull) &&
                (aSelector.mPseudoClassList->mNext == nsnull));
}

PRBool CSSParserImpl::ParseSelectorGroup(PRInt32& aErrorCode,
                                         SelectorList*& aList)
{
  SelectorList* list = nsnull;
  for (;;) {
    nsCSSSelector selector;
    if (! ParseSelector(aErrorCode, selector)) {
      break;
    }
    if (nsnull == list) {
      list = new SelectorList();
    }
    list->AddSelector(selector);
    nsCSSSelector* listSel = list->mSelectors;
    // XXX parse combinator here

    // pull out pseudo elements here
    nsAtomList* prevList = nsnull;
    nsAtomList* pseudoClassList = listSel->mPseudoClassList;
    while (nsnull != pseudoClassList) {
      if (! IsPseudoClass(pseudoClassList->mAtom)) {
        if (IsSinglePseudoClass(*listSel)) {  // convert to pseudo element selector
          nsIAtom* pseudoElement = pseudoClassList->mAtom;  // steal ref count
          pseudoClassList->mAtom = nsnull;
          listSel->Reset();
          listSel->mTag = pseudoElement;
        }
        else {  // append new pseudo element selector
          selector.Reset();
          selector.mTag = pseudoClassList->mAtom; // steal ref count
          list->AddSelector(selector);
          pseudoClassList->mAtom = nsnull;
          if (nsnull == prevList) { // delete list entry
            listSel->mPseudoClassList = pseudoClassList->mNext;
          }
          else {
            prevList->mNext = pseudoClassList->mNext;
          }
          pseudoClassList->mNext = nsnull;
          delete pseudoClassList;
        }
        break;  // only one pseudo element per selector
      }
      prevList = pseudoClassList;
      pseudoClassList = pseudoClassList->mNext;
    }
  }
  aList = list;
  return PRBool(nsnull != aList);
}

#define SEL_MASK_NSPACE   0x01
#define SEL_MASK_ELEM     0x02
#define SEL_MASK_ID       0x04
#define SEL_MASK_CLASS    0x08
#define SEL_MASK_ATTRIB   0x10
#define SEL_MASK_PCLASS   0x20
#define SEL_MASK_PELEM    0x40

/**
 * This is the format for selectors:
 * operator? [namespace \:]? element_name? [ ID | class | attrib | pseudo ]*
 */
PRBool CSSParserImpl::ParseSelector(PRInt32& aErrorCode,
                                    nsCSSSelector& aSelector)
{
  PRInt32       dataMask = 0;
  nsAutoString  buffer;

  if (! GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }
  if ((eCSSToken_Symbol == mToken.mType) && ('*' == mToken.mSymbol)) {  // universal element selector
    // don't set any tag in the selector
    dataMask |= SEL_MASK_ELEM;
    if (! GetToken(aErrorCode, PR_FALSE)) {   // premature eof is ok (here!)
      return PR_TRUE;
    }
  }
  else if (eCSSToken_Ident == mToken.mType) {    // element name
    PRInt32 colon = mToken.mIdent.Find(':');
    if (-1 == colon) {  // no namespace
      if (mCaseSensative) {
        aSelector.SetTag(mToken.mIdent);
      }
      else {
        mToken.mIdent.ToUpperCase(buffer);
        aSelector.SetTag(buffer);
      }
    }
    else {  // pull out the namespace
      nsAutoString  nameSpace;
      mToken.mIdent.Left(nameSpace, colon);
      mToken.mIdent.Right(buffer, (mToken.mIdent.Length() - (colon + 1)));
      if (! mCaseSensative) {
        buffer.ToUpperCase();
      }
      // XXX lookup namespace, set it
      // deal with * namespace (== unknown)
      aSelector.SetTag(buffer);
    }
    dataMask |= SEL_MASK_ELEM;
    if (! GetToken(aErrorCode, PR_FALSE)) {   // premature eof is ok (here!)
      return PR_TRUE;
    }
  }
  for (;;) {
    if (eCSSToken_ID == mToken.mType) {   // #id
      if ((0 == (dataMask & SEL_MASK_ID)) &&  // only one ID
          (0 < mToken.mIdent.Length()) && 
          (nsString::IsAlpha(mToken.mIdent.CharAt(0)))) { // verify is legal ID
        dataMask |= SEL_MASK_ID;
        aSelector.SetID(mToken.mIdent);
      }
      else {
        UngetToken();
        return PR_FALSE;
      }
    }
    else if ((eCSSToken_Symbol == mToken.mType) && ('.' == mToken.mSymbol)) {  // .class
      if (! GetToken(aErrorCode, PR_FALSE)) { // get ident
        return PR_FALSE;
      }
      if (eCSSToken_Ident != mToken.mType) {  // malformed selector (XXX what about leading digits?)
        UngetToken();
        return PR_FALSE;
      }
      dataMask |= SEL_MASK_CLASS;
      if (mCaseSensative) {
        aSelector.AddClass(mToken.mIdent);
      }
      else {
        mToken.mIdent.ToUpperCase(buffer);
        aSelector.AddClass(buffer);
      }
    }
    else if ((eCSSToken_Symbol == mToken.mType) && (':' == mToken.mSymbol)) { // :pseudo
      if (! GetToken(aErrorCode, PR_FALSE)) { // premature eof
        return PR_FALSE;
      }
      if (eCSSToken_Ident != mToken.mType) {  // malformed selector
        UngetToken();
        return PR_FALSE;
      }
      buffer.Truncate();
      buffer.Append(':');
      buffer.Append(mToken.mIdent);
      buffer.ToUpperCase();
      nsIAtom* pseudo = NS_NewAtom(buffer);
      if (IsPseudoClass(pseudo)) {
        // XXX parse lang pseudo class
        dataMask |= SEL_MASK_PCLASS;
        aSelector.AddPseudoClass(pseudo);
      }
      else {
        if (0 == (dataMask & SEL_MASK_PELEM)) {
          dataMask |= SEL_MASK_PELEM;
          aSelector.AddPseudoClass(pseudo); // store it here, it gets pulled later
        }
        else {  // multiple pseudo elements, not legal
          UngetToken();
          NS_RELEASE(pseudo);
          return PR_FALSE;
        }
      }
      NS_RELEASE(pseudo);
    }
    else if ((eCSSToken_Symbol == mToken.mType) && ('[' == mToken.mSymbol)) {  // attribute
      if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
        return PR_FALSE;
      }
      if (eCSSToken_Ident != mToken.mType) {  // malformed selector
        UngetToken();
        return PR_FALSE;
      }
      nsAutoString  attr(mToken.mType);
      if (! mCaseSensative) {
        attr.ToUpperCase();
      }
      if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
        return PR_FALSE;
      }
      if (eCSSToken_Symbol == mToken.mType) {
        PRUint8 func;
        if (']' == mToken.mSymbol) {
          dataMask |= SEL_MASK_ATTRIB;
          aSelector.AddAttribute(attr);
          func = NS_ATTR_FUNC_SET;
        }
        else if ('=' == mToken.mSymbol) {
          func = NS_ATTR_FUNC_EQUALS;
        }
        else if ('~' == mToken.mSymbol) {
          if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
            return PR_FALSE;
          }
          if ((eCSSToken_Symbol == mToken.mType) && ('=' == mToken.mSymbol)) {
            func = NS_ATTR_FUNC_INCLUDES;
          }
          else {
            UngetToken();
            return PR_FALSE;
          }
        }
        else if ('|' == mToken.mSymbol) {
          if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
            return PR_FALSE;
          }
          if ((eCSSToken_Symbol == mToken.mType) && ('=' == mToken.mSymbol)) {
            func = NS_ATTR_FUNC_DASHMATCH;
          }
          else {
            UngetToken();
            return PR_FALSE;
          }
        }
        else {
          UngetToken(); // bad function
          return PR_FALSE;
        }
        if (NS_ATTR_FUNC_SET != func) { // get value
          if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
            return PR_FALSE;
          }
          if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) {
            nsAutoString  value(mToken.mIdent);
            if (! GetToken(aErrorCode, PR_FALSE)) { // premature EOF
              return PR_FALSE;
            }
            if ((eCSSToken_Symbol == mToken.mType) && (']' == mToken.mSymbol)) {
              dataMask |= SEL_MASK_ATTRIB;
              if (! mCaseSensative) {
                value.ToUpperCase();
              }
              aSelector.AddAttribute(attr, func, value);
            }
            else {
              UngetToken();
              return PR_FALSE;
            }
          }
          else {
            UngetToken();
            return PR_FALSE;
          }
        }
      }
      else {
        UngetToken(); // bad dog, no biscut!
        return PR_FALSE;
      }
    }
    else {  // not a selector token, we're done
      break;
    }

    if (! GetToken(aErrorCode, PR_FALSE)) { // premature eof is ok (here!)
      return PR_TRUE;
    }
  }
  UngetToken();
  return PRBool(0 != dataMask);
}

nsICSSDeclaration*
CSSParserImpl::ParseDeclarationBlock(PRInt32& aErrorCode,
                                     PRBool aCheckForBraces)
{
  if (aCheckForBraces) {
    if (!ExpectSymbol(aErrorCode, '{', PR_TRUE)) {
      return nsnull;
    }
  }
  nsICSSDeclaration* declaration = nsnull;
  if (NS_OK == NS_NewCSSDeclaration(&declaration)) {
    PRInt32 count = 0;
    for (;;) {
      PRInt32 hint;
      if (ParseDeclaration(aErrorCode, declaration, aCheckForBraces, hint)) {
        count++;  // count declarations
      }
      else {
        if (!SkipDeclaration(aErrorCode, aCheckForBraces)) {
          break;
        }
        if (aCheckForBraces) {
          if (ExpectSymbol(aErrorCode, '}', PR_TRUE)) {
            break;
          }
        }
        // Since the skipped declaration didn't end the block we parse
        // the next declaration.
      }
    }
    if (0 == count) { // didn't get any XXX is this ok with the DOM?
      NS_RELEASE(declaration);
    }
  }
  return declaration;
}

PRBool CSSParserImpl::ParseColor(PRInt32& aErrorCode, nsCSSValue& aValue)
{
  char cbuf[KEYWORD_BUFFER_SIZE];

  if (!GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }

  nsCSSToken* tk = &mToken;
  nscolor rgba;
  switch (tk->mType) {
    case eCSSToken_ID:
      // #xxyyzz
      tk->mIdent.ToCString(cbuf, sizeof(cbuf));
      if (NS_HexToRGB(cbuf, &rgba)) {
        aValue.SetColorValue(rgba);
        return PR_TRUE;
      }
      break;

    case eCSSToken_Ident:
      tk->mIdent.ToCString(cbuf, sizeof(cbuf));
      if (NS_ColorNameToRGB(cbuf, &rgba)) {
        aValue.SetStringValue(tk->mIdent, eCSSUnit_String);
        return PR_TRUE;
      }
      break;
    case eCSSToken_Function:
      // rgb ( component , component , component )
      PRUint8 r, g, b;
      if (ExpectSymbol(aErrorCode, '(', PR_FALSE) &&
          ParseColorComponent(aErrorCode, r, ',') &&
          ParseColorComponent(aErrorCode, g, ',') &&
          ParseColorComponent(aErrorCode, b, ')')) {
        nscolor rgba = NS_RGB(r,g,b);
        aValue.SetColorValue(rgba);
        return PR_TRUE;
      }
      return PR_FALSE;  // already pushed back

    default:
      break;
  }

  // It's not a color
  UngetToken();
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseColorComponent(PRInt32& aErrorCode,
                                          PRUint8& aComponent,
                                          char aStop)
{
  if (!GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }
  float value;
  nsCSSToken* tk = &mToken;
  switch (tk->mType) {
  case eCSSToken_Number:
    value = tk->mNumber;
    break;
  case eCSSToken_Percentage:
    value = tk->mNumber * 255.0f;
    break;
  default:
    UngetToken();
    return PR_FALSE;
  }
  if (ExpectSymbol(aErrorCode, aStop, PR_TRUE)) {
    if (value < 0.0f) value = 0.0f;
    if (value > 255.0f) value = 255.0f;
    aComponent = (PRUint8) value;
    return PR_TRUE;
  }
  return PR_FALSE;
}

//----------------------------------------------------------------------

PRBool
CSSParserImpl::ParseDeclaration(PRInt32& aErrorCode,
                                nsICSSDeclaration* aDeclaration,
                                PRBool aCheckForBraces,
                                PRInt32& aChangeHint)
{
  aChangeHint = NS_STYLE_HINT_UNKNOWN;

  // Get property name
  nsCSSToken* tk = &mToken;
  char propertyName[100];
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      return PR_FALSE;
    }
    if (eCSSToken_Ident == tk->mType) {
      tk->mIdent.ToCString(propertyName, sizeof(propertyName));
      // grab the ident before the ExpectSymbol trashes the token
      if (!ExpectSymbol(aErrorCode, ':', PR_TRUE)) {
        return PR_FALSE;
      }
      break;
    }
    if ((eCSSToken_Symbol == tk->mType) && (';' == tk->mSymbol)) {
      // dangling semicolons are skipped
      continue;
    }

    // Not a declaration...
    UngetToken();
    return PR_FALSE;
  }

  // Map property name to it's ID and then parse the property
  PRInt32 propID = nsCSSProps::LookupName(propertyName);
  if (0 > propID) { // unknown property
    return PR_FALSE;
  }
  if (! ParseProperty(aErrorCode, aDeclaration, propID, aChangeHint)) {
    return PR_FALSE;
  }

  // See if the declaration is followed by a "!important" declaration
  PRBool isImportant = PR_FALSE;
  if (!GetToken(aErrorCode, PR_TRUE)) {
    if (aCheckForBraces) {
      // Premature eof is not ok when proper termination is mandated
      return PR_FALSE;
    }
    return PR_TRUE;
  }
  else {
    if (eCSSToken_Symbol == tk->mType) {
      if ('!' == tk->mSymbol) {
        // Look for important ident
        if (!GetToken(aErrorCode, PR_TRUE)) {
          // Premature eof is not ok
          return PR_FALSE;
        }
        if ((eCSSToken_Ident != tk->mType) ||
            !tk->mIdent.EqualsIgnoreCase("important")) {
          UngetToken();
          return PR_FALSE;
        }
        isImportant = PR_TRUE;
      }
      else {
        // Not a !important declaration
        UngetToken();
      }
    }
    else {
      // Not a !important declaration
      UngetToken();
    }
  }

  if (PR_TRUE == isImportant) {
    aDeclaration->SetValueImportant(propID);
  }

  // Make sure valid property declaration is terminated with either a
  // semicolon or a right-curly-brace (when aCheckForBraces is true).
  // When aCheckForBraces is false, proper termination is either
  // semicolon or EOF.
  if (!GetToken(aErrorCode, PR_TRUE)) {
    if (aCheckForBraces) {
      // Premature eof is not ok
      return PR_FALSE;
    }
    return PR_TRUE;
  } 
  if (eCSSToken_Symbol == tk->mType) {
    if (';' == tk->mSymbol) {
      return PR_TRUE;
    }
    if (!aCheckForBraces) {
      // If we didn't hit eof and we didn't see a semicolon then the
      // declaration is not properly terminated.
      return PR_FALSE;
    }
    if ('}' == tk->mSymbol) {
      UngetToken();
      return PR_TRUE;
    }
  }
  return PR_FALSE;
}

// Flags for ParseVariant method
#define VARIANT_KEYWORD         0x000001  // K
#define VARIANT_LENGTH          0x000002  // L
#define VARIANT_PERCENT         0x000004  // P
#define VARIANT_COLOR           0x000008  // C
#define VARIANT_URL             0x000010  // U
#define VARIANT_NUMBER          0x000020  // N
#define VARIANT_INTEGER         0x000040  // I
#define VARIANT_ANGLE           0x000080  // G
#define VARIANT_FREQUENCY       0x000100  // F
#define VARIANT_TIME            0x000200  // T
#define VARIANT_STRING          0x000400  // S
#define VARIANT_COUNTER         0x000800  // 
#define VARIANT_ATTR            0x001000  //
#define VARIANT_IDENTIFIER      0x002000  // D
#define VARIANT_AUTO            0x010000  // A
#define VARIANT_INHERIT         0x020000  // H
#define VARIANT_NONE            0x040000  // O
#define VARIANT_NORMAL          0x080000  // M

// Common combinations of variants
#define VARIANT_AL   (VARIANT_AUTO | VARIANT_LENGTH)
#define VARIANT_LP   (VARIANT_LENGTH | VARIANT_PERCENT)
#define VARIANT_AH   (VARIANT_AUTO | VARIANT_INHERIT)
#define VARIANT_AHLP (VARIANT_AH | VARIANT_LP)
#define VARIANT_AHI  (VARIANT_AH | VARIANT_INTEGER)
#define VARIANT_AHK  (VARIANT_AH | VARIANT_KEYWORD)
#define VARIANT_AHUK (VARIANT_AH | VARIANT_URL | VARIANT_KEYWORD)
#define VARIANT_AHL  (VARIANT_AH | VARIANT_LENGTH)
#define VARIANT_AHKL (VARIANT_AHK | VARIANT_LENGTH)
#define VARIANT_HK   (VARIANT_INHERIT | VARIANT_KEYWORD)
#define VARIANT_HKF  (VARIANT_HK | VARIANT_FREQUENCY)
#define VARIANT_HKL  (VARIANT_HK | VARIANT_LENGTH)
#define VARIANT_HKLP (VARIANT_HK | VARIANT_LP)
#define VARIANT_HL   (VARIANT_INHERIT | VARIANT_LENGTH)
#define VARIANT_HI   (VARIANT_INHERIT | VARIANT_INTEGER)
#define VARIANT_HLP  (VARIANT_HL | VARIANT_PERCENT)
#define VARIANT_HLPN (VARIANT_HLP | VARIANT_NUMBER)
#define VARIANT_HLPO (VARIANT_HLP | VARIANT_NONE)
#define VARIANT_HTP  (VARIANT_INHERIT | VARIANT_TIME | VARIANT_PERCENT)
#define VARIANT_HMK  (VARIANT_HK | VARIANT_NORMAL)
#define VARIANT_HMKI (VARIANT_HMK | VARIANT_INTEGER)
#define VARIANT_HC   (VARIANT_INHERIT | VARIANT_COLOR)
#define VARIANT_HCK  (VARIANT_HK | VARIANT_COLOR)
#define VARIANT_HUO  (VARIANT_INHERIT | VARIANT_URL | VARIANT_NONE)
#define VARIANT_AHUO (VARIANT_AUTO | VARIANT_HUO)
#define VARIANT_HPN  (VARIANT_INHERIT | VARIANT_PERCENT | VARIANT_NUMBER)
#define VARIANT_HOK  (VARIANT_HK | VARIANT_NONE)
#define VARIANT_HN   (VARIANT_INHERIT | VARIANT_NUMBER)
#define VARIANT_HON  (VARIANT_HN | VARIANT_NONE)
#define VARIANT_HOS  (VARIANT_INHERIT | VARIANT_NONE | VARIANT_STRING)

static const PRInt32 kBorderTopIDs[] = {
  PROP_BORDER_TOP_WIDTH,
  PROP_BORDER_TOP_STYLE,
  PROP_BORDER_TOP_COLOR
};
static const PRInt32 kBorderRightIDs[] = {
  PROP_BORDER_RIGHT_WIDTH,
  PROP_BORDER_RIGHT_STYLE,
  PROP_BORDER_RIGHT_COLOR
};
static const PRInt32 kBorderBottomIDs[] = {
  PROP_BORDER_BOTTOM_WIDTH,
  PROP_BORDER_BOTTOM_STYLE,
  PROP_BORDER_BOTTOM_COLOR
};
static const PRInt32 kBorderLeftIDs[] = {
  PROP_BORDER_LEFT_WIDTH,
  PROP_BORDER_LEFT_STYLE,
  PROP_BORDER_LEFT_COLOR
};

PRInt32 CSSParserImpl::SearchKeywordTable(PRInt32 aKeywordID, const PRInt32 aKeywordTable[])
{
  PRInt32 index = 0;
  while (0 <= aKeywordTable[index]) {
    if (aKeywordID == aKeywordTable[index++]) {
      return index;
    }
    index++;
  }
  return -1;
}

PRBool CSSParserImpl::ParseEnum(PRInt32& aErrorCode, nsCSSValue& aValue,
                                const PRInt32 aKeywordTable[])
{
  nsString* ident = NextIdent(aErrorCode);
  if (nsnull == ident) {
    return PR_FALSE;
  }
  char cbuf[KEYWORD_BUFFER_SIZE];
  ident->ToCString(cbuf, sizeof(cbuf));
  PRInt32 keyword = nsCSSKeywords::LookupName(cbuf);
  if (0 <= keyword) {
    PRInt32 index = SearchKeywordTable(keyword, aKeywordTable);
    if (0 < index) {
      aValue.SetIntValue(aKeywordTable[index], eCSSUnit_Enumerated);
      return PR_TRUE;
    }
  }

  // Put the unknown identifier back and return
  UngetToken();
  return PR_FALSE;
}

PRBool CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
                                         PRInt32 aVariantMask,
                                         float aNumber,
                                         const nsString& aUnit,
                                         PRInt32& aParsedVariant)
{
  nsCSSUnit units;
  PRInt32   type = 0;
  aParsedVariant = 0;
  if (0 != aUnit.Length()) {
    char cbuf[KEYWORD_BUFFER_SIZE];
    aUnit.ToCString(cbuf, sizeof(cbuf));
    PRInt32 id = nsCSSKeywords::LookupName(cbuf);
    switch (id) {
      case KEYWORD_EM:    units = eCSSUnit_EM;          type = VARIANT_LENGTH;  break;
      case KEYWORD_EX:    units = eCSSUnit_XHeight;     type = VARIANT_LENGTH;  break;
      case KEYWORD_PX:    units = eCSSUnit_Pixel;       type = VARIANT_LENGTH;  break;
      case KEYWORD_IN:    units = eCSSUnit_Inch;        type = VARIANT_LENGTH;  break;
      case KEYWORD_CM:    units = eCSSUnit_Centimeter;  type = VARIANT_LENGTH;  break;
      case KEYWORD_MM:    units = eCSSUnit_Millimeter;  type = VARIANT_LENGTH;  break;
      case KEYWORD_PT:    units = eCSSUnit_Point;       type = VARIANT_LENGTH;  break;
      case KEYWORD_PC:    units = eCSSUnit_Pica;        type = VARIANT_LENGTH;  break;

      case KEYWORD_DEG:   units = eCSSUnit_Degree;      type = VARIANT_ANGLE;   break;
      case KEYWORD_GRAD:  units = eCSSUnit_Grad;        type = VARIANT_ANGLE;   break;
      case KEYWORD_RAD:   units = eCSSUnit_Radian;      type = VARIANT_ANGLE;   break;

      case KEYWORD_HZ:    units = eCSSUnit_Hertz;       type = VARIANT_FREQUENCY; break;
      case KEYWORD_KHZ:   units = eCSSUnit_Kilohertz;   type = VARIANT_FREQUENCY; break;

      case KEYWORD_S:     units = eCSSUnit_Seconds;       type = VARIANT_TIME;  break;
      case KEYWORD_MS:    units = eCSSUnit_Milliseconds;  type = VARIANT_TIME;  break;
      default:
        // unknown unit
        return PR_FALSE;
    }
  } else {
    // Must be a zero number...
    NS_ASSERTION(0 == aNumber, "numbers without units must be 0");
    if ((VARIANT_LENGTH & aVariantMask) != 0) {
      units = eCSSUnit_Point;
      type = VARIANT_LENGTH;
    }
    else if ((VARIANT_ANGLE & aVariantMask) != 0) {
      units = eCSSUnit_Degree;
      type = VARIANT_ANGLE;
    }
    else if ((VARIANT_FREQUENCY & aVariantMask) != 0) {
      units = eCSSUnit_Hertz;
      type = VARIANT_FREQUENCY;
    }
    else if ((VARIANT_TIME & aVariantMask) != 0) {
      units = eCSSUnit_Seconds;
      type = VARIANT_TIME;
    }
  }
  if ((type & aVariantMask) != 0) {
    aValue.SetFloatValue(aNumber, units);
    aParsedVariant = type;
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseVariant(PRInt32& aErrorCode,
                                   nsICSSDeclaration* aDeclaration,
                                   PRInt32 aPropID,
                                   PRInt32 aVariantMask, const PRInt32 aKeywordTable[],
                                   PRInt32& aParsedVariant)
{
  nsCSSValue  value;
  if (ParseVariant(aErrorCode, value, aVariantMask, aKeywordTable, aParsedVariant)) {
    aDeclaration->AppendValue(aPropID, value);
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseVariant(PRInt32& aErrorCode, nsCSSValue& aValue,
                                   PRInt32 aVariantMask,
                                   const PRInt32 aKeywordTable[],
                                   PRInt32& aParsedVariant)
{
  aParsedVariant = 0;
  if (!GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }
  nsCSSToken* tk = &mToken;
  if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE)) != 0) &&
      (eCSSToken_Ident == tk->mType)) {
    char cbuf[KEYWORD_BUFFER_SIZE];
    tk->mIdent.ToCString(cbuf, sizeof(cbuf));
    PRInt32 keyword = nsCSSKeywords::LookupName(cbuf);
    if (0 <= keyword) { // known keyword
      if ((aVariantMask & VARIANT_AUTO) != 0) {
        if (KEYWORD_AUTO == keyword) {
          aValue.SetAutoValue();
          aParsedVariant = VARIANT_AUTO;
          return PR_TRUE;
        }
      }
      if ((aVariantMask & VARIANT_INHERIT) != 0) {
        if (KEYWORD_INHERIT == keyword) {
          aValue.SetInheritValue();
          aParsedVariant = VARIANT_INHERIT;
          return PR_TRUE;
        }
      }
      if ((aVariantMask & VARIANT_NONE) != 0) {
        if (KEYWORD_NONE == keyword) {
          aValue.SetNoneValue();
          aParsedVariant = VARIANT_NONE;
          return PR_TRUE;
        }
      }
      if ((aVariantMask & VARIANT_NORMAL) != 0) {
        if (KEYWORD_NORMAL == keyword) {
          aValue.SetNormalValue();
          aParsedVariant = VARIANT_NORMAL;
          return PR_TRUE;
        }
      }
      if ((aVariantMask & VARIANT_KEYWORD) != 0) {
        PRInt32 index = SearchKeywordTable(keyword, aKeywordTable);
        if (0 < index) {
          aValue.SetIntValue(aKeywordTable[index], eCSSUnit_Enumerated);
          aParsedVariant = VARIANT_KEYWORD;
          return PR_TRUE;
        }
      }
    }
  }
  if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE | VARIANT_FREQUENCY | VARIANT_TIME)) != 0) && 
      tk->isDimension()) {
    return TranslateDimension(aValue, aVariantMask, 
                              tk->mNumber, tk->mIdent, 
                              aParsedVariant);
  }
  if (((aVariantMask & VARIANT_PERCENT) != 0) &&
      (eCSSToken_Percentage == tk->mType)) {
    aValue.SetPercentValue(tk->mNumber);
    aParsedVariant = VARIANT_PERCENT;
    return PR_TRUE;
  }
  if (((aVariantMask & VARIANT_NUMBER) != 0) &&
      (eCSSToken_Number == tk->mType)) {
    aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number);
    aParsedVariant = VARIANT_NUMBER;
    return PR_TRUE;
  }
  if (((aVariantMask & VARIANT_INTEGER) != 0) &&
      (eCSSToken_Number == tk->mType) && tk->mIntegerValid) {
    aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer);
    aParsedVariant = VARIANT_INTEGER;
    return PR_TRUE;
  }
  if (PR_TRUE == mNavQuirkMode) { // NONSTANDARD: Nav interprets unitless numbers as px
    if (((aVariantMask & VARIANT_LENGTH) != 0) &&
        (eCSSToken_Number == tk->mType)) {
      aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
      aParsedVariant = VARIANT_LENGTH;
      return PR_TRUE;
    }
  }
  if (((aVariantMask & VARIANT_URL) != 0) &&
      (eCSSToken_Function == tk->mType) && 
      tk->mIdent.EqualsIgnoreCase("url")) {
    if (ParseURL(aErrorCode, aValue)) {
      aParsedVariant = VARIANT_URL;
      return PR_TRUE;
    }
    return PR_FALSE;
  }
  if ((aVariantMask & VARIANT_COLOR) != 0) {
    if ((eCSSToken_ID == tk->mType) || 
        (eCSSToken_Ident == tk->mType) ||
        ((eCSSToken_Function == tk->mType) && 
         (tk->mIdent.EqualsIgnoreCase("rgb")))) {
      // Put token back so that parse color can get it
      UngetToken();
      if (ParseColor(aErrorCode, aValue)) {
        aParsedVariant = VARIANT_COLOR;
        return PR_TRUE;
      }
      return PR_FALSE;
    }
  }
  if (((aVariantMask & VARIANT_STRING) != 0) && 
      (eCSSToken_String == tk->mType)) {
    nsAutoString  buffer;
    buffer.Append(tk->mSymbol);
    buffer.Append(tk->mIdent);
    buffer.Append(tk->mSymbol);
    aValue.SetStringValue(buffer, eCSSUnit_String);
    aParsedVariant = VARIANT_STRING;
    return PR_TRUE;
  }
  if (((aVariantMask & VARIANT_IDENTIFIER) != 0) && 
      (eCSSToken_Ident == tk->mType)) {
    aValue.SetStringValue(tk->mIdent, eCSSUnit_String);
    aParsedVariant = VARIANT_IDENTIFIER;
    return PR_TRUE;
  }
  if (((aVariantMask & VARIANT_COUNTER) != 0) &&
      (eCSSToken_Function == tk->mType) &&
      (tk->mIdent.EqualsIgnoreCase("counter") || 
       tk->mIdent.EqualsIgnoreCase("counters"))) {
    if (ParseCounter(aErrorCode, aValue)) {
      aParsedVariant = VARIANT_COUNTER;
      return PR_TRUE;
    }
    return PR_FALSE;
  }
  if (((aVariantMask & VARIANT_ATTR) != 0) &&
      (eCSSToken_Function == tk->mType) &&
      tk->mIdent.EqualsIgnoreCase("attr")) {
    
    if (ParseAttr(aErrorCode, aValue)) {
      aParsedVariant = VARIANT_ATTR;
      return PR_TRUE;
    }
    return PR_FALSE;
  }

  UngetToken();
  return PR_FALSE;
}


PRBool CSSParserImpl::ParseCounter(PRInt32& aErrorCode, nsCSSValue& aValue)
{
  nsCSSUnit unit = (mToken.mIdent.EqualsIgnoreCase("counter") ? 
                    eCSSUnit_Counter : eCSSUnit_Counters);

  if (ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
    if (GetToken(aErrorCode, PR_TRUE)) {
      if (eCSSToken_Ident == mToken.mType) {
        nsAutoString  counter;
        counter.Append(mToken.mIdent);

        if (eCSSUnit_Counters == unit) {
          // get mandatory string
          if (! ExpectSymbol(aErrorCode, ',', PR_TRUE)) {
            return PR_FALSE;
          }
          if (GetToken(aErrorCode, PR_TRUE) && (eCSSToken_String == mToken.mType)) {
            counter.Append(',');
            counter.Append(mToken.mSymbol); // quote too
            counter.Append(mToken.mIdent);
            counter.Append(mToken.mSymbol); // quote too
          }
          else {
            UngetToken();
            return PR_FALSE;
          }
        }
        // get optional type
        if (ExpectSymbol(aErrorCode, ',', PR_TRUE)) {
          if (GetToken(aErrorCode, PR_TRUE) && (eCSSToken_Ident == mToken.mType)) {
            char cbuf[KEYWORD_BUFFER_SIZE];
            mToken.mIdent.ToCString(cbuf, sizeof(cbuf));
            PRInt32 keyword = nsCSSKeywords::LookupName(cbuf);
            if ((0 <= keyword) && 
                (0 < SearchKeywordTable(keyword, nsCSSProps::kListStyleKTable))) {
              counter.Append(',');
              counter.Append(mToken.mIdent);
            }
            else {
              return PR_FALSE;
            }
          }
          else {
            UngetToken();
            return PR_FALSE;
          }
        }

        if (ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
          aValue.SetStringValue(counter, unit);
          return PR_TRUE;
        }
      }
      else {
        UngetToken();
      }
    }
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseAttr(PRInt32& aErrorCode, nsCSSValue& aValue)
{
  if (ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
    if (GetToken(aErrorCode, PR_TRUE)) {
      if ((eCSSToken_String == mToken.mType) || 
          (eCSSToken_Ident == mToken.mType)) {
        nsAutoString  attr;
        if (eCSSToken_String == mToken.mType) {
          attr.Append(mToken.mSymbol);
          attr.Append(mToken.mIdent);
          attr.Append(mToken.mSymbol);
        }
        else {
          attr.Append(mToken.mIdent);
        }
        if (ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
          aValue.SetStringValue(attr, eCSSUnit_Attr);
          return PR_TRUE;
        }
      }
      else {
        UngetToken();
      }
    }
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseURL(PRInt32& aErrorCode, nsCSSValue& aValue)
{
  if (ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
    if (! GetURLToken(aErrorCode, PR_TRUE)) {
      return PR_FALSE;
    }
    nsCSSToken* tk = &mToken;
    if ((eCSSToken_String == tk->mType) || (eCSSToken_URL == tk->mType)) {
      // Translate url into an absolute url if the url is relative to
      // the style sheet.
      // XXX editors won't like this - too bad for now
      nsAutoString absURL;
      nsString baseURL;
      nsresult rv = NS_MakeAbsoluteURL(mURL, baseURL, tk->mIdent, absURL);
      if (NS_OK != rv) {
        absURL = tk->mIdent;
      }
      if (ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
        aValue.SetStringValue(absURL, eCSSUnit_URL);
        return PR_TRUE;
      }
    }
  }
  return PR_FALSE;
}

PRInt32 CSSParserImpl::ParseChoice(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                                   const PRInt32 aPropIDs[], PRInt32 aNumIDs)
{
  PRInt32 found = 0;
  int loop;
  for (loop = 0; loop < aNumIDs; loop++) {
    // Try each property parser in order
    PRInt32 hadFound = found;
    int index;
    for (index = 0; index < aNumIDs; index++) {
      PRInt32 bit = 1 << index;
      if ((found & bit) == 0) {
        if (ParseProperty(aErrorCode, aDeclaration, aPropIDs[index])) {
          found |= bit;
        }
      }
    }
    if (found == hadFound) {  // found nothing new
      break;
    }
  }
  return found;
}

/**
 * Parse a "box" property. Box properties have 1 to 4 values. When less
 * than 4 values are provided a standard mapping is used to replicate
 * existing values. Note that the replication requires renaming the
 * values.
 */
PRBool CSSParserImpl::ParseBoxProperties(PRInt32& aErrorCode,
                                         nsICSSDeclaration* aDeclaration,
                                         const PRInt32 aPropIDs[])
{
  // Get up to four values for the property
  PRInt32 count = 0;
  int index;
  for (index = 0; index < 4; index++) {
    if (! ParseProperty(aErrorCode, aDeclaration, aPropIDs[index])) {
      break;
    }
    count++;
  }
  if (count == 0) {
    return PR_FALSE;
  }

  // Provide missing values by replicating some of the values found
  nsCSSValue value;
  switch (count) {
    case 1: // Make right == top
      aDeclaration->GetValue(aPropIDs[0], value);
      aDeclaration->AppendValue(aPropIDs[1], value);
    case 2: // Make bottom == top
      aDeclaration->GetValue(aPropIDs[0], value);
      aDeclaration->AppendValue(aPropIDs[2], value);
    case 3: // Make left == right
      aDeclaration->GetValue(aPropIDs[1], value);
      aDeclaration->AppendValue(aPropIDs[3], value);
  }

  return PR_TRUE;
}

PRBool CSSParserImpl::ParseProperty(PRInt32& aErrorCode,
                                    nsICSSDeclaration* aDeclaration,
                                    PRInt32 aPropID,
                                    PRInt32& aChangeHint)
{
  // Strip out properties we use internally. These properties are used
  // by compound property parsing routines (e.g. "background-position").
  switch (aPropID) {
    case PROP_BACKGROUND_X_POSITION:
    case PROP_BACKGROUND_Y_POSITION:
    case PROP_BORDER_X_SPACING:
    case PROP_BORDER_Y_SPACING:
    case PROP_CLIP_BOTTOM:
    case PROP_CLIP_LEFT:
    case PROP_CLIP_RIGHT:
    case PROP_CLIP_TOP:
      // The user can't use these
      return PR_FALSE;
    default:
      break;
  }

  if (aChangeHint < nsCSSProps::kHintTable[aPropID]) {
    aChangeHint = nsCSSProps::kHintTable[aPropID];
  }

  return ParseProperty(aErrorCode, aDeclaration, aPropID);
}

#define CSS_SIDE_TOP    0
#define CSS_SIDE_RIGHT  1
#define CSS_SIDE_BOTTOM 2
#define CSS_SIDE_LEFT   3

PRBool CSSParserImpl::ParseProperty(PRInt32& aErrorCode,
                                    nsICSSDeclaration* aDeclaration,
                                    PRInt32 aPropID)
{
  PRInt32 parsed;
  switch (aPropID) {
  case PROP_AZIMUTH:
    return ParseAzimuth(aErrorCode, aDeclaration);
  case PROP_BACKGROUND:
    return ParseBackground(aErrorCode, aDeclaration);
  case PROP_BACKGROUND_ATTACHMENT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kBackgroundAttachmentKTable, parsed);
  case PROP_BACKGROUND_COLOR:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HCK,
                        nsCSSProps::kBackgroundColorKTable, parsed);
  case PROP_BACKGROUND_FILTER:  // XXX
    return ParseBackgroundFilter(aErrorCode, aDeclaration);
  case PROP_BACKGROUND_IMAGE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HUO,
                        nsnull, parsed);
  case PROP_BACKGROUND_POSITION:
    return ParseBackgroundPosition(aErrorCode, aDeclaration);
  case PROP_BACKGROUND_REPEAT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kBackgroundRepeatKTable, parsed);
  case PROP_BORDER:
    return ParseBorder(aErrorCode, aDeclaration);
  case PROP_BORDER_COLLAPSE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kBorderCollapseKTable, parsed);
  case PROP_BORDER_COLOR:
    return ParseBorderColor(aErrorCode, aDeclaration);
  case PROP_BORDER_SPACING:
    return ParseBorderSpacing(aErrorCode, aDeclaration);
  case PROP_BORDER_STYLE:
    return ParseBorderStyle(aErrorCode, aDeclaration);
  case PROP_BORDER_BOTTOM:
    return ParseBorderSide(aErrorCode, aDeclaration, kBorderBottomIDs, CSS_SIDE_BOTTOM);
  case PROP_BORDER_LEFT:
    return ParseBorderSide(aErrorCode, aDeclaration, kBorderLeftIDs, CSS_SIDE_LEFT);
  case PROP_BORDER_RIGHT:
    return ParseBorderSide(aErrorCode, aDeclaration, kBorderRightIDs, CSS_SIDE_RIGHT);
  case PROP_BORDER_TOP:
    return ParseBorderSide(aErrorCode, aDeclaration, kBorderTopIDs, CSS_SIDE_TOP);
  case PROP_BORDER_BOTTOM_COLOR:
  case PROP_BORDER_LEFT_COLOR:
  case PROP_BORDER_RIGHT_COLOR:
  case PROP_BORDER_TOP_COLOR:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HCK, 
                        nsCSSProps::kBorderColorKTable, parsed);
  case PROP_BORDER_BOTTOM_STYLE:
  case PROP_BORDER_LEFT_STYLE:
  case PROP_BORDER_RIGHT_STYLE:
  case PROP_BORDER_TOP_STYLE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kBorderStyleKTable, parsed);
  case PROP_BORDER_BOTTOM_WIDTH:
  case PROP_BORDER_LEFT_WIDTH:
  case PROP_BORDER_RIGHT_WIDTH:
  case PROP_BORDER_TOP_WIDTH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HKL,
                        nsCSSProps::kBorderWidthKTable, parsed);
  case PROP_BORDER_WIDTH:
    return ParseBorderWidth(aErrorCode, aDeclaration);
  case PROP_BOTTOM:
  case PROP_TOP:
  case PROP_LEFT:
  case PROP_RIGHT:
  case PROP_HEIGHT:
  case PROP_WIDTH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHLP, nsnull, parsed);
  case PROP_CAPTION_SIDE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK, 
                        nsCSSProps::kCaptionSideKTable, parsed);
  case PROP_CLEAR:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kClearKTable, parsed);
  case PROP_CLIP:
    return ParseClip(aErrorCode, aDeclaration);
  case PROP_COLOR:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HC, nsnull, parsed);
  case PROP_CONTENT:
    return ParseContent(aErrorCode, aDeclaration);
  case PROP_COUNTER_INCREMENT:
  case PROP_COUNTER_RESET:
    return ParseCounterData(aErrorCode, aDeclaration, aPropID);
  case PROP_CUE:
    return ParseCue(aErrorCode, aDeclaration);
  case PROP_CUE_AFTER:
  case PROP_CUE_BEFORE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HUO, nsnull, parsed);
  case PROP_CURSOR:
    return ParseCursor(aErrorCode, aDeclaration);
  case PROP_DIRECTION:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kDirectionKTable, parsed);
  case PROP_DISPLAY:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kDisplayKTable, parsed);
  case PROP_ELEVATION:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK | VARIANT_ANGLE,
                        nsCSSProps::kElevationKTable, parsed);
  case PROP_EMPTY_CELLS:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kEmptyCellsKTable, parsed);
  case PROP_FILTER:
    return ParseForegroundFilter(aErrorCode, aDeclaration);
  case PROP_FLOAT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kFloatKTable, parsed);
  case PROP_FONT:
    return ParseFont(aErrorCode, aDeclaration);
  case PROP_FONT_FAMILY:
    return ParseFamily(aErrorCode, aDeclaration, aPropID);
  case PROP_FONT_SIZE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HKLP,
                        nsCSSProps::kFontSizeKTable, parsed);
  case PROP_FONT_SIZE_ADJUST:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HON,
                        nsnull, parsed);
  case PROP_FONT_STRETCH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK,
                        nsCSSProps::kFontStretchKTable, parsed);
  case PROP_FONT_STYLE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK,
                        nsCSSProps::kFontStyleKTable, parsed);
  case PROP_FONT_VARIANT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK,
                        nsCSSProps::kFontVariantKTable, parsed);
  case PROP_FONT_WEIGHT:
    return ParseVariant(aErrorCode, aDeclaration, PROP_FONT_WEIGHT, VARIANT_HMKI,
                        nsCSSProps::kFontWeightKTable, parsed);
  case PROP_LETTER_SPACING:
  case PROP_WORD_SPACING:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HL | VARIANT_NORMAL, nsnull, parsed);
  case PROP_LINE_HEIGHT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HLPN | VARIANT_NORMAL,
                        nsnull, parsed);
  case PROP_LIST_STYLE:
    return ParseListStyle(aErrorCode, aDeclaration);
  case PROP_LIST_STYLE_IMAGE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HUO, nsnull, parsed);
  case PROP_LIST_STYLE_POSITION:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kListStylePositionKTable, parsed);
  case PROP_LIST_STYLE_TYPE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kListStyleKTable, parsed);
  case PROP_MARGIN:
    return ParseMargin(aErrorCode, aDeclaration);
  case PROP_MARGIN_BOTTOM:
  case PROP_MARGIN_LEFT:
  case PROP_MARGIN_RIGHT:
  case PROP_MARGIN_TOP:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHLP, nsnull, parsed);
  case PROP_MARKER_OFFSET:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHL, nsnull, parsed);
  case PROP_MARKS:
    return ParseMarks(aErrorCode, aDeclaration);
  case PROP_MAX_HEIGHT:
  case PROP_MAX_WIDTH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HLPO, nsnull, parsed);
  case PROP_MIN_HEIGHT:
  case PROP_MIN_WIDTH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HLP, nsnull, parsed);
  case PROP_OPACITY:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HPN, nsnull, parsed);
  case PROP_ORPHANS:
  case PROP_WIDOWS:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HI, nsnull, parsed);
  case PROP_OUTLINE:
    return ParseOutline(aErrorCode, aDeclaration);
  case PROP_OUTLINE_COLOR:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HCK, 
                        nsCSSProps::kOutlineColorKTable, parsed);
  case PROP_OUTLINE_STYLE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK, 
                        nsCSSProps::kBorderStyleKTable, parsed);
  case PROP_OUTLINE_WIDTH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HKL,
                        nsCSSProps::kBorderWidthKTable, parsed);
  case PROP_OVERFLOW:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHK,
                        nsCSSProps::kOverflowKTable, parsed);
  case PROP_PADDING:
    return ParsePadding(aErrorCode, aDeclaration);
  case PROP_PADDING_BOTTOM:
  case PROP_PADDING_LEFT:
  case PROP_PADDING_RIGHT:
  case PROP_PADDING_TOP:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HLP, nsnull, parsed);
  case PROP_PAGE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AUTO | VARIANT_IDENTIFIER, 
                        nsnull, parsed);
  case PROP_PAGE_BREAK_AFTER:
  case PROP_PAGE_BREAK_BEFORE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHK, 
                        nsCSSProps::kPageBreakKTable, parsed);
  case PROP_PAGE_BREAK_INSIDE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHK, 
                        nsCSSProps::kPageBreakInsideKTable, parsed);
  case PROP_PAUSE:
    return ParsePause(aErrorCode, aDeclaration);
  case PROP_PAUSE_AFTER:
  case PROP_PAUSE_BEFORE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HTP, nsnull, parsed);
  case PROP_PITCH:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HKF,
                        nsCSSProps::kPitchKTable, parsed);
  case PROP_PITCH_RANGE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HN, nsnull, parsed);
  case PROP_PLAY_DURING:
    return ParsePlayDuring(aErrorCode, aDeclaration);
  case PROP_POSITION:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK, 
                        nsCSSProps::kPositionKTable, parsed);
  case PROP_QUOTES:
    return ParseQuotes(aErrorCode, aDeclaration);
  case PROP_RICHNESS:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HN, nsnull, parsed);
  case PROP_SIZE:
    return ParseSize(aErrorCode, aDeclaration);
  case PROP_SPEAK:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK | VARIANT_NONE,
                        nsCSSProps::kSpeakKTable, parsed);
  case PROP_SPEAK_HEADER:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kSpeakHeaderKTable, parsed);
  case PROP_SPEAK_NUMERAL:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kSpeakNumeralKTable, parsed);
  case PROP_SPEAK_PUNCTUATION:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK,
                        nsCSSProps::kSpeakPunctuationKTable, parsed);
  case PROP_SPEECH_RATE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HN | VARIANT_KEYWORD,
                        nsCSSProps::kSpeechRateKTable, parsed);
  case PROP_STRESS:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HN, nsnull, parsed);
  case PROP_TABLE_LAYOUT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHK,
                        nsCSSProps::kTableLayoutKTable, parsed);
  case PROP_TEXT_ALIGN:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK | VARIANT_STRING,
                        nsCSSProps::kTextAlignKTable, parsed);
  case PROP_TEXT_DECORATION:
    return ParseTextDecoration(aErrorCode, aDeclaration);
  case PROP_TEXT_INDENT:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HLP, nsnull, parsed);
  case PROP_TEXT_SHADOW:
    return ParseTextShadow(aErrorCode, aDeclaration);
  case PROP_TEXT_TRANSFORM:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HOK,
                        nsCSSProps::kTextTransformKTable, parsed);
  case PROP_UNICODE_BIDI:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK,
                        nsCSSProps::kUnicodeBidiKTable, parsed);
  case PROP_VERTICAL_ALIGN:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HKLP,
                        nsCSSProps::kVerticalAlignKTable, parsed);
  case PROP_VISIBILITY:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HK, 
                        nsCSSProps::kVisibilityKTable, parsed);
  case PROP_VOICE_FAMILY:
    return ParseFamily(aErrorCode, aDeclaration, aPropID);
  case PROP_VOLUME:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HPN | VARIANT_KEYWORD,
                        nsCSSProps::kVolumeKTable, parsed);
  case PROP_WHITE_SPACE:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_HMK,
                        nsCSSProps::kWhitespaceKTable, parsed);
  case PROP_Z_INDEX:
    return ParseVariant(aErrorCode, aDeclaration, aPropID, VARIANT_AHI, nsnull, parsed);
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseAzimuth(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_AZIMUTH, VARIANT_HK | VARIANT_ANGLE, 
                   nsCSSProps::kAzimuthKTable, parsed)) {
    if (VARIANT_KEYWORD == parsed) {
      nsCSSValue  value;
      aDeclaration->GetValue(PROP_AZIMUTH, value);
      PRInt32 intValue = value.GetIntValue();
      if ((NS_STYLE_AZIMUTH_LEFT_SIDE <= intValue) && 
          (intValue <= NS_STYLE_AZIMUTH_BEHIND)) {  // look for optional modifier
        nsCSSValue  modifier;
        if (ParseEnum(aErrorCode, modifier, nsCSSProps::kAzimuthKTable)) {
          PRInt32 enumValue = modifier.GetIntValue();
          if (((intValue == NS_STYLE_AZIMUTH_BEHIND) && 
               (NS_STYLE_AZIMUTH_LEFT_SIDE <= enumValue) && (enumValue <= NS_STYLE_AZIMUTH_RIGHT_SIDE)) ||
              ((enumValue == NS_STYLE_AZIMUTH_BEHIND) && 
               (NS_STYLE_AZIMUTH_LEFT_SIDE <= intValue) && (intValue <= NS_STYLE_AZIMUTH_RIGHT_SIDE))) {
            value.SetIntValue(intValue | enumValue, eCSSUnit_Enumerated);
            aDeclaration->AppendValue(PROP_AZIMUTH, value);
            return PR_TRUE;
          }
          // Put the unknown identifier back and return
          UngetToken();
          value.Reset();
          aDeclaration->AppendValue(PROP_AZIMUTH, value); // clear what we got
          return PR_FALSE;
        }
      }
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseBackground(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kBackgroundIDs[] = {
    PROP_BACKGROUND_COLOR,
    PROP_BACKGROUND_IMAGE,
    PROP_BACKGROUND_REPEAT,
    PROP_BACKGROUND_ATTACHMENT,
    PROP_BACKGROUND_POSITION,
//    PROP_BACKGROUND_FILTER
  };

  PRInt32 found = ParseChoice(aErrorCode, aDeclaration, kBackgroundIDs, 5);
  if (0 == found) {
    return PR_FALSE;
  }

  if (1 == found) {
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_BACKGROUND_COLOR, value);
    if (eCSSUnit_Inherit == value.GetUnit()) {  // one inherit value, all inherit
      aDeclaration->AppendValue(PROP_BACKGROUND_IMAGE, value);
      aDeclaration->AppendValue(PROP_BACKGROUND_REPEAT, value);
      aDeclaration->AppendValue(PROP_BACKGROUND_ATTACHMENT, value);
      aDeclaration->AppendValue(PROP_BACKGROUND_X_POSITION, value);
      aDeclaration->AppendValue(PROP_BACKGROUND_Y_POSITION, value);
//      aDeclaration->AppendValue(PROP_BACKGROUND_FILTER, value);
      return PR_TRUE;
    }
  }

  // Provide missing values
  if ((found & 1) == 0) {
    aDeclaration->AppendValue(PROP_BACKGROUND_COLOR, 
                              nsCSSValue(NS_STYLE_BG_COLOR_TRANSPARENT,
                                         eCSSUnit_Enumerated));
  }
  if ((found & 2) == 0) {
    aDeclaration->AppendValue(PROP_BACKGROUND_IMAGE, 
                              nsCSSValue(eCSSUnit_None));
  }
  if ((found & 4) == 0) {
    aDeclaration->AppendValue(PROP_BACKGROUND_REPEAT, 
                              nsCSSValue(NS_STYLE_BG_REPEAT_XY,
                                         eCSSUnit_Enumerated));
  }
  if ((found & 8) == 0) {
    aDeclaration->AppendValue(PROP_BACKGROUND_ATTACHMENT, 
                              nsCSSValue(NS_STYLE_BG_ATTACHMENT_SCROLL,
                                         eCSSUnit_Enumerated));
  }
  if ((found & 16) == 0) {
    nsCSSValue  zero(0.0f, eCSSUnit_Percent);
    aDeclaration->AppendValue(PROP_BACKGROUND_X_POSITION, zero);
    aDeclaration->AppendValue(PROP_BACKGROUND_Y_POSITION, zero);
  }

  // XXX Note: no default for filter (yet)
  return PR_TRUE;
}

// Bits used in determining which background position info we have
#define BG_CENTER  0
#define BG_TOP     1
#define BG_BOTTOM  2
#define BG_LEFT    4
#define BG_RIGHT   8
#define BG_CENTER1 16
#define BG_CENTER2 32

PRBool CSSParserImpl::ParseBackgroundPosition(PRInt32& aErrorCode,
                                              nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kBackgroundPositionIDs[] = {
    PROP_BACKGROUND_X_POSITION,
    PROP_BACKGROUND_Y_POSITION,
  };

  // Note: Don't change this table unless you update
  // parseBackgroundPosition!

  static const PRInt32 kBackgroundXYPositionKTable[] = {
    KEYWORD_CENTER, BG_CENTER,
    KEYWORD_TOP, BG_TOP,
    KEYWORD_BOTTOM, BG_BOTTOM,
    KEYWORD_LEFT, BG_LEFT,
    KEYWORD_RIGHT, BG_RIGHT,
    -1,
  };

  // First try a number or a length value

  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_BACKGROUND_X_POSITION, VARIANT_HLP, nsnull, parsed)) {
    if (VARIANT_INHERIT == parsed) {  // both are inherited
      aDeclaration->AppendValue(PROP_BACKGROUND_Y_POSITION, nsCSSValue(eCSSUnit_Inherit));
      return PR_TRUE;
    }
    // We have one number/length. Get the optional second number/length.
    if (ParseVariant(aErrorCode, aDeclaration, PROP_BACKGROUND_Y_POSITION, VARIANT_LP, nsnull, parsed)) {
      // We have two numbers
      return PR_TRUE;
    }

    // We have one number which is the x position. Create an value for
    // the vertical position which is of value 50%
    aDeclaration->AppendValue(PROP_BACKGROUND_Y_POSITION, nsCSSValue(0.5f, eCSSUnit_Percent));
    // XXX shouldn't this be CENTER enum instead?
    return PR_TRUE;
  }

  // Now try keywords. We do this manually to allow for the first
  // appearance of "center" to apply to the either the x or y
  // position (it's ambiguous so we have to disambiguate). Each
  // allowed keyword value is assigned it's own bit. We don't allow
  // any duplicate keywords other than center. We try to get two
  // keywords but it's okay if there is only one.
  PRInt32 mask = 0;
  PRInt32 centerBit = BG_CENTER1;
  for (PRInt32 i = 0; i < 2; i++) {
    nsString* ident = NextIdent(aErrorCode);
    if (nsnull == ident) {
      break;
    }
    char cbuf[KEYWORD_BUFFER_SIZE];
    ident->ToCString(cbuf, sizeof(cbuf));
    PRInt32 index = SearchKeywordTable(nsCSSKeywords::LookupName(cbuf),
                                       kBackgroundXYPositionKTable);
    if (0 < index) {
      PRInt32 bit = kBackgroundXYPositionKTable[index];
      if (0 == bit) {
        // Special hack for center bits: We can have two of them
        mask |= centerBit;
        centerBit <<= 1;
        continue;
      } else if ((mask & bit) != 0) {
        // no duplicate values allowed (other than center)
        return PR_FALSE;
      }
      mask |= bit;
    } else {
      UngetToken();
      break;
    }
  }

  // Check for bad input. Bad input consists of no matching keywords,
  // or pairs of x keywords or pairs of y keywords.
  if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
      (mask == (BG_LEFT | BG_RIGHT))) {
    return PR_FALSE;
  }

  // Map good input
  int xValue = 50;
  if ((mask & (BG_LEFT | BG_RIGHT)) != 0) {
    // We have an x value
    xValue = ((mask & BG_LEFT) != 0) ? 0 : 100;
  }
  int yValue = 50;
  if ((mask & (BG_TOP | BG_BOTTOM)) != 0) {
    // We have a y value
    yValue = ((mask & BG_TOP) != 0) ? 0 : 100;
  }

  // Create style values
  aDeclaration->AppendValue(PROP_BACKGROUND_X_POSITION, nsCSSValue(xValue, eCSSUnit_Enumerated));
  aDeclaration->AppendValue(PROP_BACKGROUND_Y_POSITION, nsCSSValue(yValue, eCSSUnit_Enumerated));
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseBackgroundFilter(PRInt32& aErrorCode,
                                            nsICSSDeclaration* aDeclaration)
{
  // XXX not yet supported
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseForegroundFilter(PRInt32& aErrorCode,
                                            nsICSSDeclaration* aDeclaration)
{
  // XXX not yet supported
  return PR_FALSE;
}

// These must be in CSS order (top,right,bottom,left) for indexing to work
static const PRInt32 kBorderStyleIDs[] = {
  PROP_BORDER_TOP_STYLE,
  PROP_BORDER_RIGHT_STYLE,
  PROP_BORDER_BOTTOM_STYLE,
  PROP_BORDER_LEFT_STYLE
};
static const PRInt32 kBorderWidthIDs[] = {
  PROP_BORDER_TOP_WIDTH,
  PROP_BORDER_RIGHT_WIDTH,
  PROP_BORDER_BOTTOM_WIDTH,
  PROP_BORDER_LEFT_WIDTH
};
static const PRInt32 kBorderColorIDs[] = {
  PROP_BORDER_TOP_COLOR,
  PROP_BORDER_RIGHT_COLOR,
  PROP_BORDER_BOTTOM_COLOR,
  PROP_BORDER_LEFT_COLOR
};


PRBool CSSParserImpl::ParseBorder(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kBorderIDs[] = {
    PROP_BORDER_TOP_WIDTH,  // only one value per property
    PROP_BORDER_TOP_STYLE,
    PROP_BORDER_TOP_COLOR
  };
  PRInt32 found = ParseChoice(aErrorCode, aDeclaration, kBorderIDs, 3);
  if (0 == found) {
    return PR_FALSE;
  }

  int index;
  if (1 == found) { // only found width
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_BORDER_TOP_WIDTH, value);
    if (eCSSUnit_Inherit == value.GetUnit()) {  // one inherit value, all inherit
      for (index = 0; index < 4; index++) {
        aDeclaration->AppendValue(kBorderWidthIDs[index], value);
        aDeclaration->AppendValue(kBorderStyleIDs[index], value);
        aDeclaration->AppendValue(kBorderColorIDs[index], value);
      }
      return PR_TRUE;
    }
  }

  if (0 == (found & 1)) {
    // provide missing border width's
    nsCSSValue medium(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
    for (index = 0; index < 4; index++) {
      aDeclaration->AppendValue(kBorderWidthIDs[index], medium);
    }
  }
  else {  // copy top to other three sides
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_BORDER_TOP_WIDTH, value);
    for (index = 1; index < 4; index++) {
      aDeclaration->AppendValue(kBorderWidthIDs[index], value);
    }
  }
  
  if (0 == (found & 2)) {
    // provide missing border style's
    nsCSSValue none(eCSSUnit_None);
    for (index = 0; index < 4; index++) {
      aDeclaration->AppendValue(kBorderStyleIDs[index], none);
    }
  }
  else {  // copy top to other three sides
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_BORDER_TOP_STYLE, value);
    for (index = 1; index < 4; index++) {
      aDeclaration->AppendValue(kBorderStyleIDs[index], value);
    }
  }

  if (0 == (found & 4)) {
    // clear missing color values so color property will be used
    nsCSSValue null;
    for (index = 0; index < 4; index++) {
      aDeclaration->AppendValue(kBorderColorIDs[index], null);
    }
  }
  else {  // copy top to other three sides
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_BORDER_TOP_COLOR, value);
    for (index = 1; index < 4; index++) {
      aDeclaration->AppendValue(kBorderColorIDs[index], value);
    }
  }
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseBorderColor(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  return ParseBoxProperties(aErrorCode, aDeclaration, kBorderColorIDs);
}

PRBool CSSParserImpl::ParseBorderSpacing(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_BORDER_X_SPACING, VARIANT_HL, nsnull, parsed)) {
    if (VARIANT_INHERIT == parsed) {  // both are inherited
      aDeclaration->AppendValue(PROP_BORDER_Y_SPACING, nsCSSValue(eCSSUnit_Inherit));
      return PR_TRUE;
    }
    // We have one length. Get the optional second length.
    if (ParseVariant(aErrorCode, aDeclaration, PROP_BORDER_Y_SPACING, VARIANT_LENGTH, 
                     nsnull, parsed)) {
      // We have two numbers
      return PR_TRUE;
    }

    // We have one length which is the horizontal spacing. Create a value for
    // the vertical spacing which is equal
    nsCSSValue value;
    aDeclaration->GetValue(PROP_BORDER_X_SPACING, value);
    aDeclaration->AppendValue(PROP_BORDER_Y_SPACING, value);
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseBorderSide(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                                      const PRInt32 aPropIDs[], PRInt32 aSide)
{
  PRInt32 found = ParseChoice(aErrorCode, aDeclaration, aPropIDs, 3);
  if (0 == found) {
    return PR_FALSE;
  }

  if (1 == found) {
    nsCSSValue  value;
    aDeclaration->GetValue(aPropIDs[0], value);
    if (eCSSUnit_Inherit == value.GetUnit()) {  // one inherit value, all inherit
      aDeclaration->AppendValue(aPropIDs[1], value);
      aDeclaration->AppendValue(aPropIDs[2], value);
      return PR_TRUE;
    }
  }

  if ((found & 1) == 0) {
    // Provide default border-width
    aDeclaration->AppendValue(kBorderWidthIDs[aSide],
                              nsCSSValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated));
  }
  if ((found & 2) == 0) {
    // Provide default border-style
    aDeclaration->AppendValue(kBorderStyleIDs[aSide], nsCSSValue(eCSSUnit_None));
  }

  if ((found & 4) == 0) {
    // reset border-color so color property gets used
    aDeclaration->AppendValue(kBorderColorIDs[aSide], nsCSSValue(eCSSUnit_Null));
  }

  return PR_TRUE;
}

PRBool CSSParserImpl::ParseBorderStyle(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  return ParseBoxProperties(aErrorCode, aDeclaration, kBorderStyleIDs);
}

PRBool CSSParserImpl::ParseBorderWidth(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  return ParseBoxProperties(aErrorCode, aDeclaration, kBorderWidthIDs);
}

PRBool CSSParserImpl::ParseClip(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kClipIDs[] = {
    PROP_CLIP_TOP,
    PROP_CLIP_RIGHT,
    PROP_CLIP_BOTTOM,
    PROP_CLIP_LEFT
  };
  if (! GetToken(aErrorCode, PR_TRUE)) {
    return PR_FALSE;
  }
  int index;
  if ((eCSSToken_Ident == mToken.mType) && 
      mToken.mIdent.EqualsIgnoreCase("auto")) {
    nsCSSValue valueAuto(eCSSUnit_Auto);
    for (index = 0; index < 4; index++) {
      aDeclaration->AppendValue(kClipIDs[index], valueAuto);
    }
    return PR_TRUE;
  } else if ((eCSSToken_Ident == mToken.mType) && 
             mToken.mIdent.EqualsIgnoreCase("inherit")) {
    nsCSSValue  inherit(eCSSUnit_Inherit);
    for (index = 0; index < 4; index++) {
      aDeclaration->AppendValue(kClipIDs[index], inherit);
    }
    return PR_TRUE;
  } else if ((eCSSToken_Function == mToken.mType) && 
             mToken.mIdent.EqualsIgnoreCase("rect")) {
    if (!ExpectSymbol(aErrorCode, '(', PR_TRUE)) {
      return PR_FALSE;
    }
    for (index = 0; index < 4; index++) {
      PRInt32 parsed;
      if (! ParseVariant(aErrorCode, aDeclaration, kClipIDs[index], VARIANT_AL, nsnull, parsed)) {
        return PR_FALSE;
        // XXX need to reset values if failed
      }
      if (3 != index) {
        // skip optional commas between elements
        ExpectSymbol(aErrorCode, ',', PR_TRUE);
      }
    }
    if (!ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
      return PR_FALSE;
    }
    return PR_TRUE;
  } else {
    UngetToken();
  }
  return PR_FALSE;
}

#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \
                         VARIANT_KEYWORD)
PRBool CSSParserImpl::ParseContent(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_CONTENT, VARIANT_CONTENT | VARIANT_INHERIT, 
                   nsCSSProps::kContentKTable, parsed)) {
    if (VARIANT_INHERIT != parsed) {
      nsCSSContent* content = nsnull;
      aDeclaration->GetData(kCSSContentSID, (nsCSSStruct**)&content);
      if (nsnull != content) {
        nsCSSValueList* list = content->mContent;
        while (nsnull != content) {
          if (ExpectEndProperty(aErrorCode, PR_TRUE)) {
            return PR_TRUE;
          }
          nsCSSValue  value;
          if (ParseVariant(aErrorCode, value, VARIANT_CONTENT, 
                           nsCSSProps::kContentKTable, parsed)) {
            list->mNext = new nsCSSValueList();
            list = list->mNext;
            if (nsnull != list) {
              list->mValue = value;
            }
          }
          else {
            break;
          }
        }
      }
      aDeclaration->AppendValue(PROP_CONTENT, nsCSSValue());
      return PR_FALSE;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseCounterData(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration,
                                       PRInt32 aPropID)
{
  nsString* ident = NextIdent(aErrorCode);
  if (nsnull == ident) {
    return PR_FALSE;
  }
  if (ident->EqualsIgnoreCase("none")) {
    aDeclaration->AppendValue(aPropID, nsCSSValue(eCSSUnit_None));
  }
  else if (ident->EqualsIgnoreCase("inherit")) {
    aDeclaration->AppendValue(aPropID, nsCSSValue(eCSSUnit_Inherit));
  }
  else {
    aDeclaration->AppendValue(aPropID, nsCSSValue(*ident, eCSSUnit_String));
    nsCSSContent* content = nsnull;
    aDeclaration->GetData(kCSSContentSID, (nsCSSStruct**)&content);
    if (nsnull != content) {
      nsCSSCounterData* data = ((PROP_COUNTER_INCREMENT == aPropID) ? 
                                content->mCounterIncrement : content->mCounterReset);
      data->mValue.Reset();
      while (nsnull != data) {
        if (ExpectEndProperty(aErrorCode, PR_TRUE)) {
          return PR_TRUE;
        }
        if (! GetToken(aErrorCode, PR_TRUE)) {
          break;
        }
        if ((eCSSToken_Number == mToken.mType) && (mToken.mIntegerValid)) {
          data->mValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
          if (ExpectEndProperty(aErrorCode, PR_TRUE)) {
            return PR_TRUE;
          }
          if (! GetToken(aErrorCode, PR_TRUE)) {
            break;
          }
        }
        if (eCSSToken_Ident == mToken.mType) {
          data->mNext = new nsCSSCounterData();
          data = data->mNext;
          if (nsnull != data) {
            data->mCounter.SetStringValue(mToken.mIdent, eCSSUnit_String);
          }
        }
        else {
          break;
        }
      }
    }

    aDeclaration->AppendValue(aPropID, nsCSSValue()); // clear it
    return PR_FALSE;
  }
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseCue(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  if (ParseProperty(aErrorCode, aDeclaration, PROP_CUE_BEFORE)) {
    if (! ParseProperty(aErrorCode, aDeclaration, PROP_CUE_AFTER)) {
      nsCSSValue  value;
      aDeclaration->GetValue(PROP_CUE_BEFORE, value);
      aDeclaration->AppendValue(PROP_CUE_AFTER, value);
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseCursor(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_CURSOR, VARIANT_AHUK, 
                   nsCSSProps::kCursorKTable, parsed)) {
    if (VARIANT_URL == parsed) {
      nsCSSColor* color = nsnull;
      aDeclaration->GetData(kCSSColorSID, (nsCSSStruct**)&color);
      if (nsnull != color) {
        nsCSSValueList* list = color->mCursor;
        while (nsnull != list) {
          if ((VARIANT_URL != parsed) || ExpectEndProperty(aErrorCode, PR_TRUE)) {
            return PR_TRUE;
          }
          nsCSSValue  value;
          if (ParseVariant(aErrorCode, value, VARIANT_AHUK, 
                           nsCSSProps::kCursorKTable, parsed)) {
            list->mNext = new nsCSSValueList();
            list = list->mNext;
            if (nsnull != list) {
              list->mValue = value;
            }
          }
          else {
            break;
          }
        }
      }
      aDeclaration->AppendValue(PROP_CURSOR, nsCSSValue());
      return PR_FALSE;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}


PRBool CSSParserImpl::ParseFont(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 fontIDs[] = {
    PROP_FONT_STYLE,
    PROP_FONT_VARIANT,
    PROP_FONT_WEIGHT
  };

  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_FONT_FAMILY, VARIANT_HK, 
                   nsCSSProps::kFontKTable, parsed)) {
    if (VARIANT_INHERIT == parsed) {
      nsCSSValue  inherit(eCSSUnit_Inherit);
      aDeclaration->AppendValue(PROP_FONT_STYLE, inherit);
      aDeclaration->AppendValue(PROP_FONT_VARIANT, inherit);
      aDeclaration->AppendValue(PROP_FONT_WEIGHT, inherit);
      aDeclaration->AppendValue(PROP_FONT_SIZE, inherit);
      aDeclaration->AppendValue(PROP_LINE_HEIGHT, inherit);
    }
    return PR_TRUE;
  }

  // Get optional font-style, font-variant and font-weight (in any order)
  PRInt32 found = ParseChoice(aErrorCode, aDeclaration, fontIDs, 3);
  nsCSSValue normal(eCSSUnit_Normal);
  if ((found & 1) == 0) {
    // Provide default font-style
    aDeclaration->AppendValue(fontIDs[0], normal);
  }
  if ((found & 2) == 0) {
    // Provide default font-variant
    aDeclaration->AppendValue(fontIDs[1], normal);
  }
  if ((found & 4) == 0) {
    // Provide default font-weight
    aDeclaration->AppendValue(fontIDs[2], normal);
  }

  // Get mandatory font-size
  if (!ParseVariant(aErrorCode, aDeclaration, PROP_FONT_SIZE,
                    VARIANT_HKLP, nsCSSProps::kFontSizeKTable, parsed)) {
    return PR_FALSE;
  }

  // Get optional "/" line-height
  if (ExpectSymbol(aErrorCode, '/', PR_TRUE)) {
    if (!ParseVariant(aErrorCode, aDeclaration, PROP_LINE_HEIGHT,
                      VARIANT_HLPN | VARIANT_NORMAL, nsnull, parsed)) {
      return PR_FALSE;
    }
  }
  else {
    aDeclaration->AppendValue(PROP_LINE_HEIGHT, normal);
  }

  // Get final mandatory font-family
  return ParseFamily(aErrorCode, aDeclaration, PROP_FONT_FAMILY);
}

PRBool CSSParserImpl::ParseFamily(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration, 
                                  PRInt32 aPropID)
{
  nsCSSToken* tk = &mToken;
  nsAutoString family;
  PRBool firstOne = PR_TRUE;
  for (;;) {
    if (!GetToken(aErrorCode, PR_TRUE)) {
      break;
    }
    if (eCSSToken_Ident == tk->mType) {
      if (firstOne) {
        if (tk->mIdent.EqualsIgnoreCase("inherit")) {
          aDeclaration->AppendValue(aPropID, nsCSSValue(eCSSUnit_Inherit));
          return PR_TRUE;
        }
      }
      else {
        family.Append(PRUnichar(','));
      }
      family.Append(tk->mIdent);
      for (;;) {
        if (!GetToken(aErrorCode, PR_FALSE)) {
          break;
        }
        if (eCSSToken_Ident == tk->mType) {
          family.Append(tk->mIdent);
        } else if (eCSSToken_WhiteSpace == tk->mType) {
          // Lookahead one token and drop whitespace if we ending the
          // font name.
          if (!GetToken(aErrorCode, PR_TRUE)) {
            break;
          }
          if (eCSSToken_Ident != tk->mType) {
            UngetToken();
            break;
          }
          UngetToken();
          family.Append(PRUnichar(' '));
        } else {
          UngetToken();
          break;
        }
      }
      firstOne = PR_FALSE;
    } else if (eCSSToken_String == tk->mType) {
      if (!firstOne) {
        family.Append(PRUnichar(','));
      }
      family.Append(tk->mSymbol); // replace the quotes
      family.Append(tk->mIdent);
      family.Append(tk->mSymbol);
      firstOne = PR_FALSE;
    } else if (eCSSToken_Symbol == tk->mType) {
      if (',' != tk->mSymbol) {
        UngetToken();
        break;
      }
    } else {
      UngetToken();
      break;
    }
  }
  if (family.Length() == 0) {
    return PR_FALSE;
  }
  aDeclaration->AppendValue(aPropID, nsCSSValue(family, eCSSUnit_String));
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseListStyle(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 listStyleIDs[] = {
    PROP_LIST_STYLE_TYPE,
    PROP_LIST_STYLE_POSITION,
    PROP_LIST_STYLE_IMAGE
  };

  int found = ParseChoice(aErrorCode, aDeclaration, listStyleIDs, 3);
  if (found == 0) {
    return PR_FALSE;
  }

  if (1 == found) {
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_LIST_STYLE_TYPE, value);
    if (eCSSUnit_Inherit == value.GetUnit()) {  // one inherit value, all inherit
      aDeclaration->AppendValue(PROP_LIST_STYLE_POSITION, value);
      aDeclaration->AppendValue(PROP_LIST_STYLE_IMAGE, value);
      return PR_TRUE;
    }
  }

  // Provide default values
  if ((found & 1) == 0) {
    aDeclaration->AppendValue(PROP_LIST_STYLE_TYPE, 
                              nsCSSValue(NS_STYLE_LIST_STYLE_DISC, eCSSUnit_Enumerated));
  }
  if ((found & 2) == 0) {
    aDeclaration->AppendValue(PROP_LIST_STYLE_POSITION, 
                              nsCSSValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE, eCSSUnit_Enumerated));
  }
  if ((found & 4) == 0) {
    aDeclaration->AppendValue(PROP_LIST_STYLE_IMAGE, nsCSSValue(eCSSUnit_None));
  }
  return PR_TRUE;
}

PRBool CSSParserImpl::ParseMargin(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kMarginSideIDs[] = {
    PROP_MARGIN_TOP,
    PROP_MARGIN_RIGHT,
    PROP_MARGIN_BOTTOM,
    PROP_MARGIN_LEFT
  };
  return ParseBoxProperties(aErrorCode, aDeclaration, kMarginSideIDs);
}

PRBool CSSParserImpl::ParseMarks(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_MARKS, VARIANT_HOK, 
                   nsCSSProps::kPageMarksKTable, parsed)) {
    if (VARIANT_KEYWORD == parsed) {
      nsCSSValue  second;
      if (ParseEnum(aErrorCode, second, nsCSSProps::kPageMarksKTable)) {
        nsCSSValue  value;
        aDeclaration->GetValue(PROP_MARKS, value);
        value.SetIntValue(value.GetIntValue() | second.GetIntValue(), eCSSUnit_Enumerated);
        aDeclaration->AppendValue(PROP_MARKS, value);
      }
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseOutline(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kOutlineIDs[] = {
    PROP_OUTLINE_COLOR,
    PROP_OUTLINE_STYLE,
    PROP_OUTLINE_WIDTH
  };
  int found = ParseChoice(aErrorCode, aDeclaration, kOutlineIDs, 3);
  if (found == 0) {
    return PR_FALSE;
  }

  if (1 == found) {
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_OUTLINE_COLOR, value);
    if (eCSSUnit_Inherit == value.GetUnit()) {  // one inherit value, all inherit
      aDeclaration->AppendValue(PROP_OUTLINE_STYLE, value);
      aDeclaration->AppendValue(PROP_OUTLINE_WIDTH, value);
      return PR_TRUE;
    }
  }

  // Provide default values
  if ((found & 1) == 0) {
    aDeclaration->AppendValue(PROP_OUTLINE_COLOR, 
                              nsCSSValue(NS_STYLE_COLOR_INVERT, eCSSUnit_Enumerated));
  }
  if ((found & 2) == 0) {
    aDeclaration->AppendValue(PROP_OUTLINE_STYLE, nsCSSValue(eCSSUnit_None));
  }
  if ((found & 4) == 0) {
    aDeclaration->AppendValue(PROP_OUTLINE_WIDTH, 
                              nsCSSValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated));
  }
  return PR_TRUE;
}

PRBool CSSParserImpl::ParsePadding(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  static const PRInt32 kPaddingSideIDs[] = {
    PROP_PADDING_TOP,
    PROP_PADDING_RIGHT,
    PROP_PADDING_BOTTOM,
    PROP_PADDING_LEFT
  };
  return ParseBoxProperties(aErrorCode, aDeclaration, kPaddingSideIDs);
}

PRBool CSSParserImpl::ParsePause(PRInt32& aErrorCode,
                                 nsICSSDeclaration* aDeclaration)
{
  if (ParseProperty(aErrorCode, aDeclaration, PROP_PAUSE_BEFORE)) {
    if (! ParseProperty(aErrorCode, aDeclaration, PROP_PAUSE_AFTER)) {
      nsCSSValue  value;
      aDeclaration->GetValue(PROP_PAUSE_BEFORE, value);
      aDeclaration->AppendValue(PROP_PAUSE_AFTER, value);
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParsePlayDuring(PRInt32& aErrorCode,
                                      nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_PLAY_DURING, VARIANT_AHUO,
                   nsnull, parsed)) {
    if (VARIANT_URL == parsed) {
      nsCSSValue  value;
      if (ParseEnum(aErrorCode, value, nsCSSProps::kPlayDuringKTable)) {
        PRInt32 intValue = value.GetIntValue();
        if (ParseEnum(aErrorCode, value, nsCSSProps::kPlayDuringKTable)) {
          value.SetIntValue(intValue | value.GetIntValue(), eCSSUnit_Enumerated);
        }
        aDeclaration->AppendValue(PROP_PLAY_DURING_FLAGS, value);
      }
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseQuotes(PRInt32& aErrorCode,
                                  nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_QUOTES_OPEN, VARIANT_HOS, 
                   nsnull, parsed)) {
    if (VARIANT_STRING == parsed) {
      nsCSSContent* content = nsnull;
      aDeclaration->GetData(kCSSContentSID, (nsCSSStruct**)&content);
      if (nsnull != content) {
        nsCSSQuotes* quotes = content->mQuotes;
        while (nsnull != quotes) {
          // get close
          if (ParseVariant(aErrorCode, quotes->mClose, VARIANT_STRING, nsnull, parsed)) {
            if (ExpectEndProperty(aErrorCode, PR_TRUE)) {
              return PR_TRUE;
            }
            // look for another open
            nsCSSValue  value;
            if (ParseVariant(aErrorCode, value, VARIANT_STRING, nsnull, parsed)) {
              quotes->mNext = new nsCSSQuotes();
              quotes = quotes->mNext;
              if (nsnull != quotes) {
                quotes->mOpen = value;
                continue;
              }
            }
          }
          break;
        }
      }
      aDeclaration->AppendValue(PROP_QUOTES_OPEN, nsCSSValue());
      aDeclaration->AppendValue(PROP_QUOTES_CLOSE, nsCSSValue());
      return PR_FALSE;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseSize(PRInt32& aErrorCode, nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant (aErrorCode, aDeclaration, PROP_SIZE_WIDTH, VARIANT_AHKL,
                    nsCSSProps::kPageSizeKTable, parsed)) {
    if (VARIANT_LENGTH == parsed) {
      if (ParseVariant(aErrorCode, aDeclaration, PROP_SIZE_HEIGHT, VARIANT_LENGTH,
                       nsnull, parsed)) {
        return PR_TRUE;
      }
    }
    nsCSSValue  value;
    aDeclaration->GetValue(PROP_SIZE_WIDTH, value);
    aDeclaration->AppendValue(PROP_SIZE_HEIGHT, value);
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseTextDecoration(PRInt32& aErrorCode,
                                          nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_TEXT_DECORATION, VARIANT_HOK, 
                   nsCSSProps::kTextDecorationKTable, parsed)) {
    if (VARIANT_KEYWORD == parsed) {  // look for more keywords
      nsCSSValue  value;
      aDeclaration->GetValue(PROP_TEXT_DECORATION, value);
      PRInt32 intValue = value.GetIntValue();

      int index;
      for (index = 0; index < 3; index++) {
        if (ParseEnum(aErrorCode, value, nsCSSProps::kTextDecorationKTable)) {
          intValue |= value.GetIntValue();
        }
        else {
          break;
        }
      }
      value.SetIntValue(intValue, eCSSUnit_Enumerated);
      aDeclaration->AppendValue(PROP_TEXT_DECORATION, value);
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool CSSParserImpl::ParseTextShadow(PRInt32& aErrorCode,
                                      nsICSSDeclaration* aDeclaration)
{
  PRInt32 parsed;
  if (ParseVariant(aErrorCode, aDeclaration, PROP_TEXT_SHADOW_X, 
                   VARIANT_HC | VARIANT_LENGTH | VARIANT_NONE,
                   nsnull, parsed)) {
    if ((VARIANT_COLOR == parsed) || (VARIANT_LENGTH == parsed)) {
      nsCSSText*  text;
      aDeclaration->GetData(kCSSTextSID, (nsCSSStruct**)&text);
      if (nsnull != text) {
        nsCSSShadow*  shadow = text->mTextShadow;

        while (nsnull != shadow) {
          nsCSSValue  value;
          PRBool  haveColor = PR_FALSE;
          if (VARIANT_COLOR == parsed) {
            haveColor = PR_TRUE;
            shadow->mColor = shadow->mXOffset;
            shadow->mXOffset.Reset();
            if (ParseVariant(aErrorCode, value, VARIANT_LENGTH, nsnull, parsed)) {
              shadow->mXOffset = value;
            }
            else {
              break;
            }
          }

          if (ParseVariant(aErrorCode, value, VARIANT_LENGTH, nsnull, parsed)) {
            shadow->mYOffset = value;
          }
          else {
            break;
          }
          if (ParseVariant(aErrorCode, value, VARIANT_LENGTH, nsnull, parsed)) {
            shadow->mRadius = value;
          } // optional
          if (PR_FALSE == haveColor) {
            if (ParseVariant(aErrorCode, value, VARIANT_COLOR, nsnull, parsed)) {
              shadow->mColor = value;
            }
          }
          if (ExpectSymbol(aErrorCode, ',', PR_TRUE)) {
            shadow->mNext = new nsCSSShadow();
            shadow = shadow->mNext;
            if ((nsnull != shadow) && 
                ParseVariant(aErrorCode, value, VARIANT_COLOR | VARIANT_LENGTH,
                             nsnull, parsed)) {
              shadow->mXOffset = value;
            }
            else {
              break;
            }
          }
          else {
            if (ExpectEndProperty(aErrorCode, PR_TRUE)) {
              return PR_TRUE;
            }
            break;
          }
        }
      }
      nsCSSValue  nullValue;
      aDeclaration->AppendValue(PROP_TEXT_SHADOW_COLOR, nullValue);
      aDeclaration->AppendValue(PROP_TEXT_SHADOW_X, nullValue);
      aDeclaration->AppendValue(PROP_TEXT_SHADOW_Y, nullValue);
      aDeclaration->AppendValue(PROP_TEXT_SHADOW_RADIUS, nullValue);
      return PR_FALSE;
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

