/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Schema Validation.
 *
 * The Initial Developer of the Original Code is
 * IBM Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2004
 * IBM Corporation. All Rights Reserved.
 *
 * Contributor(s):
 *   Doron Rosenberg <doronr@us.ibm.com> (original author)
 *   Laurent Jouanneau <laurent@xulfr.org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// string includes
#include "nsStringAPI.h"
#include "nsUnicharUtils.h"

#include "nsISVSchema.h"
#include "nsSchemaValidator.h"
#include "nsSchemaValidatorUtils.h"
#include "nsISchemaValidatorRegexp.h"
#include "nsSchemaDuration.h"
#include "nsServiceManagerUtils.h"

#include <stdlib.h>
#include <math.h>
#include <errno.h>
#include <limits.h>
#include <float.h>
#include "prlog.h"
#include "prprf.h"
#include "prdtoa.h"

#ifdef PR_LOGGING
PRLogModuleInfo *gSchemaValidationUtilsLog = PR_NewLogModule("schemaValidation");

#define LOG(x) PR_LOG(gSchemaValidationUtilsLog, PR_LOG_DEBUG, x)
#define LOG_ENABLED() PR_LOG_TEST(gSchemaValidationUtilsLog, PR_LOG_DEBUG)
#else
#define LOG(x)
#endif

PRBool
nsSchemaValidatorUtils::IsValidSchemaInteger(const nsAString & aNodeValue,
                                             long *aResult, PRBool aOverFlowCheck)
{
  return !aNodeValue.IsEmpty() &&
         IsValidSchemaInteger(NS_ConvertUTF16toUTF8(aNodeValue).get(),
                              aResult, aOverFlowCheck);
}

// overloaded, for char* rather than nsAString
PRBool
nsSchemaValidatorUtils::IsValidSchemaInteger(const char* aString,
                                             long *aResult,
                                             PRBool aOverFlowCheck)
{
  PRBool isValid = PR_FALSE;

  if (*aString == 0)
    return PR_FALSE;

  char * pEnd;
  long intValue = strtol(aString, &pEnd, 10);

  if (aResult)
    *aResult = intValue;

  if (aOverFlowCheck) {
    isValid = (!((intValue == LONG_MAX || intValue == LONG_MIN) && errno == ERANGE))
              && *pEnd == '\0';
  } else {
    isValid = (*pEnd == '\0');
  }

  return isValid;
}

PRBool
nsSchemaValidatorUtils::IsValidSchemaDouble(const nsAString & aNodeValue,
                                            double *aResult)
{
  return !aNodeValue.IsEmpty() &&
         IsValidSchemaDouble(NS_ConvertUTF16toUTF8(aNodeValue).get(), aResult);
}

// overloaded, for char* rather than nsAString
PRBool
nsSchemaValidatorUtils::IsValidSchemaDouble(const char* aString,
                                            double *aResult)
{
  PRBool isValid = PR_TRUE;

  if (*aString == 0)
    return PR_FALSE;

  char * pEnd;
  double value = PR_strtod(aString, &pEnd);

  // If the end pointer desn't point at the end, it wasn't a true double (could
  // be INF, -INF or NaN though)
  if (*pEnd != '\0') {
    nsCAutoString temp(aString);

    // doubles may be INF, -INF or NaN
    if (temp.EqualsLiteral("INF")) {
      value = DBL_MAX;
    } else if (temp.EqualsLiteral("-INF")) {
      value = - DBL_MAX;
    } else if (!temp.EqualsLiteral("NaN")) {
      isValid = PR_FALSE;
    }
  }

  if (aResult)
    *aResult = value;

  return isValid;
}

PRBool
nsSchemaValidatorUtils::ParseDateTime(const nsAString & aNodeValue,
                                      nsSchemaDateTime *aResult)
{
  PRBool isValid = PR_FALSE;

  nsAutoString datetimeString(aNodeValue);

  aResult->date.isNegative = (datetimeString.First() == '-');

  /*
    http://www.w3.org/TR/xmlschema-2/#dateTime
    (-)CCYY-MM-DDThh:mm:ss(.sss...)
      then either: Z
      or [+/-]hh:mm
  */

  // first handle the date part
  // search for 'T'

  LOG(("  Validating DateTime:"));

  int findString = datetimeString.FindChar('T');

  // if no T, invalid
  if (findString >= 0) {
    // we get the date part (from 0 to before 'T')
    isValid = ParseSchemaDate(Substring(aNodeValue, 0, findString), PR_FALSE, &aResult->date);

    if (isValid) {
      // we get the time part (from after the 'T' till the end)
      isValid = ParseSchemaTime(
                  Substring(aNodeValue, findString + 1, aNodeValue.Length()),
                  &aResult->time);
    }
  }

  return isValid;
}

PRBool
nsSchemaValidatorUtils::ParseSchemaDate(const nsAString & aStrValue,
                                        PRBool aAllowTimeZone,
                                        nsSchemaDate *aDate)
{
  PRBool isValid = PR_FALSE;

  /*
    http://www.w3.org/TR/xmlschema-2/#date
    (-)CCYY-MM-DD
    then optionally: Z
      or [+/-]hh:mm
  */

  const PRUnichar *start, *end, *buffStart;
  aStrValue.BeginReading(&start, &end);
  aStrValue.BeginReading(&buffStart);
  PRUint32 state = 0;
  PRUint32 buffLength = 0;
  PRBool done = PR_FALSE;
  PRUnichar currentChar;

  nsAutoString year;
  char month[3] = "";
  char day[3] = "";
  char timezoneHour[3] = "";
  char timezoneMinute[3] = "";

  // if year is negative, skip it
  if (aStrValue.First() == '-') {
    start++;
    buffStart = start;
  }

  while ((start != end) && !done) {
    currentChar = *start++;
    switch (state) {
      case 0: {
        // year
        if (currentChar == '-') {
          if (buffLength < 4) {
            done = PR_TRUE;
          } else {
            year.Assign(Substring(buffStart, --start));
            state = 1;
            buffLength = 0;
            buffStart = ++start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          buffLength++;
        }
        break;
      }

      case 1: {
        // month
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == '-') {
          if (strcmp(month, "12") == 1 || buffLength < 2) {
            done = PR_TRUE;
          } else {
            state = 2;
            buffLength = 0;
            buffStart = start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else
            month[buffLength] = currentChar;
          buffLength++;
        }
        break;
      }

      case 2: {
        // day
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == 'Z') {
          if (aAllowTimeZone) {
            if ((start == end) && (buffLength == 2) && (strcmp(day, "31") < 1)) {
            isValid = PR_TRUE;
            }
          }

          done = PR_TRUE;
        } else if ((currentChar == '+') || (currentChar == '-')) {
          // timezone
          if (aAllowTimeZone) {
            state = 3;
            buffLength = 0;
            buffStart = start;
          } else {
            // no timezones allowed
            done = PR_TRUE;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else {
            day[buffLength] = currentChar;
          }
          buffLength++;

          // are we at the end?
          if (start == end && buffLength == 2) {
            isValid = PR_TRUE;
            done = PR_TRUE;
          }
        }
        break;
      }

      case 3: {
        // timezone hh:mm
        if (end-buffStart == 5) {
          isValid = ParseSchemaTimeZone(Substring(buffStart, end), timezoneHour,
                                        timezoneMinute);
        }

        done = PR_TRUE;
        break;
      }
    }
  }

  if (isValid) {
    char * pEnd;

    PRUint32 yearval = strtoul(NS_ConvertUTF16toUTF8(year).get(), &pEnd, 10);
    if (yearval == 0 || yearval == ULONG_MAX) {
      isValid = PR_FALSE;
    } else {
      PRUint8 monthval = strtol(month, &pEnd, 10);
      if (monthval < 1 || monthval > 12) {
        isValid = PR_FALSE;
      } else {
        PRUint8 dayval = strtol(day, &pEnd, 10);
        if (dayval < 1) {
          isValid = PR_FALSE;
        } else {
          // check for leap years
          PRUint8 maxDay = GetMaximumDayInMonthFor(yearval, monthval);
          if (maxDay >= dayval) {
            aDate->year = yearval;

            // month/day are validated in the parsing code above
            aDate->month = monthval;
            aDate->day = dayval;
          } else {
            isValid = PR_FALSE;
          }
        }
      }
    }
  }

  LOG(("     Date is %s", ((isValid) ? "Valid" : "Not Valid")));

  return isValid;
}

// parses a string as a schema time type and returns the parsed
// hour/minute/second/fraction seconds as well as if its a valid
// schema time type.
PRBool
nsSchemaValidatorUtils::ParseSchemaTime(const nsAString & aStrValue,
                                        nsSchemaTime* aTime)
{
  PRBool isValid = PR_FALSE;

  // time looks like this: HH:MM:SS(.[S]+)(+/-HH:MM)

  char hour[3] = "";
  char minute[3] = "";
  char second[3] = "";

  char timezoneHour[3] = "";
  char timezoneMinute[3] = "";
  // we store the fraction seconds because PR_ExplodeTime seems to skip them.
  nsAutoString usec;

  const PRUnichar *start, *end, *buffStart;
  aStrValue.BeginReading(&start, &end);
  aStrValue.BeginReading(&buffStart);
  PRUint32 state = 0;
  PRUint32 buffLength = 0;
  PRBool done = PR_FALSE;
  PRUnichar currentChar;
  PRUnichar tzSign = PRUnichar(' ');

  while ((start != end) && !done) {
    currentChar = *start++;

    switch (state) {
      case 0: {
        // hour
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == ':') {
          // validate hour
          if (strcmp(hour, "24") == 1) {
            done = PR_TRUE;
          } else {
            state = 1;
            buffLength = 0;
            buffStart = start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else
            hour[buffLength] = currentChar;
          buffLength++;
        }
        break;
      }

      case 1: {
        // minute
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == ':') {
          // validate minute
          if (strcmp(minute, "59") == 1) {
            done = PR_TRUE;
          } else {
            state = 2;
            buffLength = 0;
            buffStart = start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else
            minute[buffLength] = currentChar;
          buffLength++;
        }
        break;
      }

      case 2: {
        // seconds
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == 'Z') {
          // if its Z, has to be the last character
          if ((start == end) && (strcmp(second, "59") != 1)) {
            isValid = PR_TRUE;
          }
          done = PR_TRUE;
          tzSign = currentChar;
        } else if ((currentChar == '+') || (currentChar == '-')) {
          // timezone exists
          if (strcmp(second, "59") == 1) {
            done = PR_TRUE;
          } else {
            state = 4;
            buffLength = 0;
            buffStart = start;
            tzSign = currentChar;
          }
        } else if (currentChar == '.') {
          // fractional seconds exist
          if (strcmp(second, "59") == 1) {
            done = PR_TRUE;
          } else {
            state = 3;
            buffLength = 0;
            buffStart = start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else {
            second[buffLength] = currentChar;
            if (start == end) {
              isValid = PR_TRUE;
              done = PR_TRUE;
            }
          }
          buffLength++;
        }

        break;
      }

      case 3: {
        // fractional seconds

        if (currentChar == 'Z') {
          // if its Z, has to be the last character
          if (start == end)
            isValid = PR_TRUE;
          else
            done = PR_TRUE;
          tzSign = currentChar;
          usec.Assign(Substring(buffStart, start - 1));
        } else if ((currentChar == '+') || (currentChar == '-')) {
          // timezone exists
          usec.Assign(Substring(buffStart, start - 1));
          state = 4;
          buffLength = 0;
          buffStart = start;
          tzSign = currentChar;
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else if (start == end) {
            usec.Assign(Substring(buffStart, end));
            isValid = PR_TRUE;
            done = PR_TRUE;
          }
          buffLength++;
        }
        break;
      }

      case 4: {
        // timezone hh:mm
       if (end-buffStart == 5)
         isValid = ParseSchemaTimeZone(Substring(buffStart, end), timezoneHour,
                                       timezoneMinute);

       done = PR_TRUE;
       break;
      }
    }
  }

  if (isValid) {
    char * pEnd;

    PRUint32 usecval = strtoul(NS_ConvertUTF16toUTF8(usec).get(), &pEnd, 10);
    // be carefull, empty usec returns 0
    if (!usec.IsEmpty() && (usecval == 0 || usecval == ULONG_MAX)) {
      isValid = PR_FALSE;
    } else {
      aTime->hour = strtol(hour, &pEnd, 10);
      aTime->minute = strtol(minute, &pEnd, 10);
      aTime->second = strtol(second, &pEnd, 10);
      aTime->millisecond = usecval;

      if (tzSign == '+')
        aTime->tzIsNegative = PR_FALSE;
      else
        aTime->tzIsNegative = PR_TRUE;

      aTime->tzhour = strtol(timezoneHour, &pEnd, 10);
      aTime->tzminute = strtol(timezoneMinute, &pEnd, 10);
    }
  }

  LOG(("     Time is %s", ((isValid) ? "Valid" : "Not Valid")));

  return isValid;
}

PRBool
nsSchemaValidatorUtils::ParseSchemaTimeZone(const nsAString & aStrValue,
                                            char *rv_tzhour, char *rv_tzminute)
{
  PRBool isValid = PR_FALSE;
  char timezoneHour[3] = "";
  char timezoneMinute[3] = "";

  const PRUnichar *start, *end, *buffStart;
  aStrValue.BeginReading(&start, &end);
  aStrValue.BeginReading(&buffStart);
  PRUint32 state = 0;
  PRUint32 buffLength = 0;
  PRBool done = PR_FALSE;
  PRUnichar currentChar;

  LOG(("\n    Validating TimeZone"));

  while ((start != end) && !done) {
    currentChar = *start++;

    switch (state) {
      case 0: {
        // hour
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (currentChar == ':') {
          timezoneHour[2] = '\0';
          if (strcmp(timezoneHour, "24") == 1) {
            done = PR_TRUE;
          } else {
            state = 1;
            buffLength = 0;
            buffStart = start;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0'))
            done = PR_TRUE;
          else {
            timezoneHour[buffLength] = currentChar;
          }
          buffLength++;
        }
        break;
      }

      case 1: {
        // minute
        if (buffLength > 2) {
          done = PR_TRUE;
        } else if (start == end) {
          if (buffLength == 1) {
            if ((currentChar > '9') || (currentChar < '0')) {
              done = PR_TRUE;
            } else {
              timezoneMinute[buffLength] = currentChar;

              timezoneMinute[2] = '\0';
              if (strcmp(timezoneMinute, "59") == 1) {
                done = PR_TRUE;
              } else {
                isValid = PR_TRUE;
              }
            }
          } else {
            done = PR_FALSE;
          }
        } else {
          // has to be a numerical character or else abort
          if ((currentChar > '9') || (currentChar < '0')) {
            done = PR_TRUE;
          } else {
            timezoneMinute[buffLength] = currentChar;
          }
          buffLength++;
        }
        break;
      }

    }
  }

  if (isValid) {
    strncpy(rv_tzhour, timezoneHour, 3);
    strncpy(rv_tzminute, timezoneMinute, 3);
  }

  return isValid;
}

/*
 -1 - aDateTime1 < aDateTime2
  0 - equal
  1 - aDateTime1 > aDateTime2
*/
int
nsSchemaValidatorUtils::CompareDateTime(nsSchemaDateTime aDateTime1,
                                        nsSchemaDateTime aDateTime2)
{
  int result;

  nsSchemaDateTime dateTime1, dateTime2;
  AddTimeZoneToDateTime(aDateTime1, &dateTime1);
  AddTimeZoneToDateTime(aDateTime2, &dateTime2);

  if (!dateTime1.date.isNegative && dateTime2.date.isNegative) {
    // positive year is always bigger than negative year
    result = 1;
  } else if (dateTime1.date.isNegative && !dateTime2.date.isNegative) {
    result = -1;
  } else {
    result = CompareDate(dateTime1.date, dateTime2.date);

    if (result == 0)
      result = CompareTime(dateTime1.time, dateTime2.time);

    if (dateTime1.date.isNegative && dateTime2.date.isNegative) {
      // -20 is smaller than -21
      if (result == -1)
        result = 1;
      else if (result == 1)
        result = -1;
    }
  }

  return result;
}

/*
 -1 - aDateTime1 < aDateTime2
  0 - equal
  1 - aDateTime1 > aDateTime2
*/
int
nsSchemaValidatorUtils::CompareDate(nsSchemaDate aDate1, nsSchemaDate aDate2)
{
  int result;

  if (aDate1.year < aDate2.year) {
    result = -1;
  } else if (aDate1.year > aDate2.year) {
    result = 1;
  } else {
    if (aDate1.month < aDate2.month) {
      result = -1;
    } else if (aDate1.month > aDate2.month) {
      result = 1;
    } else {
      if (aDate1.day < aDate2.day) {
        result = -1;
      } else if (aDate1.day > aDate2.day) {
        result = 1;
      } else {
        result = 0;
      }
    }
  }

  return result;
}

/*
 -1 - aDateTime1 < aDateTime2
  0 - equal
  1 - aDateTime1 > aDateTime2
*/
int
nsSchemaValidatorUtils::CompareTime(nsSchemaTime aTime1, nsSchemaTime aTime2)
{
  int result;

  if (aTime1.hour < aTime2.hour) {
    result = -1;
  } else if (aTime1.hour > aTime2.hour) {
    result = 1;
  } else {
    if (aTime1.minute < aTime2.minute) {
      result = -1;
    } else if (aTime1.minute > aTime2.minute) {
      result = 1;
    } else {
      if (aTime1.second < aTime2.second) {
        result = -1;
      } else if (aTime1.second > aTime2.second) {
        result = 1;
      } else {
        if (aTime1.millisecond < aTime2.millisecond) {
          result = -1;
        } else if (aTime1.millisecond > aTime2.millisecond) {
          result = 1;
        } else {
          result = 0;
        }
      }
    }
  }

  return result;
}

void
nsSchemaValidatorUtils::AddTimeZoneToDateTime(nsSchemaDateTime aDateTime,
                                              nsSchemaDateTime* aDestDateTime)
{
  // With timezones, you subtract the timezone difference. So for example,
  // 2002-10-10T12:00:00+05:00 is 2002-10-10T07:00:00Z
  PRUint32 year = aDateTime.date.year;
  PRUint8 month = aDateTime.date.month;
  PRUint8 day = aDateTime.date.day;
  int hour = aDateTime.time.hour;
  int minute = aDateTime.time.minute;
  PRUint8 second = aDateTime.time.second;
  PRUint32 millisecond = aDateTime.time.millisecond;

  if (aDateTime.time.tzIsNegative) {
    hour = hour + aDateTime.time.tzhour;
    minute = minute + aDateTime.time.tzminute;
  } else {
    hour = hour - aDateTime.time.tzhour;
    minute = minute - aDateTime.time.tzminute;
  }

  div_t divresult;

  if (minute > 59) {
    divresult = div(minute, 60);
    hour += divresult.quot;
    minute = divresult.rem;
  } else if (minute < 0) {
    minute = 60 + minute;
    hour--;
  }

  // hour
  if (hour == 24 && (minute > 0 || second > 0)) {
    // can only be 24:0:0 - need to increment day
    day++;
    hour = 0;
  } else if (hour > 23) {
    divresult = div(hour, 24);
    day += divresult.quot;
    hour = divresult.rem;
  } else if (hour < 0) {
    hour = 24 + hour;
    day--;
  }

  // day
  if (day == 0) {
    // if day is 0, go back a month and make sure we handle month 0 (ie back a year).
    month--;

    if (month == 0) {
      month = 12;
      year--;
    }

    day = GetMaximumDayInMonthFor(month, year);
  } else {
    int maxDay = GetMaximumDayInMonthFor(month, year);
    while (day > maxDay) {
      day -= maxDay;
      month++;

      // since we are a valid datetime, month has to be 12 before the ++, so will
      // be 13
      if (month == 13) {
        month = 1;
        year++;
      }

      maxDay = GetMaximumDayInMonthFor(month, year);
    }
  }

  aDestDateTime->date.year = year;
  aDestDateTime->date.month = month;
  aDestDateTime->date.day = day;
  aDestDateTime->date.isNegative = aDateTime.date.isNegative;
  aDestDateTime->time.hour = hour;
  aDestDateTime->time.minute = minute;
  aDestDateTime->time.second = second;
  aDestDateTime->time.millisecond = millisecond;
  aDestDateTime->time.tzIsNegative = aDateTime.time.tzIsNegative;
}

void
nsSchemaValidatorUtils::GetMonthShorthand(PRUint8 aMonth, nsACString & aReturn)
{
  aReturn.Assign(monthShortHand[aMonth - 1].shortHand);
}

/*
 -1 - aYearMonth1 < aYearMonth2
  0 - equal
  1 - aYearMonth1 > aYearMonth2
*/
int
nsSchemaValidatorUtils::CompareGYearMonth(nsSchemaGYearMonth aYearMonth1,
                                          nsSchemaGYearMonth aYearMonth2)
{
  int rv;

  if (aYearMonth1.gYear.year > aYearMonth2.gYear.year) {
    rv = 1;
  } else if (aYearMonth1.gYear.year < aYearMonth2.gYear.year) {
    rv = -1;
  } else {
    // both have the same year
    if (aYearMonth1.gMonth.month > aYearMonth2.gMonth.month)
      rv = 1;
    else if (aYearMonth1.gMonth.month < aYearMonth2.gMonth.month)
      rv = -1;
    else
      rv = 0;
  }

  return rv;
}

/*
 -1 - aMonthDay1 < aMonthDay2
  0 - equal
  1 - aMonthDay1 > aMonthDay2
*/
int
nsSchemaValidatorUtils::CompareGMonthDay(nsSchemaGMonthDay aMonthDay1,
                                         nsSchemaGMonthDay aMonthDay2)
{
  int rv;

  if (aMonthDay1.gMonth.month > aMonthDay2.gMonth.month) {
    rv = 1;
  } else if (aMonthDay1.gMonth.month < aMonthDay2.gMonth.month) {
    rv = -1;
  } else {
    // both have the same year
    if (aMonthDay1.gDay.day > aMonthDay2.gDay.day)
      rv = 1;
    else if (aMonthDay1.gDay.day < aMonthDay2.gDay.day)
      rv = -1;
    else
      rv = 0;
  }

  return rv;
}

PRBool
nsSchemaValidatorUtils::ParseSchemaDuration(const nsAString & aStrValue,
                                            nsISchemaDuration **aDuration)
{
  PRBool isValid = PR_FALSE;

  const PRUnichar *start, *end, *buffStart;
  aStrValue.BeginReading(&start, &end);
  aStrValue.BeginReading(&buffStart);
  PRUint32 state = 0;
  PRUint32 buffLength = 0;
  PRBool done = PR_FALSE;
  PRUnichar currentChar;

  PRBool isNegative = PR_FALSE;

  // make sure leading P is present.  Take negative durations into consideration.
  if (*start == '-') {
    ++start;
    if (*start != 'P') {
      return PR_FALSE;
    } else {
      isNegative = PR_TRUE;
      buffStart = ++start;
    }
  } else { 
    if (*start != 'P')
      return PR_FALSE;
    else
      ++start;
  }

  nsAutoString parseBuffer;
  PRBool timeSeparatorFound = PR_FALSE;

  // designators may not repeat, so keep track of those we find.
  PRBool yearFound = PR_FALSE;
  PRBool monthFound = PR_FALSE;
  PRBool dayFound = PR_FALSE;
  PRBool hourFound = PR_FALSE;
  PRBool minuteFound = PR_FALSE;
  PRBool secondFound = PR_FALSE;
  PRBool fractionSecondFound = PR_FALSE;

  PRUint32 year = 0;
  PRUint32 month = 0;
  PRUint32 day = 0;
  PRUint32 hour = 0;
  PRUint32 minute = 0;
  PRUint32 second = 0;
  double fractionSecond = 0;

  /* durations look like this:
       (-)PnYnMnDTnHnMn(.n)S
     - P is required, plus sign is invalid
     - order is important, so day after year is invalid (PnDnY)
     - Y,M,D,H,M,S are called designators
     - designators are not allowed without a number before them
     - T is the date/time seperator and is only allowed given a time part
  */

  while ((start != end) && !done) {
    currentChar = *start++;
    // not a number - so it has to be a type designator (YMDTHMS)
    // it can also be |.| for fractional seconds
    if ((currentChar > '9') || (currentChar < '0')) {
      // first check if the buffer is bigger than what long can store
      // which is 11 digits, as we convert to long
      if ((parseBuffer.Length() == 10) &&
          (CompareStrings(parseBuffer, NS_LITERAL_STRING("2147483647")) == 1)) {
        done = PR_TRUE;
      } else if (currentChar == 'Y') {
        if (yearFound || monthFound || dayFound || timeSeparatorFound) {
          done = PR_TRUE;
        } else {
          state = 0;
          yearFound = PR_TRUE;
        }
      } else if (currentChar == 'M') {
        // M is used twice - Months and Minutes
        if (!timeSeparatorFound)
          if (monthFound || dayFound || timeSeparatorFound) {
            done = PR_TRUE;
          } else {
            state = 1;
            monthFound = PR_TRUE;
          }
        else {
          if (!timeSeparatorFound) {
            done = PR_TRUE;
          } else {
            if (minuteFound || secondFound) {
              done = PR_TRUE;
            } else {
              state = 4;
              minuteFound = PR_TRUE;
            }
          }
        }
      } else if (currentChar == 'D') {
        if (dayFound || timeSeparatorFound) {
          done = PR_TRUE;
        } else {
          state = 2;
          dayFound = PR_TRUE;
        }
      } else if (currentChar == 'T') {
        // can't have the time seperator more than once
        if (timeSeparatorFound)
          done = PR_TRUE;
        else
          timeSeparatorFound = PR_TRUE;
      } else  if (currentChar == 'H') {
        if (!timeSeparatorFound || hourFound || secondFound ) {
          done = PR_TRUE;
        } else {
          state = 3;
          hourFound = PR_TRUE;
        }
      } else if (currentChar == 'S') {
        if (!timeSeparatorFound)
          done = PR_TRUE;
        else
          if (secondFound) {
            done = PR_TRUE;
          } else {
            state = 5;
            secondFound = PR_TRUE;
          }
      } else if (currentChar == '.') {
        // fractional seconds
        if (fractionSecondFound) {
          done = PR_TRUE;
        } else {
          parseBuffer.Append(currentChar);
          buffLength++;
          fractionSecondFound = PR_TRUE;
        }
      } else {
        done = PR_TRUE;
      }

      // if its a designator and no buffer, invalid per spec.  Rule doesn't apply
      // if T or '.' (fractional seconds), as we need to parse on for those.
      // so P200YM is invalid as there is no number before M
      if ((currentChar != 'T') && (currentChar != '.') && (parseBuffer.Length() == 0)) {
        done = PR_TRUE;
      } else if ((currentChar == 'T') && (start == end)) {
        // if 'T' is found but no time data after it, invalid
        done = PR_TRUE;
      }

      if (!done && (currentChar != 'T') && (currentChar != '.')) {
        long temp;
        switch (state) {
          case 0: {
            // years
            if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
              done = PR_TRUE;
            else
              year = temp;
            break;
          }

          case 1: {
            // months
            if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
              done = PR_TRUE;
            else
              month = temp;
            break;
          }

          case 2: {
            // days
            if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
              done = PR_TRUE;
            else
              day = temp;
            break;
          }

          case 3: {
            // hours
            if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
              done = PR_TRUE;
            else
              hour = temp;
            break;
          }

          case 4: {
            // minutes
            if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
              done = PR_TRUE;
            else
              minute = temp;
            break;
          }

          case 5: {
            // seconds - we have to handle optional fraction seconds as well
            if (fractionSecondFound) {
              double temp2, intpart;

              if (!IsValidSchemaDouble(parseBuffer, &temp2)) {
                done = PR_TRUE;
              } else {
                fractionSecond = modf(temp2, &intpart);
                second = static_cast<PRUint32>(intpart);
              }
            } else {
              if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
                done = PR_TRUE;
              else
                second = temp;
            }
            break;
          }
        }
      }

      // clear buffer unless we are at fraction seconds, since we want to parse
      // the seconds and fraction seconds into the same buffer.
      if (!fractionSecondFound) {
        parseBuffer.AssignLiteral("");
        buffLength = 0;
      }
    } else {
      if (buffLength > 11) {
        done = PR_TRUE;
      } else {
        parseBuffer.Append(currentChar);
        buffLength++;
      }
    }
  }

  if ((start == end) && (!done)) {
    isValid = PR_TRUE;
  }

  if (isValid) {
    nsISchemaDuration* duration = new nsSchemaDuration(year, month, day, hour,
                                                       minute, second,
                                                       fractionSecond,
                                                       isNegative);

    *aDuration = duration;
    NS_IF_ADDREF(*aDuration);
  }

  return isValid;
}

/* compares 2 strings that contain integers.
   Schema Integers have no limit, thus converting the strings
   into numbers won't work.

 -1 - aString1 < aString2
  0 - equal
  1 - aString1 > aString2

 */
int
nsSchemaValidatorUtils::CompareStrings(const nsAString & aString1,
                                       const nsAString & aString2)
{
  int rv;

  PRBool isNegative1 = (aString1.First() == PRUnichar('-'));
  PRBool isNegative2 = (aString2.First() == PRUnichar('-'));

  if (isNegative1 && !isNegative2) {
    // negative is always smaller than positive
    return -1;
  } else if (!isNegative1 && isNegative2) {
    // positive is always bigger than negative
    return 1;
  }

  const PRUnichar *start1, *start2, *end1, *end2;
  aString1.BeginReading(&start1, &end1);
  aString2.BeginReading(&start2, &end2);

  // skip negative sign
  if (isNegative1)
    start1++;

  if (isNegative2)
    start2++;

  // jump over leading zeros
  PRBool done = PR_FALSE;
  while ((start1 != end1) && !done) {
    if (*start1 != '0')
      done = PR_TRUE;
    else
      ++start1;
  }

  done = PR_FALSE;
  while ((start2 != end2) && !done) {
    if (*start2 != '0')
      done = PR_TRUE;
    else
      ++start2;
  }

  nsAutoString compareString1, compareString2;
  compareString1.Assign(Substring(start1, end1));
  compareString2.Assign(Substring(start2, end2));

  // after removing leading 0s, check if they are the same
  if (compareString1.Equals(compareString2)) {
    return 0;
  }

  if (compareString1.Length() > compareString2.Length())
    rv = 1;
  else if (compareString1.Length() < compareString2.Length())
    rv = -1;
  else
    rv = strcmp(NS_ConvertUTF16toUTF8(compareString1).get(),
                NS_ConvertUTF16toUTF8(compareString2).get());

  // 3>2, but -2>-3
  if (isNegative1 && isNegative2) {
    if (rv == 1)
      rv = -1;
    else
      rv = 1;
  }
  return rv;
}

// For xsd:duration support, the the maximum day for a month/year combo as
// defined in http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes.
int
nsSchemaValidatorUtils::GetMaximumDayInMonthFor(PRUint32 aYearValue, PRUint8 aMonthValue)
{
  PRUint8 maxDay = 28;
  PRUint8 month = ((aMonthValue - 1) % 12) + 1;
  PRUint32 year = aYearValue + ((aMonthValue - 1) / 12);

  /*
    Return Value      Condition
    31                month is either 1, 3, 5, 7, 8, 10, 12
    30                month is either 4, 6, 9, 11
    29                month is 2 AND either ((year % 4 == 0) AND (year % 100 != 0))
                                            OR (year % 400 == 0)
    28                Otherwise
  */

  if ((month == 1) || (month == 3) || (month == 5) || (month == 7) ||
      (month == 8) || (month == 10) || (month == 12))
    maxDay = 31;
  else if ((month == 4) || (month == 6) || (month == 9) || (month == 11))
    maxDay = 30;
  else if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
    maxDay = 29;

  return maxDay;
}

/*
 * compares 2 durations using the algorithm defined in
 * http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes by adding them to
 * the 4 datetimes specified in http://w3.org/TR/xmlschema-2/#duration-order.
 * If not all 4 result in x<y, we return indeterminate per
 * http://www.w3.org/TR/xmlschema-2/#facet-comparison-for-durations.
 *
 *  0 - aDuration1 < aDuration2
 *  1 - indeterminate
 *
 */
int
nsSchemaValidatorUtils::CompareDurations(nsISchemaDuration *aDuration1,
                                         nsISchemaDuration *aDuration2)
{
  int cmp = 0, tmpcmp, i = 0;

  nsSchemaDateTime dateTime, newDateTime1, newDateTime2;

  char* datetimeArray[] = { "1696-09-01T00:00:00Z", "1697-02-01T00:00:00Z",
                            "1903-03-01T00:00:00Z", "1903-07-01T00:00:00Z" };
  PRBool indeterminate = PR_FALSE;

  while (!indeterminate && (i < 4)) {
    ParseDateTime(NS_ConvertASCIItoUTF16(datetimeArray[i]), &dateTime);

    AddDurationToDatetime(dateTime, aDuration1, &newDateTime1);
    AddDurationToDatetime(dateTime, aDuration2, &newDateTime2);

    tmpcmp = CompareDateTime(newDateTime1, newDateTime2);

    if (i > 0) {
      if (tmpcmp != cmp || tmpcmp > -1) {
        indeterminate = PR_TRUE;
      }
    }

    cmp = tmpcmp;
    ++i;
  }

  return indeterminate ? 1 : 0;
}

/* 
 * This method implements the algorithm described at:
 *    http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
 */
void
nsSchemaValidatorUtils::AddDurationToDatetime(nsSchemaDateTime aDatetime,
                                              nsISchemaDuration *aDuration,
                                              nsSchemaDateTime* aResultDateTime)
{
  // first handle months
  PRUint32 temp = 0;
  aDuration->GetMonths(&temp);
  temp += aDatetime.date.month;
  aResultDateTime->date.month = ((temp - 1) % 12) + 1;
  PRInt32 carry = (temp - 1) / 12;

  // years
  aDuration->GetYears(&temp);
  aResultDateTime->date.year = aDatetime.date.year + carry + temp;

  // reset the carry
  carry = 0;

  /* fraction seconds
   * XXX: Since the 4 datetimes we add durations to don't have fraction seconds
   *      we can just add the duration's fraction second (stored as an float),
   *      which will be < 1.0.
   */
  double dblValue;
  aDuration->GetFractionSeconds(&dblValue);
  aResultDateTime->time.millisecond = (int) dblValue * 1000000;

  // seconds
  aDuration->GetSeconds(&temp);
  temp += aDatetime.time.second + carry;
  aResultDateTime->time.second = temp % 60;
  carry = temp / 60;

  // minutes
  aDuration->GetMinutes(&temp);
  temp += aDatetime.time.minute + carry;
  aResultDateTime->time.minute = temp % 60;
  carry = temp / 60;

  // hours
  aDuration->GetHours(&temp);
  temp += aDatetime.time.hour + carry;
  aResultDateTime->time.hour = temp % 24;
  carry = temp / 24;

  // days
  int maxDay = GetMaximumDayInMonthFor(aResultDateTime->date.year,
                                       aResultDateTime->date.month);
  int tempDays = 0;
  if (aDatetime.date.day > maxDay)
    tempDays = maxDay;
  else if (aDatetime.date.day < 1)
    tempDays = 1;
  else
    tempDays = aDatetime.date.day;

  aDuration->GetDays(&temp);
  aResultDateTime->date.day = tempDays + carry + temp;

  PRBool done = PR_FALSE;
  while (!done) {
    maxDay = GetMaximumDayInMonthFor(aResultDateTime->date.year,
                                     aResultDateTime->date.month);
    if (aResultDateTime->date.day < 1) {
      aResultDateTime->date.day +=
        GetMaximumDayInMonthFor(aResultDateTime->date.year,
                                aResultDateTime->date.month - 1);
      carry = -1;
    } else if (aResultDateTime->date.day > maxDay) {
      aResultDateTime->date.day -= maxDay;
      carry = 1;
    } else {
      done = PR_TRUE;
    }

    if (!done) {
      temp = aResultDateTime->date.month + carry;
      aResultDateTime->date.month = ((temp - 1) % 12) + 1;
      aResultDateTime->date.year += (temp - 1) / 12;
    }
  }

  // copy over negative and tz data
  aResultDateTime->date.isNegative = aDatetime.date.isNegative;

  aResultDateTime->time.tzIsNegative = aDatetime.time.tzIsNegative;
  aResultDateTime->time.tzhour = aDatetime.time.tzhour;
  aResultDateTime->time.tzminute = aDatetime.time.tzminute;

  LOG(("\n  New datetime is %d-%d-%d %d:%d:%d\n", aResultDateTime->date.day,
    aResultDateTime->date.month, aResultDateTime->date.year,
    aResultDateTime->time.hour, aResultDateTime->time.minute,
    aResultDateTime->time.second));
}

// http://www.w3.org/TR/xmlschema-2/#normalizedString
PRBool
nsSchemaValidatorUtils::IsValidSchemaNormalizedString(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;
  nsAutoString string(aStrValue);

  // may not contain carriage return, line feed nor tab characters
  if (FindCharInSet(string, "\t\r\n") == kNotFound)
    isValid = PR_TRUE;
  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#token
PRBool
nsSchemaValidatorUtils::IsValidSchemaToken(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;
  nsAutoString string(aStrValue);

  // may not contain carriage return, line feed, tab characters.  Also can
  // not contain leading/trailing whitespace and no internal sequences of
  // two or more spaces.
  if ((FindCharInSet(string, "\t\r\n") == kNotFound) &&
      (string.Find(NS_LITERAL_STRING("  ")) == kNotFound) &&
      (string.First() != ' ') &&
      (string.CharAt(string.Length() - 1) != ' '))
    isValid = PR_TRUE;

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#language
PRBool
nsSchemaValidatorUtils::IsValidSchemaLanguage(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // pattern is defined in spec
  nsAutoString pattern;
  pattern.AssignLiteral("[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*");

  nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
  nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
  NS_ENSURE_SUCCESS(rv, rv);

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#name
PRBool
nsSchemaValidatorUtils::IsValidSchemaName(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // xsd:Name is restriction on xsd:token
  if (IsValidSchemaToken(aStrValue)) {
    /* http://www.w3.org/TR/2000/WD-xml-2e-20000814
    [4]	NameChar ::=  Letter | Digit | '.' | '-' | '_' | ':' |
                      CombiningChar | Extender
    [5]	Name     ::= (Letter | '_' | ':') ( NameChar)*
    */
    // XXX Need to handling CombiningChar and Extender as well
    // XXX Additional Unicode testing needed?
    nsAutoString pattern;
    pattern.AssignLiteral("^[a-zA-Z_:][\\w\\.\\-:]*$");
    nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
    nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#ncname
PRBool
nsSchemaValidatorUtils::IsValidSchemaNCName(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;
  
  // xsd:NCNAME is a restriction on xsd:Name
  if (IsValidSchemaToken(aStrValue)) {
    /* http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName
      NCNameChar ::=  Letter | Digit | '.' | '-' | '_' |
                      CombiningChar | Extender
      NCName     ::= (Letter | '_') (NCNameChar)*
     */
    nsAutoString pattern;
    // XXX Need to handle Combining|Extender and Unicode Letters
    // xsd:Name minus the ":"
    pattern.AssignLiteral("^[a-zA-Z_][\\w\\.\\-]*$");
    nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
    nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#id
PRBool
nsSchemaValidatorUtils::IsValidSchemaID(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // xsd:ID is a restriction of xsd:NCNAME
  if (IsValidSchemaNCName(aStrValue)) {
    isValid = PR_TRUE;
    // XXX Uniqueness tests per
    //   http://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-TokenizedType
  }

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#idref
PRBool
nsSchemaValidatorUtils::IsValidSchemaIDRef(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // xsd:IDREF is a restriction of xsd:NCName
  if (IsValidSchemaNCName(aStrValue)) {
    isValid = PR_TRUE;
    // XXX Ensure IDREF really references an ID,
    //   http://www.w3.org/TR/2000/WD-xml-2e-20000814#idref
  }

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#idrefs
PRBool
nsSchemaValidatorUtils::IsValidSchemaIDRefs(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // Need to validate each IDREF
  const PRUnichar *iter, *end, *tokenStart;
  nsAutoString idref;
  aStrValue.BeginReading(&iter, &end);
  aStrValue.BeginReading(&tokenStart);
  while (iter != end) {
    for (;IsWhitespace(*iter) && iter != end; ++iter);
    tokenStart = iter;

    // Find end of token
    for (;!IsWhitespace(*iter) && iter != end; ++iter);

    // Get the token/idref and validate
    idref = Substring(tokenStart, iter);
    isValid = IsValidSchemaIDRef(idref);
    if (!isValid) break; // No need to continue

    if (iter != end) ++iter;
  }

  return isValid;
}

PRBool
nsSchemaValidatorUtils::IsWhitespace(PRUnichar aChar)
{
  return aChar == ' '  || aChar == '\t' || aChar == '\n' ||
         aChar == '\r' || aChar == '\v';
}

// http://www.w3.org/TR/xmlschema-2/#nmtoken
PRBool
nsSchemaValidatorUtils::IsValidSchemaNMToken(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // xsd:NMTOKEN is a restriction on xsd:token
  if (IsValidSchemaToken(aStrValue)) {
    /*
      NameChar ::= 	Letter | Digit | '.' | '-' | '_' | ':' |
                    CombiningChar | Extender
      Nmtoken	 ::= (NameChar)+
    */
    nsAutoString pattern;
    // XXX Need to handle Combining|Extender and possibly unicode letters
    pattern.AssignLiteral("^[\\w\\.\\-_:]*$");
    nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
    nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return isValid;
}

// http://www.w3.org/TR/xmlschema-2/#nmtokens
PRBool
nsSchemaValidatorUtils::IsValidSchemaNMTokens(const nsAString &aStrValue)
{
  PRBool isValid = PR_FALSE;

  // Need to validate each NNTOKEN
  const PRUnichar *iter, *end, *tokenStart;
  nsAutoString idref;
  aStrValue.BeginReading(&iter, &end);
  aStrValue.BeginReading(&tokenStart);
  while (iter != end) {
    for (;IsWhitespace(*iter) && iter != end; ++iter);
    tokenStart = iter;

    // Find end of token
    for (;!IsWhitespace(*iter) && iter != end; ++iter);

    // Get the token/idref and validate
    idref = Substring(tokenStart, iter);
    isValid = IsValidSchemaNMToken(idref);
    if (!isValid) break; // No need to continue

    if (iter != end) ++iter;
  }

  return isValid;
}

PRBool
nsSchemaValidatorUtils::HandleEnumeration(const nsAString &aStrValue,
                                          const nsStringArray &aEnumerationList)
{
  PRBool isValid = PR_FALSE;

  // check enumeration
  PRInt32 count = aEnumerationList.Count();
  for (PRInt32 i = 0; i < count; ++i) {
    if (aEnumerationList[i]->Equals(aStrValue)) {
      isValid = PR_TRUE;
      LOG(("  Valid: Value matched enumeration #%d", i));
      break;
    }
  }

  if (!isValid) {
    LOG(("  Not valid: Value doesn't match any of the enumerations"));
  }

  return isValid;
}

void
nsSchemaValidatorUtils::RemoveLeadingZeros(nsAString & aString)
{
  const PRUnichar *start, *end;
  aString.BeginReading(&start, &end);

  PRBool done = PR_FALSE;
  PRUint32 count = 0, indexstart = 0;

  if (*start == '+' || *start == '-') {
    start++;
    indexstart = 1;
  }

  while ((start != end) && !done)
  {
    if (*start++ == '0') {
      ++count;
    } else {
      done = PR_TRUE;
    }
  }

  PRUint32 length = aString.Length() - indexstart;

  // if the entire string is composed of zeros, set it to one zero
  if (length == count) {
    aString.AssignLiteral("0");
  } else {
    // finally, remove the leading zeros
    aString.Cut(indexstart, count);
  }
}

void
nsSchemaValidatorUtils::RemoveTrailingZeros(nsAString & aString)
{
  const PRUnichar *start, *end;
  aString.BeginReading(&start, &end);

  PRUint32 length = aString.Length();

  PRBool done = PR_FALSE;
  PRUint32 count = 0;
  if(start != end)
      end --;

  while ((start != end) && !done)
  {
    if (*end-- == '0') {
      ++count;
    } else {
      done = PR_TRUE;
    }
  }

  // finally, remove the trailing zeros
  aString.Cut(length - count, count);
}

// Walks the inheritance tree until it finds a type that isn't a restriction
// type.  While it finds restriction types, it collects restriction facets and
// places them into the nsSchemaDerivedSimpleType.  Once a facet has been found,
// it makes sure that it won't be overwritten by the same facet defined in one
// of the inherited types.
nsresult
nsSchemaValidatorUtils::GetDerivedSimpleType(nsISVSchemaSimpleType *aSimpleType,
                                             nsSchemaDerivedSimpleType *aDerived)
{
  PRBool done = PR_FALSE, hasEnumerations = PR_FALSE;
  nsCOMPtr<nsISVSchemaSimpleType> simpleType(aSimpleType);
  PRUint16 simpleTypeValue;
  PRUint32 facetCount;

  nsAutoString enumeration;
  nsresult rv = NS_OK;

  while(simpleType && !done) {
    // get the type of the simpletype
    rv = simpleType->GetSimpleType(&simpleTypeValue);
    NS_ENSURE_SUCCESS(rv, rv);

    switch (simpleTypeValue) {
      case nsISVSchemaSimpleType::SIMPLE_TYPE_RESTRICTION: {
        // handle the facets

        nsCOMPtr<nsISVSchemaRestrictionType> restrictionType =
          do_QueryInterface(simpleType);

        nsCOMPtr<nsISVSchemaFacet> facet;
        PRUint32 facetCounter;
        PRUint16 facetType;

        // get the amount of restriction facet defined.
        rv = restrictionType->GetFacetCount(&facetCount);
        NS_ENSURE_SUCCESS(rv, rv);
        LOG(("    %d facet(s) defined.", facetCount));

        // if we had enumerations, we may not add new ones, since we are
        // being restricted. So if x restricts y, x defines the possible
        // enumerations and any enumerations on y are skipped
        hasEnumerations = (aDerived->enumerationList.Count() > 0);

        for (facetCounter = 0; facetCounter < facetCount; ++facetCounter) {
          rv = restrictionType->GetFacet(facetCounter, getter_AddRefs(facet));
          NS_ENSURE_SUCCESS(rv, rv);
          facet->GetFacetType(&facetType);

          switch (facetType) {
            case nsISVSchemaFacet::FACET_TYPE_LENGTH: {
              nsSchemaIntFacet *length = &aDerived->length;
              if (!length->isDefined) {
                length->isDefined = PR_TRUE;
                facet->GetLengthValue(&length->value);
                LOG(("  - Length Facet found (value is %d)",
                     length->value));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MINLENGTH: {
              nsSchemaIntFacet *minLength = &aDerived->minLength;
              if (!minLength->isDefined) {
                minLength->isDefined = PR_TRUE;
                facet->GetLengthValue(&minLength->value);
                LOG(("  - Min Length Facet found (value is %d)",
                     minLength->value));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MAXLENGTH: {
              nsSchemaIntFacet *maxLength = &aDerived->maxLength;
              if (!maxLength->isDefined) {
                maxLength->isDefined = PR_TRUE;
                facet->GetLengthValue(&maxLength->value);
                LOG(("  - Max Length Facet found (value is %d)",
                     maxLength->value));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_PATTERN: {
              nsSchemaStringFacet *pattern = &aDerived->pattern;
              if (!pattern->isDefined) {
                pattern->isDefined = PR_TRUE;
                facet->GetValue(pattern->value);
                LOG(("  - Pattern Facet found (value is %s)",
                      NS_ConvertUTF16toUTF8(pattern->value).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_ENUMERATION: {
              if (!hasEnumerations) {
                facet->GetValue(enumeration);
                aDerived->enumerationList.AppendString(enumeration);
                LOG(("  - Enumeration found (%s)",
                  NS_ConvertUTF16toUTF8(enumeration).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_WHITESPACE: {
              if (!aDerived->isWhitespaceDefined)
                facet->GetWhitespaceValue(&aDerived->whitespace);
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MAXINCLUSIVE: {
              nsSchemaStringFacet *maxInclusive = &aDerived->maxInclusive;
              if (!maxInclusive->isDefined) {
                maxInclusive->isDefined = PR_TRUE;
                facet->GetValue(maxInclusive->value);
                LOG(("  - Max Inclusive Facet found (value is %s)",
                  NS_ConvertUTF16toUTF8(maxInclusive->value).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MININCLUSIVE: {
              nsSchemaStringFacet *minInclusive = &aDerived->minInclusive;
              if (!minInclusive->isDefined) {
                minInclusive->isDefined = PR_TRUE;
                facet->GetValue(minInclusive->value);
                LOG(("  - Min Inclusive Facet found (value is %s)",
                  NS_ConvertUTF16toUTF8(minInclusive->value).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MAXEXCLUSIVE: {
              nsSchemaStringFacet *maxExclusive = &aDerived->maxExclusive;
              if (!maxExclusive->isDefined) {
                maxExclusive->isDefined = PR_TRUE;
                facet->GetValue(aDerived->maxExclusive.value);
                LOG(("  - Max Exclusive Facet found (value is %s)",
                  NS_ConvertUTF16toUTF8(maxExclusive->value).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_MINEXCLUSIVE: {
              nsSchemaStringFacet *minExclusive = &aDerived->minExclusive;
              if (!minExclusive->isDefined) {
                minExclusive->isDefined = PR_TRUE;
                facet->GetValue(minExclusive->value);
                LOG(("  - Min Exclusive Facet found (value is %s)",
                  NS_ConvertUTF16toUTF8(minExclusive->value).get()));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_TOTALDIGITS: {
              nsSchemaIntFacet *totalDigits = &aDerived->totalDigits;
              if (!totalDigits->isDefined) {
                totalDigits->isDefined = PR_TRUE;
                facet->GetDigitsValue(&totalDigits->value);
                LOG(("  - Totaldigits Facet found (value is %d)",
                     totalDigits->value));
              }
              break;
            }

            case nsISVSchemaFacet::FACET_TYPE_FRACTIONDIGITS: {
              nsSchemaIntFacet *fractionDigits = &aDerived->fractionDigits;
              if (!fractionDigits->isDefined) {
                fractionDigits->isDefined = PR_TRUE;
                facet->GetDigitsValue(&fractionDigits->value);
                LOG(("  - FractionDigits Facet found (value is %d)",
                     fractionDigits->value));
              }
              break;
            }
          }
        }

        // get base type
        nsresult rv = restrictionType->GetBaseType(getter_AddRefs(simpleType));
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      }

      case nsISVSchemaSimpleType::SIMPLE_TYPE_BUILTIN: {
        // we are done
        aDerived->mBaseType = simpleType;
        done = PR_TRUE;
        break;
      }

      case nsISVSchemaSimpleType::SIMPLE_TYPE_LIST: {
        // set as base type
        aDerived->mBaseType = simpleType;
        done = PR_TRUE;
        break;
      }

      case nsISVSchemaSimpleType::SIMPLE_TYPE_UNION: {
        // set as base type
        aDerived->mBaseType = simpleType;
        done = PR_TRUE;
        break;
      }
    }
  }

  return rv;
}

// copies the data from aDerivedSrc to aDerivedDest
void
nsSchemaValidatorUtils::CopyDerivedSimpleType(nsSchemaDerivedSimpleType *aDerivedDest,
                                              nsSchemaDerivedSimpleType *aDerivedSrc)
{
  aDerivedDest->mBaseType = aDerivedSrc->mBaseType;

  aDerivedDest->length.value = aDerivedSrc->length.value;
  aDerivedDest->length.isDefined = aDerivedSrc->length.isDefined;
  aDerivedDest->minLength.value = aDerivedSrc->minLength.value;
  aDerivedDest->minLength.isDefined = aDerivedSrc->minLength.isDefined;
  aDerivedDest->maxLength.value = aDerivedSrc->maxLength.value;
  aDerivedDest->maxLength.isDefined = aDerivedSrc->maxLength.isDefined;

  aDerivedDest->pattern.value = aDerivedSrc->pattern.value;
  aDerivedDest->pattern.isDefined = aDerivedSrc->pattern.isDefined;

  aDerivedDest->isWhitespaceDefined = aDerivedSrc->isWhitespaceDefined;
  aDerivedDest->whitespace = aDerivedSrc->whitespace;

  aDerivedDest->maxInclusive.value = aDerivedSrc->maxInclusive.value;
  aDerivedDest->maxInclusive.isDefined = aDerivedSrc->maxInclusive.isDefined;
  aDerivedDest->minInclusive.value = aDerivedSrc->minInclusive.value;
  aDerivedDest->minInclusive.isDefined = aDerivedSrc->minInclusive.isDefined;
  aDerivedDest->maxExclusive.value = aDerivedSrc->maxExclusive.value;
  aDerivedDest->maxExclusive.isDefined = aDerivedSrc->maxExclusive.isDefined;
  aDerivedDest->minExclusive.value = aDerivedSrc->minExclusive.value;
  aDerivedDest->minExclusive.isDefined = aDerivedSrc->minExclusive.isDefined;

  aDerivedDest->totalDigits.value = aDerivedSrc->totalDigits.value;
  aDerivedDest->totalDigits.isDefined = aDerivedSrc->totalDigits.isDefined;
  aDerivedDest->fractionDigits.value = aDerivedSrc->fractionDigits.value;
  aDerivedDest->fractionDigits.isDefined = aDerivedSrc->fractionDigits.isDefined;

  aDerivedDest->enumerationList = aDerivedSrc->enumerationList;
}

// sets aResultNode to aNode, making sure it points to null or a dom element
void
nsSchemaValidatorUtils::SetToNullOrElement(nsIDOMNode *aNode,
                                           nsIDOMNode **aResultNode)
{
  nsCOMPtr<nsIDOMNode> currentNode(aNode), tmpNode;

  if (currentNode) {
    PRUint16 nodeType;
    currentNode->GetNodeType(&nodeType);

    // if not an element node, skip
    while (currentNode && nodeType != nsIDOMNode::ELEMENT_NODE) {
      currentNode->GetNextSibling(getter_AddRefs(tmpNode));
      currentNode = tmpNode;
      if (currentNode)
        currentNode->GetNodeType(&nodeType);
    }

    currentNode.swap(*aResultNode);

  }
}

PRInt32
nsSchemaValidatorUtils::FindCharInSet(const nsAString & aString,
                                      const char *aSet, PRInt32 aOffset)
{
  if (aString.IsEmpty()) {
    return kNotFound;
  }

  if (aOffset < 0) {
    aOffset = 0;
  } else if (aOffset > (PRInt32)aString.Length()) {
    return kNotFound;
  }

  const PRUnichar *start, *end;
  aString.BeginReading(&start, &end);

  for (; start != end; ++start) {
    for (const char *temp = aSet; *temp; ++temp) {
      if (*start == PRUnichar(*temp)) {
        return (temp - aSet + aOffset) ;
      }
    }
  }

  return kNotFound;
}

PRBool
nsSchemaValidatorUtils::IsGMT(const nsAString & aDateTime)
{
  if (!aDateTime.IsEmpty()) {
    PRInt32 end = aDateTime.Length() - 1;
    PRUnichar c = aDateTime.CharAt(end);
    if (c == 'Z') {
      return PR_TRUE;
    }
  }

  return PR_FALSE;
}
