Back to index

lightning-sunbird  0.9+nobinonly
nsSchemaValidatorUtils.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Schema Validation.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * IBM Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2004
00020  * IBM Corporation. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Doron Rosenberg <doronr@us.ibm.com> (original author)
00024  *   Laurent Jouanneau <laurent@xulfr.org>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 // string includes
00041 #include "nsReadableUtils.h"
00042 #include "nsString.h"
00043 #include "nsUnicharUtils.h"
00044 
00045 #include "nsISchema.h"
00046 #include "nsSchemaValidator.h"
00047 #include "nsSchemaValidatorUtils.h"
00048 #include "nsISchemaValidatorRegexp.h"
00049 #include "nsSchemaDuration.h"
00050 
00051 #include <stdlib.h>
00052 #include <math.h>
00053 #include <errno.h>
00054 #include <limits.h>
00055 #include <float.h>
00056 #include "prlog.h"
00057 #include "prprf.h"
00058 #include "prdtoa.h"
00059 
00060 #ifdef PR_LOGGING
00061 PRLogModuleInfo *gSchemaValidationUtilsLog = PR_NewLogModule("schemaValidation");
00062 
00063 #define LOG(x) PR_LOG(gSchemaValidationUtilsLog, PR_LOG_DEBUG, x)
00064 #define LOG_ENABLED() PR_LOG_TEST(gSchemaValidationUtilsLog, PR_LOG_DEBUG)
00065 #else
00066 #define LOG(x)
00067 #endif
00068 
00069 PRBool
00070 nsSchemaValidatorUtils::IsValidSchemaInteger(const nsAString & aNodeValue,
00071                                              long *aResult, PRBool aOverFlowCheck)
00072 {
00073   return !aNodeValue.IsEmpty() &&
00074          IsValidSchemaInteger(NS_ConvertUTF16toUTF8(aNodeValue).get(),
00075                               aResult, aOverFlowCheck);
00076 }
00077 
00078 // overloaded, for char* rather than nsAString
00079 PRBool
00080 nsSchemaValidatorUtils::IsValidSchemaInteger(const char* aString,
00081                                              long *aResult,
00082                                              PRBool aOverFlowCheck)
00083 {
00084   PRBool isValid = PR_FALSE;
00085 
00086   if (*aString == 0)
00087     return PR_FALSE;
00088 
00089   char * pEnd;
00090   long intValue = strtol(aString, &pEnd, 10);
00091 
00092   if (aResult)
00093     *aResult = intValue;
00094 
00095   if (aOverFlowCheck) {
00096     isValid = (!((intValue == LONG_MAX || intValue == LONG_MIN) && errno == ERANGE))
00097               && *pEnd == '\0';
00098   } else {
00099     isValid = (*pEnd == '\0');
00100   }
00101 
00102   return isValid;
00103 }
00104 
00105 PRBool
00106 nsSchemaValidatorUtils::IsValidSchemaDouble(const nsAString & aNodeValue,
00107                                             double *aResult)
00108 {
00109   return !aNodeValue.IsEmpty() &&
00110          IsValidSchemaDouble(NS_ConvertUTF16toUTF8(aNodeValue).get(), aResult);
00111 }
00112 
00113 // overloaded, for char* rather than nsAString
00114 PRBool
00115 nsSchemaValidatorUtils::IsValidSchemaDouble(const char* aString,
00116                                             double *aResult)
00117 {
00118   PRBool isValid = PR_TRUE;
00119 
00120   if (*aString == 0)
00121     return PR_FALSE;
00122 
00123   char * pEnd;
00124   double value = PR_strtod(aString, &pEnd);
00125 
00126   // If end pointer hasn't moved, then the string wasn't a
00127   // true double (could be INF, -INF or NaN though)
00128   if (*pEnd != '\0') {
00129     nsCAutoString temp(aString);
00130 
00131     // doubles may be INF, -INF or NaN
00132     if (temp.EqualsLiteral("INF")) {
00133       value = DBL_MAX;
00134     } else if (temp.EqualsLiteral("-INF")) {
00135       value = - DBL_MAX;
00136     } else if (!temp.EqualsLiteral("NaN")) {
00137       isValid = PR_FALSE;
00138     }
00139   }
00140 
00141   if (aResult)
00142     *aResult = value;
00143 
00144   return isValid;
00145 }
00146 
00147 PRBool
00148 nsSchemaValidatorUtils::ParseDateTime(const nsAString & aNodeValue,
00149                                       nsSchemaDateTime *aResult)
00150 {
00151   PRBool isValid = PR_FALSE;
00152 
00153   nsAutoString datetimeString(aNodeValue);
00154 
00155   aResult->date.isNegative = (datetimeString.First() == '-');
00156 
00157   /*
00158     http://www.w3.org/TR/xmlschema-2/#dateTime
00159     (-)CCYY-MM-DDThh:mm:ss(.sss...)
00160       then either: Z
00161       or [+/-]hh:mm
00162   */
00163 
00164   // first handle the date part
00165   // search for 'T'
00166 
00167   LOG(("  Validating DateTime:"));
00168 
00169   int findString = datetimeString.FindChar('T');
00170 
00171   // if no T, invalid
00172   if (findString >= 0) {
00173     // we get the date part (from 0 to before 'T')
00174     isValid = ParseSchemaDate(Substring(aNodeValue, 0, findString), PR_FALSE, &aResult->date);
00175 
00176     if (isValid) {
00177       // we get the time part (from after the 'T' till the end)
00178       isValid = ParseSchemaTime(
00179                   Substring(aNodeValue, findString + 1, aNodeValue.Length()),
00180                   &aResult->time);
00181     }
00182   }
00183 
00184   return isValid;
00185 }
00186 
00187 PRBool
00188 nsSchemaValidatorUtils::ParseSchemaDate(const nsAString & aStrValue,
00189                                         PRBool aAllowTimeZone,
00190                                         nsSchemaDate *aDate)
00191 {
00192   PRBool isValid = PR_FALSE;
00193 
00194   /*
00195     http://www.w3.org/TR/xmlschema-2/#date
00196     (-)CCYY-MM-DD
00197     then optionally: Z
00198       or [+/-]hh:mm
00199   */
00200 
00201   nsAString::const_iterator start, end, buffStart;
00202   aStrValue.BeginReading(start);
00203   aStrValue.BeginReading(buffStart);
00204   aStrValue.EndReading(end);
00205   PRUint32 state = 0;
00206   PRUint32 buffLength = 0;
00207   PRBool done = PR_FALSE;
00208   PRUnichar currentChar;
00209 
00210   nsAutoString year;
00211   char month[3] = "";
00212   char day[3] = "";
00213   char timezoneHour[3] = "";
00214   char timezoneMinute[3] = "";
00215 
00216   // if year is negative, skip it
00217   if (aStrValue.First() == '-') {
00218     start++;
00219     buffStart = start;
00220   }
00221 
00222   while ((start != end) && !done) {
00223     currentChar = *start++;
00224     switch (state) {
00225       case 0: {
00226         // year
00227         if (currentChar == '-') {
00228           if (buffLength < 4) {
00229             done = PR_TRUE;
00230           } else {
00231             year.Assign(Substring(buffStart, --start));
00232             state = 1;
00233             buffLength = 0;
00234             buffStart = ++start;
00235           }
00236         } else {
00237           // has to be a numerical character or else abort
00238           if ((currentChar > '9') || (currentChar < '0'))
00239             done = PR_TRUE;
00240           buffLength++;
00241         }
00242         break;
00243       }
00244 
00245       case 1: {
00246         // month
00247         if (buffLength > 2) {
00248           done = PR_TRUE;
00249         } else if (currentChar == '-') {
00250           if (strcmp(month, "12") == 1 || buffLength < 2) {
00251             done = PR_TRUE;
00252           } else {
00253             state = 2;
00254             buffLength = 0;
00255             buffStart = start;
00256           }
00257         } else {
00258           // has to be a numerical character or else abort
00259           if ((currentChar > '9') || (currentChar < '0'))
00260             done = PR_TRUE;
00261           else
00262             month[buffLength] = currentChar;
00263           buffLength++;
00264         }
00265         break;
00266       }
00267 
00268       case 2: {
00269         // day
00270         if (buffLength > 2) {
00271           done = PR_TRUE;
00272         } else if (currentChar == 'Z') {
00273           if (aAllowTimeZone) {
00274             if ((start == end) && (buffLength == 2) && (strcmp(day, "31") < 1)) {
00275             isValid = PR_TRUE;
00276             }
00277           }
00278 
00279           done = PR_TRUE;
00280         } else if ((currentChar == '+') || (currentChar == '-')) {
00281           // timezone
00282           if (aAllowTimeZone) {
00283             state = 3;
00284             buffLength = 0;
00285             buffStart = start;
00286           } else {
00287             // no timezones allowed
00288             done = PR_TRUE;
00289           }
00290         } else {
00291           // has to be a numerical character or else abort
00292           if ((currentChar > '9') || (currentChar < '0'))
00293             done = PR_TRUE;
00294           else {
00295             day[buffLength] = currentChar;
00296           }
00297           buffLength++;
00298 
00299           // are we at the end?
00300           if (start == end && buffLength == 2) {
00301             isValid = PR_TRUE;
00302             done = PR_TRUE;
00303           }
00304         }
00305         break;
00306       }
00307 
00308       case 3: {
00309         // timezone hh:mm
00310         if (end.get()-buffStart.get() == 5) {
00311           isValid = ParseSchemaTimeZone(Substring(buffStart, end), timezoneHour,
00312                                         timezoneMinute);
00313         }
00314 
00315         done = PR_TRUE;
00316         break;
00317       }
00318     }
00319   }
00320 
00321   if (isValid) {
00322     char * pEnd;
00323 
00324     PRUint32 yearval = strtoul(NS_ConvertUTF16toUTF8(year).get(), &pEnd, 10);
00325     if (yearval == 0 || yearval == ULONG_MAX) {
00326       isValid = PR_FALSE;
00327     } else {
00328       PRUint8 monthval = strtol(month, &pEnd, 10);
00329       if (monthval < 1 || monthval > 12) {
00330         isValid = PR_FALSE;
00331       } else {
00332         PRUint8 dayval = strtol(day, &pEnd, 10);
00333         if (dayval < 1) {
00334           isValid = PR_FALSE;
00335         } else {
00336           // check for leap years
00337           PRUint8 maxDay = GetMaximumDayInMonthFor(yearval, monthval);
00338           if (maxDay >= dayval) {
00339             aDate->year = yearval;
00340 
00341             // month/day are validated in the parsing code above
00342             aDate->month = monthval;
00343             aDate->day = dayval;
00344           } else {
00345             isValid = PR_FALSE;
00346           }
00347         }
00348       }
00349     }
00350   }
00351 
00352   LOG(("     Date is %s", ((isValid) ? "Valid" : "Not Valid")));
00353 
00354   return isValid;
00355 }
00356 
00357 // parses a string as a schema time type and returns the parsed
00358 // hour/minute/second/fraction seconds as well as if its a valid
00359 // schema time type.
00360 PRBool
00361 nsSchemaValidatorUtils::ParseSchemaTime(const nsAString & aStrValue,
00362                                         nsSchemaTime* aTime)
00363 {
00364   PRBool isValid = PR_FALSE;
00365 
00366   // time looks like this: HH:MM:SS(.[S]+)(+/-HH:MM)
00367 
00368   char hour[3] = "";
00369   char minute[3] = "";
00370   char second[3] = "";
00371 
00372   char timezoneHour[3] = "";
00373   char timezoneMinute[3] = "";
00374   // we store the fraction seconds because PR_ExplodeTime seems to skip them.
00375   nsAutoString usec;
00376 
00377   nsAString::const_iterator start, end, buffStart;
00378   aStrValue.BeginReading(start);
00379   aStrValue.BeginReading(buffStart);
00380   aStrValue.EndReading(end);
00381   PRUint32 state = 0;
00382   PRUint32 buffLength = 0;
00383   PRBool done = PR_FALSE;
00384   PRUnichar currentChar;
00385   PRUnichar tzSign = PRUnichar(' ');
00386 
00387   while ((start != end) && !done) {
00388     currentChar = *start++;
00389 
00390     switch (state) {
00391       case 0: {
00392         // hour
00393         if (buffLength > 2) {
00394           done = PR_TRUE;
00395         } else if (currentChar == ':') {
00396           // validate hour
00397           if (strcmp(hour, "24") == 1) {
00398             done = PR_TRUE;
00399           } else {
00400             state = 1;
00401             buffLength = 0;
00402             buffStart = start;
00403           }
00404         } else {
00405           // has to be a numerical character or else abort
00406           if ((currentChar > '9') || (currentChar < '0'))
00407             done = PR_TRUE;
00408           else
00409             hour[buffLength] = currentChar;
00410           buffLength++;
00411         }
00412         break;
00413       }
00414 
00415       case 1: {
00416         // minute
00417         if (buffLength > 2) {
00418           done = PR_TRUE;
00419         } else if (currentChar == ':') {
00420           // validate minute
00421           if (strcmp(minute, "59") == 1) {
00422             done = PR_TRUE;
00423           } else {
00424             state = 2;
00425             buffLength = 0;
00426             buffStart = start;
00427           }
00428         } else {
00429           // has to be a numerical character or else abort
00430           if ((currentChar > '9') || (currentChar < '0'))
00431             done = PR_TRUE;
00432           else
00433             minute[buffLength] = currentChar;
00434           buffLength++;
00435         }
00436         break;
00437       }
00438 
00439       case 2: {
00440         // seconds
00441         if (buffLength > 2) {
00442           done = PR_TRUE;
00443         } else if (currentChar == 'Z') {
00444           // if its Z, has to be the last character
00445           if ((start == end) && (strcmp(second, "59") != 1)) {
00446             isValid = PR_TRUE;
00447           }
00448           done = PR_TRUE;
00449           tzSign = currentChar;
00450         } else if ((currentChar == '+') || (currentChar == '-')) {
00451           // timezone exists
00452           if (strcmp(second, "59") == 1) {
00453             done = PR_TRUE;
00454           } else {
00455             state = 4;
00456             buffLength = 0;
00457             buffStart = start;
00458             tzSign = currentChar;
00459           }
00460         } else if (currentChar == '.') {
00461           // fractional seconds exist
00462           if (strcmp(second, "59") == 1) {
00463             done = PR_TRUE;
00464           } else {
00465             state = 3;
00466             buffLength = 0;
00467             buffStart = start;
00468           }
00469         } else {
00470           // has to be a numerical character or else abort
00471           if ((currentChar > '9') || (currentChar < '0'))
00472             done = PR_TRUE;
00473           else {
00474             second[buffLength] = currentChar;
00475             if (start == end) {
00476               isValid = PR_TRUE;
00477               done = PR_TRUE;
00478             }
00479           }
00480           buffLength++;
00481         }
00482 
00483         break;
00484       }
00485 
00486       case 3: {
00487         // fractional seconds
00488 
00489         if (currentChar == 'Z') {
00490           // if its Z, has to be the last character
00491           if (start == end)
00492             isValid = PR_TRUE;
00493           else
00494             done = PR_TRUE;
00495           tzSign = currentChar;
00496           usec.Assign(Substring(buffStart.get(), start.get()-1));
00497         } else if ((currentChar == '+') || (currentChar == '-')) {
00498           // timezone exists
00499           usec.Assign(Substring(buffStart.get(), start.get()-1));
00500           state = 4;
00501           buffLength = 0;
00502           buffStart = start;
00503           tzSign = currentChar;
00504         } else {
00505           // has to be a numerical character or else abort
00506           if ((currentChar > '9') || (currentChar < '0'))
00507             done = PR_TRUE;
00508           else if (start == end) {
00509             usec.Assign(Substring(buffStart, end));
00510             isValid = PR_TRUE;
00511             done = PR_TRUE;
00512           }
00513           buffLength++;
00514         }
00515         break;
00516       }
00517 
00518       case 4: {
00519         // timezone hh:mm
00520        if (buffStart.size_forward() == 5)
00521          isValid = ParseSchemaTimeZone(Substring(buffStart, end), timezoneHour,
00522                                        timezoneMinute);
00523 
00524        done = PR_TRUE;
00525        break;
00526       }
00527     }
00528   }
00529 
00530   if (isValid) {
00531     char * pEnd;
00532 
00533     PRUint32 usecval = strtoul(NS_ConvertUTF16toUTF8(usec).get(), &pEnd, 10);
00534     // be carefull, empty usec returns 0
00535     if (!usec.IsEmpty() && (usecval == 0 || usecval == ULONG_MAX)) {
00536       isValid = PR_FALSE;
00537     } else {
00538       aTime->hour = strtol(hour, &pEnd, 10);
00539       aTime->minute = strtol(minute, &pEnd, 10);
00540       aTime->second = strtol(second, &pEnd, 10);
00541       aTime->milisecond = usecval;
00542 
00543       if (tzSign == '+')
00544         aTime->tzIsNegative = PR_FALSE;
00545       else
00546         aTime->tzIsNegative = PR_TRUE;
00547 
00548       aTime->tzhour = strtol(timezoneHour, &pEnd, 10);
00549       aTime->tzminute = strtol(timezoneMinute, &pEnd, 10);
00550     }
00551   }
00552 
00553   LOG(("     Time is %s", ((isValid) ? "Valid" : "Not Valid")));
00554 
00555   return isValid;
00556 }
00557 
00558 PRBool
00559 nsSchemaValidatorUtils::ParseSchemaTimeZone(const nsAString & aStrValue,
00560                                             char *rv_tzhour, char *rv_tzminute)
00561 {
00562   PRBool isValid = PR_FALSE;
00563   char timezoneHour[3] = "";
00564   char timezoneMinute[3] = "";
00565 
00566   nsAString::const_iterator start, end, buffStart;
00567   aStrValue.BeginReading(start);
00568   aStrValue.BeginReading(buffStart);
00569   aStrValue.EndReading(end);
00570   PRUint32 state = 0;
00571   PRUint32 buffLength = 0;
00572   PRBool done = PR_FALSE;
00573   PRUnichar currentChar;
00574 
00575   LOG(("\n    Validating TimeZone"));
00576 
00577   while ((start != end) && !done) {
00578     currentChar = *start++;
00579 
00580     switch (state) {
00581       case 0: {
00582         // hour
00583         if (buffLength > 2) {
00584           done = PR_TRUE;
00585         } else if (currentChar == ':') {
00586           timezoneHour[2] = '\0';
00587           if (strcmp(timezoneHour, "24") == 1) {
00588             done = PR_TRUE;
00589           } else {
00590             state = 1;
00591             buffLength = 0;
00592             buffStart = start;
00593           }
00594         } else {
00595           // has to be a numerical character or else abort
00596           if ((currentChar > '9') || (currentChar < '0'))
00597             done = PR_TRUE;
00598           else {
00599             timezoneHour[buffLength] = currentChar;
00600           }
00601           buffLength++;
00602         }
00603         break;
00604       }
00605 
00606       case 1: {
00607         // minute
00608         if (buffLength > 2) {
00609           done = PR_TRUE;
00610         } else if (start == end) {
00611           if (buffLength == 1) {
00612             if ((currentChar > '9') || (currentChar < '0')) {
00613               done = PR_TRUE;
00614             } else {
00615               timezoneMinute[buffLength] = currentChar;
00616 
00617               timezoneMinute[2] = '\0';
00618               if (strcmp(timezoneMinute, "59") == 1) {
00619                 done = PR_TRUE;
00620               } else {
00621                 isValid = PR_TRUE;
00622               }
00623             }
00624           } else {
00625             done = PR_FALSE;
00626           }
00627         } else {
00628           // has to be a numerical character or else abort
00629           if ((currentChar > '9') || (currentChar < '0')) {
00630             done = PR_TRUE;
00631           } else {
00632             timezoneMinute[buffLength] = currentChar;
00633           }
00634           buffLength++;
00635         }
00636         break;
00637       }
00638 
00639     }
00640   }
00641 
00642   if (isValid) {
00643     strncpy(rv_tzhour, timezoneHour, 3);
00644     strncpy(rv_tzminute, timezoneMinute, 3);
00645   }
00646 
00647   return isValid;
00648 }
00649 
00650 /*
00651  -1 - aDateTime1 < aDateTime2
00652   0 - equal
00653   1 - aDateTime1 > aDateTime2
00654 */
00655 int
00656 nsSchemaValidatorUtils::CompareDateTime(nsSchemaDateTime aDateTime1,
00657                                         nsSchemaDateTime aDateTime2)
00658 {
00659   int result;
00660 
00661   nsSchemaDateTime dateTime1, dateTime2;
00662   AddTimeZoneToDateTime(aDateTime1, &dateTime1);
00663   AddTimeZoneToDateTime(aDateTime2, &dateTime2);
00664 
00665   if (!dateTime1.date.isNegative && dateTime2.date.isNegative) {
00666     // positive year is always bigger than negative year
00667     result = 1;
00668   } else if (dateTime1.date.isNegative && !dateTime2.date.isNegative) {
00669     result = -1;
00670   } else {
00671     result = CompareDate(dateTime1.date, dateTime2.date);
00672 
00673     if (result == 0)
00674       result = CompareTime(dateTime1.time, dateTime2.time);
00675 
00676     if (dateTime1.date.isNegative && dateTime2.date.isNegative) {
00677       // -20 is smaller than -21
00678       if (result == -1)
00679         result = 1;
00680       else if (result == 1)
00681         result = -1;
00682     }
00683   }
00684 
00685   return result;
00686 }
00687 
00688 /*
00689  -1 - aDateTime1 < aDateTime2
00690   0 - equal
00691   1 - aDateTime1 > aDateTime2
00692 */
00693 int
00694 nsSchemaValidatorUtils::CompareDate(nsSchemaDate aDate1, nsSchemaDate aDate2)
00695 {
00696   int result;
00697 
00698   if (aDate1.year < aDate2.year) {
00699     result = -1;
00700   } else if (aDate1.year > aDate2.year) {
00701     result = 1;
00702   } else {
00703     if (aDate1.month < aDate2.month) {
00704       result = -1;
00705     } else if (aDate1.month > aDate2.month) {
00706       result = 1;
00707     } else {
00708       if (aDate1.day < aDate2.day) {
00709         result = -1;
00710       } else if (aDate1.day > aDate2.day) {
00711         result = 1;
00712       } else {
00713         result = 0;
00714       }
00715     }
00716   }
00717 
00718   return result;
00719 }
00720 
00721 /*
00722  -1 - aDateTime1 < aDateTime2
00723   0 - equal
00724   1 - aDateTime1 > aDateTime2
00725 */
00726 int
00727 nsSchemaValidatorUtils::CompareTime(nsSchemaTime aTime1, nsSchemaTime aTime2)
00728 {
00729   int result;
00730 
00731   if (aTime1.hour < aTime2.hour) {
00732     result = -1;
00733   } else if (aTime1.hour > aTime2.hour) {
00734     result = 1;
00735   } else {
00736     if (aTime1.minute < aTime2.minute) {
00737       result = -1;
00738     } else if (aTime1.minute > aTime2.minute) {
00739       result = 1;
00740     } else {
00741       if (aTime1.second < aTime2.second) {
00742         result = -1;
00743       } else if (aTime1.second > aTime2.second) {
00744         result = 1;
00745       } else {
00746         if (aTime1.milisecond < aTime2.milisecond) {
00747           result = -1;
00748         } else if (aTime1.milisecond > aTime2.milisecond) {
00749           result = 1;
00750         } else {
00751           result = 0;
00752         }
00753       }
00754     }
00755   }
00756 
00757   return result;
00758 }
00759 
00760 void
00761 nsSchemaValidatorUtils::AddTimeZoneToDateTime(nsSchemaDateTime aDateTime,
00762                                               nsSchemaDateTime* aDestDateTime)
00763 {
00764   // With timezones, you subtract the timezone difference. So for example,
00765   // 2002-10-10T12:00:00+05:00 is 2002-10-10T07:00:00Z
00766   PRUint32 year = aDateTime.date.year;
00767   PRUint8 month = aDateTime.date.month;
00768   PRUint8 day = aDateTime.date.day;
00769   int hour = aDateTime.time.hour;
00770   int minute = aDateTime.time.minute;
00771   PRUint8 second = aDateTime.time.second;
00772   PRUint32 milisecond = aDateTime.time.milisecond;
00773 
00774   if (aDateTime.time.tzIsNegative) {
00775     hour = hour + aDateTime.time.tzhour;
00776     minute = minute + aDateTime.time.tzminute;
00777   } else {
00778     hour = hour - aDateTime.time.tzhour;
00779     minute = minute - aDateTime.time.tzminute;
00780   }
00781 
00782   div_t divresult;
00783 
00784   if (minute > 59) {
00785     divresult = div(minute, 60);
00786     hour += divresult.quot;
00787     minute = divresult.rem;
00788   } else if (minute < 0) {
00789     minute = 60 + minute;
00790     hour--;
00791   }
00792 
00793   // hour
00794   if (hour == 24 && (minute > 0 || second > 0)) {
00795     // can only be 24:0:0 - need to increment day
00796     day++;
00797     hour = 0;
00798   } else if (hour > 23) {
00799     divresult = div(hour, 24);
00800     day += divresult.quot;
00801     hour = divresult.rem;
00802   } else if (hour < 0) {
00803     hour = 24 + hour;
00804     day--;
00805   }
00806 
00807   // day
00808   if (day == 0) {
00809     // if day is 0, go back a month and make sure we handle month 0 (ie back a year).
00810     month--;
00811 
00812     if (month == 0) {
00813       month = 12;
00814       year--;
00815     }
00816 
00817     day = GetMaximumDayInMonthFor(month, year);
00818   } else {
00819     int maxDay = GetMaximumDayInMonthFor(month, year);
00820     while (day > maxDay) {
00821       day -= maxDay;
00822       month++;
00823 
00824       // since we are a valid datetime, month has to be 12 before the ++, so will
00825       // be 13
00826       if (month == 13) {
00827         month = 1;
00828         year++;
00829       }
00830 
00831       maxDay = GetMaximumDayInMonthFor(month, year);
00832     }
00833   }
00834 
00835   aDestDateTime->date.year = year;
00836   aDestDateTime->date.month = month;
00837   aDestDateTime->date.day = day;
00838   aDestDateTime->date.isNegative = aDateTime.date.isNegative;
00839   aDestDateTime->time.hour = hour;
00840   aDestDateTime->time.minute = minute;
00841   aDestDateTime->time.second = second;
00842   aDestDateTime->time.milisecond = milisecond;
00843   aDestDateTime->time.tzIsNegative = aDateTime.time.tzIsNegative;
00844 }
00845 
00846 void
00847 nsSchemaValidatorUtils::GetMonthShorthand(PRUint8 aMonth, nsACString & aReturn)
00848 {
00849   aReturn.AssignASCII(monthShortHand[aMonth - 1].shortHand);
00850 }
00851 
00852 /*
00853  -1 - aYearMonth1 < aYearMonth2
00854   0 - equal
00855   1 - aYearMonth1 > aYearMonth2
00856 */
00857 int
00858 nsSchemaValidatorUtils::CompareGYearMonth(nsSchemaGYearMonth aYearMonth1,
00859                                           nsSchemaGYearMonth aYearMonth2)
00860 {
00861   int rv;
00862 
00863   if (aYearMonth1.gYear.year > aYearMonth2.gYear.year) {
00864     rv = 1;
00865   } else if (aYearMonth1.gYear.year < aYearMonth2.gYear.year) {
00866     rv = -1;
00867   } else {
00868     // both have the same year
00869     if (aYearMonth1.gMonth.month > aYearMonth2.gMonth.month)
00870       rv = 1;
00871     else if (aYearMonth1.gMonth.month < aYearMonth2.gMonth.month)
00872       rv = -1;
00873     else
00874       rv = 0;
00875   }
00876 
00877   return rv;
00878 }
00879 
00880 /*
00881  -1 - aMonthDay1 < aMonthDay2
00882   0 - equal
00883   1 - aMonthDay1 > aMonthDay2
00884 */
00885 int
00886 nsSchemaValidatorUtils::CompareGMonthDay(nsSchemaGMonthDay aMonthDay1,
00887                                          nsSchemaGMonthDay aMonthDay2)
00888 {
00889   int rv;
00890 
00891   if (aMonthDay1.gMonth.month > aMonthDay2.gMonth.month) {
00892     rv = 1;
00893   } else if (aMonthDay1.gMonth.month < aMonthDay2.gMonth.month) {
00894     rv = -1;
00895   } else {
00896     // both have the same year
00897     if (aMonthDay1.gDay.day > aMonthDay2.gDay.day)
00898       rv = 1;
00899     else if (aMonthDay1.gDay.day < aMonthDay2.gDay.day)
00900       rv = -1;
00901     else
00902       rv = 0;
00903   }
00904 
00905   return rv;
00906 }
00907 
00908 PRBool
00909 nsSchemaValidatorUtils::ParseSchemaDuration(const nsAString & aStrValue,
00910                                             nsISchemaDuration **aDuration)
00911 {
00912   PRBool isValid = PR_FALSE;
00913 
00914   nsAString::const_iterator start, end, buffStart;
00915   aStrValue.BeginReading(start);
00916   aStrValue.BeginReading(buffStart);
00917   aStrValue.EndReading(end);
00918   PRUint32 state = 0;
00919   PRUint32 buffLength = 0;
00920   PRBool done = PR_FALSE;
00921   PRUnichar currentChar;
00922 
00923   PRBool isNegative = PR_FALSE;
00924 
00925   // make sure leading P is present.  Take negative durations into consideration.
00926   if (*start == '-') {
00927     start.advance(1);
00928     if (*start != 'P') {
00929       return PR_FALSE;
00930     } else {
00931       isNegative = PR_TRUE;
00932       buffStart = ++start;
00933     }
00934   } else { 
00935     if (*start != 'P')
00936       return PR_FALSE;
00937     else
00938       ++start;
00939   }
00940 
00941   nsAutoString parseBuffer;
00942   PRBool timeSeparatorFound = PR_FALSE;
00943 
00944   // designators may not repeat, so keep track of those we find.
00945   PRBool yearFound = PR_FALSE;
00946   PRBool monthFound = PR_FALSE;
00947   PRBool dayFound = PR_FALSE;
00948   PRBool hourFound = PR_FALSE;
00949   PRBool minuteFound = PR_FALSE;
00950   PRBool secondFound = PR_FALSE;
00951   PRBool fractionSecondFound = PR_FALSE;
00952 
00953   PRUint32 year = 0;
00954   PRUint32 month = 0;
00955   PRUint32 day = 0;
00956   PRUint32 hour = 0;
00957   PRUint32 minute = 0;
00958   PRUint32 second = 0;
00959   double fractionSecond = 0;
00960 
00961   /* durations look like this:
00962        (-)PnYnMnDTnHnMn(.n)S
00963      - P is required, plus sign is invalid
00964      - order is important, so day after year is invalid (PnDnY)
00965      - Y,M,D,H,M,S are called designators
00966      - designators are not allowed without a number before them
00967      - T is the date/time seperator and is only allowed given a time part
00968   */
00969 
00970   while ((start != end) && !done) {
00971     currentChar = *start++;
00972     // not a number - so it has to be a type designator (YMDTHMS)
00973     // it can also be |.| for fractional seconds
00974     if ((currentChar > '9') || (currentChar < '0')) {
00975       // first check if the buffer is bigger than what long can store
00976       // which is 11 digits, as we convert to long
00977       if ((parseBuffer.Length() == 10) &&
00978           (CompareStrings(parseBuffer, NS_LITERAL_STRING("2147483647")) == 1)) {
00979         done = PR_TRUE;
00980       } else if (currentChar == 'Y') {
00981         if (yearFound || monthFound || dayFound || timeSeparatorFound) {
00982           done = PR_TRUE;
00983         } else {
00984           state = 0;
00985           yearFound = PR_TRUE;
00986         }
00987       } else if (currentChar == 'M') {
00988         // M is used twice - Months and Minutes
00989         if (!timeSeparatorFound)
00990           if (monthFound || dayFound || timeSeparatorFound) {
00991             done = PR_TRUE;
00992           } else {
00993             state = 1;
00994             monthFound = PR_TRUE;
00995           }
00996         else {
00997           if (!timeSeparatorFound) {
00998             done = PR_TRUE;
00999           } else {
01000             if (minuteFound || secondFound) {
01001               done = PR_TRUE;
01002             } else {
01003               state = 4;
01004               minuteFound = PR_TRUE;
01005             }
01006           }
01007         }
01008       } else if (currentChar == 'D') {
01009         if (dayFound || timeSeparatorFound) {
01010           done = PR_TRUE;
01011         } else {
01012           state = 2;
01013           dayFound = PR_TRUE;
01014         }
01015       } else if (currentChar == 'T') {
01016         // can't have the time seperator more than once
01017         if (timeSeparatorFound)
01018           done = PR_TRUE;
01019         else
01020           timeSeparatorFound = PR_TRUE;
01021       } else  if (currentChar == 'H') {
01022         if (!timeSeparatorFound || hourFound || secondFound ) {
01023           done = PR_TRUE;
01024         } else {
01025           state = 3;
01026           hourFound = PR_TRUE;
01027         }
01028       } else if (currentChar == 'S') {
01029         if (!timeSeparatorFound)
01030           done = PR_TRUE;
01031         else
01032           if (secondFound) {
01033             done = PR_TRUE;
01034           } else {
01035             state = 5;
01036             secondFound = PR_TRUE;
01037           }
01038       } else if (currentChar == '.') {
01039         // fractional seconds
01040         if (fractionSecondFound) {
01041           done = PR_TRUE;
01042         } else {
01043           parseBuffer.Append(currentChar);
01044           buffLength++;
01045           fractionSecondFound = PR_TRUE;
01046         }
01047       } else {
01048         done = PR_TRUE;
01049       }
01050 
01051       // if its a designator and no buffer, invalid per spec.  Rule doesn't apply
01052       // if T or '.' (fractional seconds), as we need to parse on for those.
01053       // so P200YM is invalid as there is no number before M
01054       if ((currentChar != 'T') && (currentChar != '.') && (parseBuffer.Length() == 0)) {
01055         done = PR_TRUE;
01056       } else if ((currentChar == 'T') && (start == end)) {
01057         // if 'T' is found but no time data after it, invalid
01058         done = PR_TRUE;
01059       }
01060 
01061       if (!done && (currentChar != 'T') && (currentChar != '.')) {
01062         long temp;
01063         switch (state) {
01064           case 0: {
01065             // years
01066             if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01067               done = PR_TRUE;
01068             else
01069               year = temp;
01070             break;
01071           }
01072 
01073           case 1: {
01074             // months
01075             if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01076               done = PR_TRUE;
01077             else
01078               month = temp;
01079             break;
01080           }
01081 
01082           case 2: {
01083             // days
01084             if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01085               done = PR_TRUE;
01086             else
01087               day = temp;
01088             break;
01089           }
01090 
01091           case 3: {
01092             // hours
01093             if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01094               done = PR_TRUE;
01095             else
01096               hour = temp;
01097             break;
01098           }
01099 
01100           case 4: {
01101             // minutes
01102             if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01103               done = PR_TRUE;
01104             else
01105               minute = temp;
01106             break;
01107           }
01108 
01109           case 5: {
01110             // seconds - we have to handle optional fraction seconds as well
01111             if (fractionSecondFound) {
01112               double temp2, intpart;
01113 
01114               if (!IsValidSchemaDouble(parseBuffer, &temp2)) {
01115                 done = PR_TRUE;
01116               } else {
01117                 fractionSecond = modf(temp2, &intpart);
01118                 second = NS_STATIC_CAST(PRUint32, intpart);
01119               }
01120             } else {
01121               if (!IsValidSchemaInteger(parseBuffer, &temp, PR_TRUE))
01122                 done = PR_TRUE;
01123               else
01124                 second = temp;
01125             }
01126             break;
01127           }
01128         }
01129       }
01130 
01131       // clear buffer unless we are at fraction seconds, since we want to parse
01132       // the seconds and fraction seconds into the same buffer.
01133       if (!fractionSecondFound) {
01134         parseBuffer.AssignLiteral("");
01135         buffLength = 0;
01136       }
01137     } else {
01138       if (buffLength > 11) {
01139         done = PR_TRUE;
01140       } else {
01141         parseBuffer.Append(currentChar);
01142         buffLength++;
01143       }
01144     }
01145   }
01146 
01147   if ((start == end) && (!done)) {
01148     isValid = PR_TRUE;
01149   }
01150 
01151   if (isValid) {
01152     nsISchemaDuration* duration = new nsSchemaDuration(year, month, day, hour,
01153                                                        minute, second,
01154                                                        fractionSecond,
01155                                                        isNegative);
01156 
01157     *aDuration = duration;
01158     NS_IF_ADDREF(*aDuration);
01159   }
01160 
01161   return isValid;
01162 }
01163 
01164 /* compares 2 strings that contain integers.
01165    Schema Integers have no limit, thus converting the strings
01166    into numbers won't work.
01167 
01168  -1 - aString1 < aString2
01169   0 - equal
01170   1 - aString1 > aString2
01171 
01172  */
01173 int
01174 nsSchemaValidatorUtils::CompareStrings(const nsAString & aString1,
01175                                        const nsAString & aString2)
01176 {
01177   int rv;
01178 
01179   PRBool isNegative1 = (aString1.First() == PRUnichar('-'));
01180   PRBool isNegative2 = (aString2.First() == PRUnichar('-'));
01181 
01182   if (isNegative1 && !isNegative2) {
01183     // negative is always smaller than positive
01184     return -1;
01185   } else if (!isNegative1 && isNegative2) {
01186     // positive is always bigger than negative
01187     return 1;
01188   }
01189 
01190   nsAString::const_iterator start1, start2, end1, end2;
01191   aString1.BeginReading(start1);
01192   aString1.EndReading(end1);
01193   aString2.BeginReading(start2);
01194   aString2.EndReading(end2);
01195 
01196   // skip negative sign
01197   if (isNegative1)
01198     start1++;
01199 
01200   if (isNegative2)
01201     start2++;
01202 
01203   // jump over leading zeros
01204   PRBool done = PR_FALSE;
01205   while ((start1 != end1) && !done) {
01206     if (*start1 != '0')
01207       done = PR_TRUE;
01208     else
01209       ++start1;
01210   }
01211 
01212   done = PR_FALSE;
01213   while ((start2 != end2) && !done) {
01214     if (*start2 != '0')
01215       done = PR_TRUE;
01216     else
01217       ++start2;
01218   }
01219 
01220   nsAutoString compareString1, compareString2;
01221   compareString1.Assign(Substring(start1, end1));
01222   compareString2.Assign(Substring(start2, end2));
01223 
01224   // after removing leading 0s, check if they are the same
01225   if (compareString1.Equals(compareString2)) {
01226     return 0;
01227   }
01228 
01229   if (compareString1.Length() > compareString2.Length())
01230     rv = 1;
01231   else if (compareString1.Length() < compareString2.Length())
01232     rv = -1;
01233   else
01234     rv = strcmp(NS_ConvertUTF16toUTF8(compareString1).get(),
01235                 NS_ConvertUTF16toUTF8(compareString2).get());
01236 
01237   // 3>2, but -2>-3
01238   if (isNegative1 && isNegative2) {
01239     if (rv == 1)
01240       rv = -1;
01241     else
01242       rv = 1;
01243   }
01244   return rv;
01245 }
01246 
01247 // For xsd:duration support, the the maximum day for a month/year combo as
01248 // defined in http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes.
01249 int
01250 nsSchemaValidatorUtils::GetMaximumDayInMonthFor(PRUint32 aYearValue, PRUint8 aMonthValue)
01251 {
01252   PRUint8 maxDay = 28;
01253   PRUint8 month = ((aMonthValue - 1) % 12) + 1;
01254   PRUint32 year = aYearValue + ((aMonthValue - 1) / 12);
01255 
01256   /*
01257     Return Value      Condition
01258     31                month is either 1, 3, 5, 7, 8, 10, 12
01259     30                month is either 4, 6, 9, 11
01260     29                month is 2 AND either ((year % 4 == 0) AND (year % 100 != 0))
01261                                             OR (year % 400 == 0)
01262     28                Otherwise
01263   */
01264 
01265   if ((month == 1) || (month == 3) || (month == 5) || (month == 7) ||
01266       (month == 8) || (month == 10) || (month == 12))
01267     maxDay = 31;
01268   else if ((month == 4) || (month == 6) || (month == 9) || (month == 11))
01269     maxDay = 30;
01270   else if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
01271     maxDay = 29;
01272 
01273   return maxDay;
01274 }
01275 
01276 /*
01277  * compares 2 durations using the algorithm defined in
01278  * http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes by adding them to
01279  * the 4 datetimes specified in http://w3.org/TR/xmlschema-2/#duration-order.
01280  * If not all 4 result in x<y, we return indeterminate per
01281  * http://www.w3.org/TR/xmlschema-2/#facet-comparison-for-durations.
01282  *
01283  *  0 - aDuration1 < aDuration2
01284  *  1 - indeterminate
01285  *
01286  */
01287 int
01288 nsSchemaValidatorUtils::CompareDurations(nsISchemaDuration *aDuration1,
01289                                          nsISchemaDuration *aDuration2)
01290 {
01291   int cmp = 0, tmpcmp, i = 0;
01292 
01293   nsSchemaDateTime dateTime, newDateTime1, newDateTime2;
01294 
01295   char* datetimeArray[] = { "1696-09-01T00:00:00Z", "1697-02-01T00:00:00Z",
01296                             "1903-03-01T00:00:00Z", "1903-07-01T00:00:00Z" };
01297   PRBool indeterminate = PR_FALSE;
01298 
01299   while (!indeterminate && (i < 4)) {
01300     ParseDateTime(NS_ConvertASCIItoUTF16(datetimeArray[i]), &dateTime);
01301 
01302     AddDurationToDatetime(dateTime, aDuration1, &newDateTime1);
01303     AddDurationToDatetime(dateTime, aDuration2, &newDateTime2);
01304 
01305     tmpcmp = CompareDateTime(newDateTime1, newDateTime2);
01306 
01307     if (i > 0) {
01308       if (tmpcmp != cmp || tmpcmp > -1) {
01309         indeterminate = PR_TRUE;
01310       }
01311     }
01312 
01313     cmp = tmpcmp;
01314     ++i;
01315   }
01316 
01317   return indeterminate ? 1 : 0;
01318 }
01319 
01320 /* 
01321  * This method implements the algorithm described at:
01322  *    http://w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
01323  */
01324 void
01325 nsSchemaValidatorUtils::AddDurationToDatetime(nsSchemaDateTime aDatetime,
01326                                               nsISchemaDuration *aDuration,
01327                                               nsSchemaDateTime* aResultDateTime)
01328 {
01329   // first handle months
01330   PRUint32 temp = 0;
01331   aDuration->GetMonths(&temp);
01332   temp += aDatetime.date.month;
01333   aResultDateTime->date.month = ((temp - 1) % 12) + 1;
01334   PRInt32 carry = (temp - 1) / 12;
01335 
01336   // years
01337   aDuration->GetYears(&temp);
01338   aResultDateTime->date.year = aDatetime.date.year + carry + temp;
01339 
01340   // reset the carry
01341   carry = 0;
01342 
01343   /* fraction seconds
01344    * XXX: Since the 4 datetimes we add durations to don't have fraction seconds
01345    *      we can just add the duration's fraction second (stored as an float),
01346    *      which will be < 1.0.
01347    */
01348   double dblValue;
01349   aDuration->GetFractionSeconds(&dblValue);
01350   aResultDateTime->time.milisecond = (int) dblValue * 1000000;
01351 
01352   // seconds
01353   aDuration->GetSeconds(&temp);
01354   temp += aDatetime.time.second + carry;
01355   aResultDateTime->time.second = temp % 60;
01356   carry = temp / 60;
01357 
01358   // minutes
01359   aDuration->GetMinutes(&temp);
01360   temp += aDatetime.time.minute + carry;
01361   aResultDateTime->time.minute = temp % 60;
01362   carry = temp / 60;
01363 
01364   // hours
01365   aDuration->GetHours(&temp);
01366   temp += aDatetime.time.hour + carry;
01367   aResultDateTime->time.hour = temp % 24;
01368   carry = temp / 24;
01369 
01370   // days
01371   int maxDay = GetMaximumDayInMonthFor(aResultDateTime->date.year,
01372                                        aResultDateTime->date.month);
01373   int tempDays = 0;
01374   if (aDatetime.date.day > maxDay)
01375     tempDays = maxDay;
01376   else if (aDatetime.date.day < 1)
01377     tempDays = 1;
01378   else
01379     tempDays = aDatetime.date.day;
01380 
01381   aDuration->GetDays(&temp);
01382   aResultDateTime->date.day = tempDays + carry + temp;
01383 
01384   PRBool done = PR_FALSE;
01385   while (!done) {
01386     maxDay = GetMaximumDayInMonthFor(aResultDateTime->date.year,
01387                                      aResultDateTime->date.month);
01388     if (aResultDateTime->date.day < 1) {
01389       aResultDateTime->date.day +=
01390         GetMaximumDayInMonthFor(aResultDateTime->date.year,
01391                                 aResultDateTime->date.month - 1);
01392       carry = -1;
01393     } else if (aResultDateTime->date.day > maxDay) {
01394       aResultDateTime->date.day -= maxDay;
01395       carry = 1;
01396     } else {
01397       done = PR_TRUE;
01398     }
01399 
01400     if (!done) {
01401       temp = aResultDateTime->date.month + carry;
01402       aResultDateTime->date.month = ((temp - 1) % 12) + 1;
01403       aResultDateTime->date.year += (temp - 1) / 12;
01404     }
01405   }
01406 
01407   // copy over negative and tz data
01408   aResultDateTime->date.isNegative = aDatetime.date.isNegative;
01409 
01410   aResultDateTime->time.tzIsNegative = aDatetime.time.tzIsNegative;
01411   aResultDateTime->time.tzhour = aDatetime.time.tzhour;
01412   aResultDateTime->time.tzminute = aDatetime.time.tzminute;
01413 
01414   LOG(("\n  New datetime is %d-%d-%d %d:%d:%d\n", aResultDateTime->date.day,
01415     aResultDateTime->date.month, aResultDateTime->date.year,
01416     aResultDateTime->time.hour, aResultDateTime->time.minute,
01417     aResultDateTime->time.second));
01418 }
01419 
01420 // http://www.w3.org/TR/xmlschema-2/#normalizedString
01421 PRBool
01422 nsSchemaValidatorUtils::IsValidSchemaNormalizedString(const nsAString &aStrValue)
01423 {
01424   PRBool isValid = PR_FALSE;
01425   nsAutoString string(aStrValue);
01426 
01427   // may not contain carriage return, line feed nor tab characters
01428   if (string.FindCharInSet("\t\r\n") == kNotFound)
01429     isValid = PR_TRUE;
01430 
01431   return isValid;
01432 }
01433 
01434 // http://www.w3.org/TR/xmlschema-2/#token
01435 PRBool
01436 nsSchemaValidatorUtils::IsValidSchemaToken(const nsAString &aStrValue)
01437 {
01438   PRBool isValid = PR_FALSE;
01439   nsAutoString string(aStrValue);
01440 
01441   // may not contain carriage return, line feed, tab characters.  Also can
01442   // not contain leading/trailing whitespace and no internal sequences of
01443   // two or more spaces.
01444   if ((string.FindCharInSet("\t\r\n") == kNotFound) &&
01445       (string.Find(NS_LITERAL_STRING("  ")) == kNotFound) &&
01446       (string.First() != ' ') &&
01447       (string.Last() != ' '))
01448     isValid = PR_TRUE;
01449 
01450   return isValid;
01451 }
01452 
01453 // http://www.w3.org/TR/xmlschema-2/#language
01454 PRBool
01455 nsSchemaValidatorUtils::IsValidSchemaLanguage(const nsAString &aStrValue)
01456 {
01457   PRBool isValid = PR_FALSE;
01458 
01459   // pattern is defined in spec
01460   nsAutoString pattern;
01461   pattern.AssignLiteral("[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*");
01462 
01463   nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
01464   nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
01465   NS_ENSURE_SUCCESS(rv, rv);
01466 
01467   return isValid;
01468 }
01469 
01470 // http://www.w3.org/TR/xmlschema-2/#name
01471 PRBool
01472 nsSchemaValidatorUtils::IsValidSchemaName(const nsAString &aStrValue)
01473 {
01474   PRBool isValid = PR_FALSE;
01475 
01476   // xsd:Name is restriction on xsd:token
01477   if (IsValidSchemaToken(aStrValue)) {
01478     /* http://www.w3.org/TR/2000/WD-xml-2e-20000814
01479     [4]       NameChar ::=  Letter | Digit | '.' | '-' | '_' | ':' |
01480                       CombiningChar | Extender
01481     [5]       Name     ::= (Letter | '_' | ':') ( NameChar)*
01482     */
01483     // XXX Need to handling CombiningChar and Extender as well
01484     // XXX Additional Unicode testing needed?
01485     nsAutoString pattern;
01486     pattern.AssignLiteral("^[a-zA-Z_:][\\w\\.\\-:]*$");
01487     nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
01488     nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
01489     NS_ENSURE_SUCCESS(rv, rv);
01490   }
01491 
01492   return isValid;
01493 }
01494 
01495 // http://www.w3.org/TR/xmlschema-2/#ncname
01496 PRBool
01497 nsSchemaValidatorUtils::IsValidSchemaNCName(const nsAString &aStrValue)
01498 {
01499   PRBool isValid = PR_FALSE;
01500   
01501   // xsd:NCNAME is a restriction on xsd:Name
01502   if (IsValidSchemaToken(aStrValue)) {
01503     /* http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName
01504       NCNameChar ::=  Letter | Digit | '.' | '-' | '_' |
01505                       CombiningChar | Extender
01506       NCName     ::= (Letter | '_') (NCNameChar)*
01507      */
01508     nsAutoString pattern;
01509     // XXX Need to handle Combining|Extender and Unicode Letters
01510     // xsd:Name minus the ":"
01511     pattern.AssignLiteral("^[a-zA-Z_][\\w\\.\\-]*$");
01512     nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
01513     nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
01514     NS_ENSURE_SUCCESS(rv, rv);
01515   }
01516 
01517   return isValid;
01518 }
01519 
01520 // http://www.w3.org/TR/xmlschema-2/#id
01521 PRBool
01522 nsSchemaValidatorUtils::IsValidSchemaID(const nsAString &aStrValue)
01523 {
01524   PRBool isValid = PR_FALSE;
01525 
01526   // xsd:ID is a restriction of xsd:NCNAME
01527   if (IsValidSchemaNCName(aStrValue)) {
01528     isValid = PR_TRUE;
01529     // XXX Uniqueness tests per
01530     //   http://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-TokenizedType
01531   }
01532 
01533   return isValid;
01534 }
01535 
01536 // http://www.w3.org/TR/xmlschema-2/#idref
01537 PRBool
01538 nsSchemaValidatorUtils::IsValidSchemaIDRef(const nsAString &aStrValue)
01539 {
01540   PRBool isValid = PR_FALSE;
01541 
01542   // xsd:IDREF is a restriction of xsd:NCName
01543   if (IsValidSchemaNCName(aStrValue)) {
01544     isValid = PR_TRUE;
01545     // XXX Ensure IDREF really references an ID,
01546     //   http://www.w3.org/TR/2000/WD-xml-2e-20000814#idref
01547   }
01548 
01549   return isValid;
01550 }
01551 
01552 // http://www.w3.org/TR/xmlschema-2/#idrefs
01553 PRBool
01554 nsSchemaValidatorUtils::IsValidSchemaIDRefs(const nsAString &aStrValue)
01555 {
01556   PRBool isValid = PR_FALSE;
01557 
01558   // Need to validate each IDREF
01559   nsAString::const_iterator iter, end, tokenStart;
01560   nsAutoString idref;
01561   aStrValue.BeginReading(iter);
01562   aStrValue.BeginReading(tokenStart);
01563   aStrValue.EndReading(end);
01564   while (iter != end) {
01565     for (;IsWhitespace(*iter) && iter != end; ++iter);
01566     tokenStart = iter;
01567 
01568     // Find end of token
01569     for (;!IsWhitespace(*iter) && iter != end; ++iter);
01570 
01571     // Get the token/idref and validate
01572     idref = Substring(tokenStart, iter);
01573     isValid = IsValidSchemaIDRef(idref);
01574     if (!isValid) break; // No need to continue
01575 
01576     if (iter != end) ++iter;
01577   }
01578 
01579   return isValid;
01580 }
01581 
01582 PRBool
01583 nsSchemaValidatorUtils::IsWhitespace(PRUnichar aChar)
01584 {
01585   return aChar == ' '  || aChar == '\t' || aChar == '\n' ||
01586          aChar == '\r' || aChar == '\v';
01587 }
01588 
01589 // http://www.w3.org/TR/xmlschema-2/#nmtoken
01590 PRBool
01591 nsSchemaValidatorUtils::IsValidSchemaNMToken(const nsAString &aStrValue)
01592 {
01593   PRBool isValid = PR_FALSE;
01594 
01595   // xsd:NMTOKEN is a restriction on xsd:token
01596   if (IsValidSchemaToken(aStrValue)) {
01597     /*
01598       NameChar ::=   Letter | Digit | '.' | '-' | '_' | ':' |
01599                     CombiningChar | Extender
01600       Nmtoken  ::= (NameChar)+
01601     */
01602     nsAutoString pattern;
01603     // XXX Need to handle Combining|Extender and possibly unicode letters
01604     pattern.AssignLiteral("^[\\w\\.\\-_:]*$");
01605     nsCOMPtr<nsISchemaValidatorRegexp> regexp = do_GetService(kREGEXP_CID);
01606     nsresult rv = regexp->RunRegexp(aStrValue, pattern, "g", &isValid);
01607     NS_ENSURE_SUCCESS(rv, rv);
01608   }
01609 
01610   return isValid;
01611 }
01612 
01613 // http://www.w3.org/TR/xmlschema-2/#nmtokens
01614 PRBool
01615 nsSchemaValidatorUtils::IsValidSchemaNMTokens(const nsAString &aStrValue)
01616 {
01617   PRBool isValid = PR_FALSE;
01618 
01619   // Need to validate each NNTOKEN
01620   nsAString::const_iterator iter, end, tokenStart;
01621   nsAutoString idref;
01622   aStrValue.BeginReading(iter);
01623   aStrValue.BeginReading(tokenStart);
01624   aStrValue.EndReading(end);
01625   while (iter != end) {
01626     for (;IsWhitespace(*iter) && iter != end; ++iter);
01627     tokenStart = iter;
01628 
01629     // Find end of token
01630     for (;!IsWhitespace(*iter) && iter != end; ++iter);
01631 
01632     // Get the token/idref and validate
01633     idref = Substring(tokenStart, iter);
01634     isValid = IsValidSchemaNMToken(idref);
01635     if (!isValid) break; // No need to continue
01636 
01637     if (iter != end) ++iter;
01638   }
01639 
01640   return isValid;
01641 }
01642 
01643 PRBool
01644 nsSchemaValidatorUtils::HandleEnumeration(const nsAString &aStrValue,
01645                                           const nsStringArray &aEnumerationList)
01646 {
01647   PRBool isValid = PR_FALSE;
01648 
01649   // check enumeration
01650   PRInt32 count = aEnumerationList.Count();
01651   for (PRInt32 i = 0; i < count; ++i) {
01652     if (aEnumerationList[i]->Equals(aStrValue)) {
01653       isValid = PR_TRUE;
01654       LOG(("  Valid: Value matched enumeration #%d", i));
01655       break;
01656     }
01657   }
01658 
01659   if (!isValid) {
01660     LOG(("  Not valid: Value doesn't match any of the enumerations"));
01661   }
01662 
01663   return isValid;
01664 }
01665 
01666 void
01667 nsSchemaValidatorUtils::RemoveLeadingZeros(nsAString & aString)
01668 {
01669   nsAString::const_iterator start, end;
01670   aString.BeginReading(start);
01671   aString.EndReading(end);
01672 
01673   PRBool done = PR_FALSE;
01674   PRUint32 count = 0, indexstart = 0;
01675 
01676   if (*start == '+' || *start == '-') {
01677     start++;
01678     indexstart = 1;
01679   }
01680 
01681   while ((start != end) && !done)
01682   {
01683     if (*start++ == '0') {
01684       ++count;
01685     } else {
01686       done = PR_TRUE;
01687     }
01688   }
01689 
01690   PRUint32 length = aString.Length() - indexstart;
01691 
01692   // if the entire string is composed of zeros, set it to one zero
01693   if (length == count) {
01694     aString.AssignLiteral("0");
01695   } else {
01696     // finally, remove the leading zeros
01697     aString.Cut(indexstart, count);
01698   }
01699 }
01700 
01701 void
01702 nsSchemaValidatorUtils::RemoveTrailingZeros(nsAString & aString)
01703 {
01704   nsAString::const_iterator start, end;
01705   aString.BeginReading(start);
01706   aString.EndReading(end);
01707 
01708   PRUint32 length = aString.Length();
01709 
01710   PRBool done = PR_FALSE;
01711   PRUint32 count = 0;
01712   if(start != end)
01713       end --;
01714 
01715   while ((start != end) && !done)
01716   {
01717     if (*end-- == '0') {
01718       ++count;
01719     } else {
01720       done = PR_TRUE;
01721     }
01722   }
01723 
01724   // finally, remove the trailing zeros
01725   aString.Cut(length - count, count);
01726 }
01727 
01728 // Walks the inheritance tree until it finds a type that isn't a restriction
01729 // type.  While it finds restriction types, it collects restriction facets and
01730 // places them into the nsSchemaDerivedSimpleType.  Once a facet has been found,
01731 // it makes sure that it won't be overwritten by the same facet defined in one
01732 // of the inherited types.
01733 nsresult
01734 nsSchemaValidatorUtils::GetDerivedSimpleType(nsISchemaSimpleType *aSimpleType,
01735                                              nsSchemaDerivedSimpleType *aDerived)
01736 {
01737   PRBool done = PR_FALSE, hasEnumerations = PR_FALSE;
01738   nsCOMPtr<nsISchemaSimpleType> simpleType(aSimpleType);
01739   PRUint16 simpleTypeValue;
01740   PRUint32 facetCount;
01741 
01742   nsAutoString enumeration;
01743   nsresult rv = NS_OK;
01744 
01745   while(simpleType && !done) {
01746     // get the type of the simpletype
01747     rv = simpleType->GetSimpleType(&simpleTypeValue);
01748     NS_ENSURE_SUCCESS(rv, rv);
01749 
01750     switch (simpleTypeValue) {
01751       case nsISchemaSimpleType::SIMPLE_TYPE_RESTRICTION: {
01752         // handle the facets
01753 
01754         nsCOMPtr<nsISchemaRestrictionType> restrictionType =
01755           do_QueryInterface(simpleType);
01756 
01757         nsCOMPtr<nsISchemaFacet> facet;
01758         PRUint32 facetCounter;
01759         PRUint16 facetType;
01760 
01761         // get the amount of restriction facet defined.
01762         rv = restrictionType->GetFacetCount(&facetCount);
01763         NS_ENSURE_SUCCESS(rv, rv);
01764         LOG(("    %d facet(s) defined.", facetCount));
01765 
01766         // if we had enumerations, we may not add new ones, since we are
01767         // being restricted. So if x restricts y, x defines the possible
01768         // enumerations and any enumerations on y are skipped
01769         hasEnumerations = (aDerived->enumerationList.Count() > 0);
01770 
01771         for (facetCounter = 0; facetCounter < facetCount; ++facetCounter) {
01772           rv = restrictionType->GetFacet(facetCounter, getter_AddRefs(facet));
01773           NS_ENSURE_SUCCESS(rv, rv);
01774           facet->GetFacetType(&facetType);
01775 
01776           switch (facetType) {
01777             case nsISchemaFacet::FACET_TYPE_LENGTH: {
01778               nsSchemaIntFacet *length = &aDerived->length;
01779               if (!length->isDefined) {
01780                 length->isDefined = PR_TRUE;
01781                 facet->GetLengthValue(&length->value);
01782                 LOG(("  - Length Facet found (value is %d)",
01783                      length->value));
01784               }
01785               break;
01786             }
01787 
01788             case nsISchemaFacet::FACET_TYPE_MINLENGTH: {
01789               nsSchemaIntFacet *minLength = &aDerived->minLength;
01790               if (!minLength->isDefined) {
01791                 minLength->isDefined = PR_TRUE;
01792                 facet->GetLengthValue(&minLength->value);
01793                 LOG(("  - Min Length Facet found (value is %d)",
01794                      minLength->value));
01795               }
01796               break;
01797             }
01798 
01799             case nsISchemaFacet::FACET_TYPE_MAXLENGTH: {
01800               nsSchemaIntFacet *maxLength = &aDerived->maxLength;
01801               if (!maxLength->isDefined) {
01802                 maxLength->isDefined = PR_TRUE;
01803                 facet->GetLengthValue(&maxLength->value);
01804                 LOG(("  - Max Length Facet found (value is %d)",
01805                      maxLength->value));
01806               }
01807               break;
01808             }
01809 
01810             case nsISchemaFacet::FACET_TYPE_PATTERN: {
01811               nsSchemaStringFacet *pattern = &aDerived->pattern;
01812               if (!pattern->isDefined) {
01813                 pattern->isDefined = PR_TRUE;
01814                 facet->GetValue(pattern->value);
01815                 LOG(("  - Pattern Facet found (value is %s)",
01816                       NS_ConvertUTF16toUTF8(pattern->value).get()));
01817               }
01818               break;
01819             }
01820 
01821             case nsISchemaFacet::FACET_TYPE_ENUMERATION: {
01822               if (!hasEnumerations) {
01823                 facet->GetValue(enumeration);
01824                 aDerived->enumerationList.AppendString(enumeration);
01825                 LOG(("  - Enumeration found (%s)",
01826                   NS_ConvertUTF16toUTF8(enumeration).get()));
01827               }
01828               break;
01829             }
01830 
01831             case nsISchemaFacet::FACET_TYPE_WHITESPACE: {
01832               if (!aDerived->isWhitespaceDefined)
01833                 facet->GetWhitespaceValue(&aDerived->whitespace);
01834               break;
01835             }
01836 
01837             case nsISchemaFacet::FACET_TYPE_MAXINCLUSIVE: {
01838               nsSchemaStringFacet *maxInclusive = &aDerived->maxInclusive;
01839               if (!maxInclusive->isDefined) {
01840                 maxInclusive->isDefined = PR_TRUE;
01841                 facet->GetValue(maxInclusive->value);
01842                 LOG(("  - Max Inclusive Facet found (value is %s)",
01843                   NS_ConvertUTF16toUTF8(maxInclusive->value).get()));
01844               }
01845               break;
01846             }
01847 
01848             case nsISchemaFacet::FACET_TYPE_MININCLUSIVE: {
01849               nsSchemaStringFacet *minInclusive = &aDerived->minInclusive;
01850               if (!minInclusive->isDefined) {
01851                 minInclusive->isDefined = PR_TRUE;
01852                 facet->GetValue(minInclusive->value);
01853                 LOG(("  - Min Inclusive Facet found (value is %s)",
01854                   NS_ConvertUTF16toUTF8(minInclusive->value).get()));
01855               }
01856               break;
01857             }
01858 
01859             case nsISchemaFacet::FACET_TYPE_MAXEXCLUSIVE: {
01860               nsSchemaStringFacet *maxExclusive = &aDerived->maxExclusive;
01861               if (!maxExclusive->isDefined) {
01862                 maxExclusive->isDefined = PR_TRUE;
01863                 facet->GetValue(aDerived->maxExclusive.value);
01864                 LOG(("  - Max Exclusive Facet found (value is %s)",
01865                   NS_ConvertUTF16toUTF8(maxExclusive->value).get()));
01866               }
01867               break;
01868             }
01869 
01870             case nsISchemaFacet::FACET_TYPE_MINEXCLUSIVE: {
01871               nsSchemaStringFacet *minExclusive = &aDerived->minExclusive;
01872               if (!minExclusive->isDefined) {
01873                 minExclusive->isDefined = PR_TRUE;
01874                 facet->GetValue(minExclusive->value);
01875                 LOG(("  - Min Exclusive Facet found (value is %s)",
01876                   NS_ConvertUTF16toUTF8(minExclusive->value).get()));
01877               }
01878               break;
01879             }
01880 
01881             case nsISchemaFacet::FACET_TYPE_TOTALDIGITS: {
01882               nsSchemaIntFacet *totalDigits = &aDerived->totalDigits;
01883               if (!totalDigits->isDefined) {
01884                 totalDigits->isDefined = PR_TRUE;
01885                 facet->GetDigitsValue(&totalDigits->value);
01886                 LOG(("  - Totaldigits Facet found (value is %d)",
01887                      totalDigits->value));
01888               }
01889               break;
01890             }
01891 
01892             case nsISchemaFacet::FACET_TYPE_FRACTIONDIGITS: {
01893               nsSchemaIntFacet *fractionDigits = &aDerived->fractionDigits;
01894               if (!fractionDigits->isDefined) {
01895                 fractionDigits->isDefined = PR_TRUE;
01896                 facet->GetDigitsValue(&fractionDigits->value);
01897                 LOG(("  - FractionDigits Facet found (value is %d)",
01898                      fractionDigits->value));
01899               }
01900               break;
01901             }
01902           }
01903         }
01904 
01905         // get base type
01906         nsresult rv = restrictionType->GetBaseType(getter_AddRefs(simpleType));
01907         NS_ENSURE_SUCCESS(rv, rv);
01908         break;
01909       }
01910 
01911       case nsISchemaSimpleType::SIMPLE_TYPE_BUILTIN: {
01912         // we are done
01913         aDerived->mBaseType = simpleType;
01914         done = PR_TRUE;
01915         break;
01916       }
01917 
01918       case nsISchemaSimpleType::SIMPLE_TYPE_LIST: {
01919         // set as base type
01920         aDerived->mBaseType = simpleType;
01921         done = PR_TRUE;
01922         break;
01923       }
01924 
01925       case nsISchemaSimpleType::SIMPLE_TYPE_UNION: {
01926         // set as base type
01927         aDerived->mBaseType = simpleType;
01928         done = PR_TRUE;
01929         break;
01930       }
01931     }
01932   }
01933 
01934   return rv;
01935 }
01936 
01937 // copies the data from aDerivedSrc to aDerivedDest
01938 void
01939 nsSchemaValidatorUtils::CopyDerivedSimpleType(nsSchemaDerivedSimpleType *aDerivedDest,
01940                                               nsSchemaDerivedSimpleType *aDerivedSrc)
01941 {
01942   aDerivedDest->mBaseType = aDerivedSrc->mBaseType;
01943 
01944   aDerivedDest->length.value = aDerivedSrc->length.value;
01945   aDerivedDest->length.isDefined = aDerivedSrc->length.isDefined;
01946   aDerivedDest->minLength.value = aDerivedSrc->minLength.value;
01947   aDerivedDest->minLength.isDefined = aDerivedSrc->minLength.isDefined;
01948   aDerivedDest->maxLength.value = aDerivedSrc->maxLength.value;
01949   aDerivedDest->maxLength.isDefined = aDerivedSrc->maxLength.isDefined;
01950 
01951   aDerivedDest->pattern.value = aDerivedSrc->pattern.value;
01952   aDerivedDest->pattern.isDefined = aDerivedSrc->pattern.isDefined;
01953 
01954   aDerivedDest->isWhitespaceDefined = aDerivedSrc->isWhitespaceDefined;
01955   aDerivedDest->whitespace = aDerivedSrc->whitespace;
01956 
01957   aDerivedDest->maxInclusive.value = aDerivedSrc->maxInclusive.value;
01958   aDerivedDest->maxInclusive.isDefined = aDerivedSrc->maxInclusive.isDefined;
01959   aDerivedDest->minInclusive.value = aDerivedSrc->minInclusive.value;
01960   aDerivedDest->minInclusive.isDefined = aDerivedSrc->minInclusive.isDefined;
01961   aDerivedDest->maxExclusive.value = aDerivedSrc->maxExclusive.value;
01962   aDerivedDest->maxExclusive.isDefined = aDerivedSrc->maxExclusive.isDefined;
01963   aDerivedDest->minExclusive.value = aDerivedSrc->minExclusive.value;
01964   aDerivedDest->minExclusive.isDefined = aDerivedSrc->minExclusive.isDefined;
01965 
01966   aDerivedDest->totalDigits.value = aDerivedSrc->totalDigits.value;
01967   aDerivedDest->totalDigits.isDefined = aDerivedSrc->totalDigits.isDefined;
01968   aDerivedDest->fractionDigits.value = aDerivedSrc->fractionDigits.value;
01969   aDerivedDest->fractionDigits.isDefined = aDerivedSrc->fractionDigits.isDefined;
01970 
01971   aDerivedDest->enumerationList = aDerivedSrc->enumerationList;
01972 }
01973 
01974 // sets aResultNode to aNode, making sure it points to null or a dom element
01975 void
01976 nsSchemaValidatorUtils::SetToNullOrElement(nsIDOMNode *aNode,
01977                                            nsIDOMNode **aResultNode)
01978 {
01979   nsCOMPtr<nsIDOMNode> currentNode(aNode), tmpNode;
01980 
01981   if (currentNode) {
01982     PRUint16 nodeType;
01983     currentNode->GetNodeType(&nodeType);
01984 
01985     // if not an element node, skip
01986     while (currentNode && nodeType != nsIDOMNode::ELEMENT_NODE) {
01987       currentNode->GetNextSibling(getter_AddRefs(tmpNode));
01988       currentNode = tmpNode;
01989       if (currentNode)
01990         currentNode->GetNodeType(&nodeType);
01991     }
01992 
01993     currentNode.swap(*aResultNode);
01994 
01995   }
01996 }
01997 
01998 PRBool
01999 nsSchemaValidatorUtils::IsGMT(const nsAString & aDateTime)
02000 {
02001   if (!aDateTime.IsEmpty()) {
02002     if (aDateTime.Last() == 'Z') {
02003       return PR_TRUE;
02004     }
02005   }
02006 
02007   return PR_FALSE;
02008 }