xmlschemastypes.c   [plain text]


/*
 * schemastypes.c : implementation of the XML Schema Datatypes
 *             definition and validity checking
 *
 * See Copyright for the status of this software.
 *
 * Daniel Veillard <veillard@redhat.com>
 */

#define IN_LIBXML
#include "libxml.h"

#ifdef LIBXML_SCHEMAS_ENABLED

#include <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/hash.h>
#include <libxml/valid.h>
#include <libxml/xpath.h>
#include <libxml/uri.h>

#include <libxml/xmlschemas.h>
#include <libxml/schemasInternals.h>
#include <libxml/xmlschemastypes.h>

#ifdef HAVE_MATH_H
#include <math.h>
#endif

#define DEBUG

#define TODO 								\
    xmlGenericError(xmlGenericErrorContext,				\
	    "Unimplemented block at %s:%d\n",				\
            __FILE__, __LINE__);

#define XML_SCHEMAS_NAMESPACE_NAME \
    (const xmlChar *)"http://www.w3.org/2001/XMLSchema"

typedef enum {
    XML_SCHEMAS_UNKNOWN = 0,
    XML_SCHEMAS_STRING,
    XML_SCHEMAS_NMTOKEN,
    XML_SCHEMAS_DECIMAL,
    XML_SCHEMAS_TIME,
    XML_SCHEMAS_GDAY,
    XML_SCHEMAS_GMONTH,
    XML_SCHEMAS_GMONTHDAY,
    XML_SCHEMAS_GYEAR,
    XML_SCHEMAS_GYEARMONTH,
    XML_SCHEMAS_DATE,
    XML_SCHEMAS_DATETIME,
    XML_SCHEMAS_DURATION,
    XML_SCHEMAS_FLOAT,
    XML_SCHEMAS_DOUBLE,
    XML_SCHEMAS_BOOLEAN,
    XML_SCHEMAS_INT,
    XML_SCHEMAS_,
    XML_SCHEMAS_XXX
} xmlSchemaValType;

unsigned long powten[10] = {
    1, 10, 100, 1000, 10000, 100000, 1000000, 10000000L,
    100000000L, 1000000000L
};

/* Date value */
typedef struct _xmlSchemaValDate xmlSchemaValDate;
typedef xmlSchemaValDate *xmlSchemaValDatePtr;
struct _xmlSchemaValDate {
    long		year;
    unsigned int	mon	:4;	/* 1 <=  mon    <= 12   */
    unsigned int	day	:5;	/* 1 <=  day    <= 31   */
    unsigned int	hour	:5;	/* 0 <=  hour   <= 23   */
    unsigned int	min	:6;	/* 0 <=  min    <= 59	*/
    double		sec;
    int			tz_flag	:1;	/* is tzo explicitely set? */
    int			tzo	:11;	/* -1440 <= tzo <= 1440 */
};

/* Duration value */
typedef struct _xmlSchemaValDuration xmlSchemaValDuration;
typedef xmlSchemaValDuration *xmlSchemaValDurationPtr;
struct _xmlSchemaValDuration {
    long	        mon;		/* mon stores years also */
    long        	day;
    double		sec;            /* sec stores min and hour also */
};

typedef struct _xmlSchemaValDecimal xmlSchemaValDecimal;
typedef xmlSchemaValDecimal *xmlSchemaValDecimalPtr;
struct _xmlSchemaValDecimal {
    /* would use long long but not portable */
    unsigned long base;
    unsigned int extra;
    unsigned int sign:1;
    int frac:7;
    int total:8;
};

struct _xmlSchemaVal {
    xmlSchemaValType type;
    union {
	xmlSchemaValDecimal     decimal;
        xmlSchemaValDate        date;
        xmlSchemaValDuration    dur;
	float			f;
	double			d;
	int			b;
    } value;
};

static int xmlSchemaTypesInitialized = 0;
static xmlHashTablePtr xmlSchemaTypesBank = NULL;

/*
 * Basic types
 */
static xmlSchemaTypePtr xmlSchemaTypeStringDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeAnyTypeDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeAnySimpleTypeDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeDecimalDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeDatetimeDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeDateDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeTimeDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeGYearDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeGYearMonthDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeGDayDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeGMonthDayDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeGMonthDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeDurationDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNmtoken = NULL;
static xmlSchemaTypePtr xmlSchemaTypeFloatDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeBooleanDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeDoubleDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNameDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeQNameDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeAnyURIDef = NULL;

/*
 * Derived types
 */
static xmlSchemaTypePtr xmlSchemaTypePositiveIntegerDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNonPositiveIntegerDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNegativeIntegerDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNonNegativeIntegerDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeIntegerDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeLongDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeIntDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeShortDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeByteDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeUnsignedLongDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeUnsignedIntDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeUnsignedShortDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeUnsignedByteDef = NULL;
static xmlSchemaTypePtr xmlSchemaTypeNCNameDef = NULL;

/*
 * xmlSchemaInitBasicType:
 * @name:  the type name
 *
 * Initialize one default type
 */
static xmlSchemaTypePtr
xmlSchemaInitBasicType(const char *name) {
    xmlSchemaTypePtr ret;

    ret = (xmlSchemaTypePtr) xmlMalloc(sizeof(xmlSchemaType));
    if (ret == NULL) {
	xmlGenericError(xmlGenericErrorContext,
		"Could not initilize type %s: out of memory\n", name);
	return(NULL);
    }
    memset(ret, 0, sizeof(xmlSchemaType));
    ret->name = xmlStrdup((const xmlChar *)name);
    ret->type = XML_SCHEMA_TYPE_BASIC;
    ret->contentType = XML_SCHEMA_CONTENT_BASIC;
    xmlHashAddEntry2(xmlSchemaTypesBank, ret->name,
	             XML_SCHEMAS_NAMESPACE_NAME, ret);
    return(ret);
}

/*
 * xmlSchemaInitTypes:
 *
 * Initialize the default XML Schemas type library
 */
void
xmlSchemaInitTypes(void) {
    if (xmlSchemaTypesInitialized != 0)
	return;
    xmlSchemaTypesBank = xmlHashCreate(40);
    
    /*
     * primitive datatypes
     */
    xmlSchemaTypeStringDef = xmlSchemaInitBasicType("string");
    xmlSchemaTypeAnyTypeDef = xmlSchemaInitBasicType("anyType");
    xmlSchemaTypeAnySimpleTypeDef = xmlSchemaInitBasicType("anySimpleType");
    xmlSchemaTypeDecimalDef = xmlSchemaInitBasicType("decimal");
    xmlSchemaTypeDateDef = xmlSchemaInitBasicType("date");
    xmlSchemaTypeDatetimeDef = xmlSchemaInitBasicType("dateTime");
    xmlSchemaTypeTimeDef = xmlSchemaInitBasicType("time");
    xmlSchemaTypeGYearDef = xmlSchemaInitBasicType("gYear");
    xmlSchemaTypeGYearMonthDef = xmlSchemaInitBasicType("gYearMonth");
    xmlSchemaTypeGMonthDef = xmlSchemaInitBasicType("gMonth");
    xmlSchemaTypeGMonthDayDef = xmlSchemaInitBasicType("gMonthDay");
    xmlSchemaTypeGDayDef = xmlSchemaInitBasicType("gDay");
    xmlSchemaTypeDurationDef = xmlSchemaInitBasicType("duration");
    xmlSchemaTypeNmtoken = xmlSchemaInitBasicType("NMTOKEN");
    xmlSchemaTypeFloatDef = xmlSchemaInitBasicType("float");
    xmlSchemaTypeDoubleDef = xmlSchemaInitBasicType("double");
    xmlSchemaTypeBooleanDef = xmlSchemaInitBasicType("boolean");
    xmlSchemaTypeNameDef = xmlSchemaInitBasicType("Name");
    xmlSchemaTypeQNameDef = xmlSchemaInitBasicType("QName");
    xmlSchemaTypeAnyURIDef = xmlSchemaInitBasicType("anyURI");

    /*
     * derived datatypes
     */
    xmlSchemaTypeIntegerDef = xmlSchemaInitBasicType("integer");;
    xmlSchemaTypeNonPositiveIntegerDef = xmlSchemaInitBasicType("nonPositiveInteger");;
    xmlSchemaTypeNegativeIntegerDef = xmlSchemaInitBasicType("negativeInteger");;
    xmlSchemaTypeLongDef = xmlSchemaInitBasicType("long");;
    xmlSchemaTypeIntDef = xmlSchemaInitBasicType("int");;
    xmlSchemaTypeShortDef = xmlSchemaInitBasicType("short");;
    xmlSchemaTypeByteDef = xmlSchemaInitBasicType("byte");;
    xmlSchemaTypeNonNegativeIntegerDef = xmlSchemaInitBasicType("nonNegativeInteger");
    xmlSchemaTypeUnsignedLongDef = xmlSchemaInitBasicType("unsignedLong");;
    xmlSchemaTypeUnsignedIntDef = xmlSchemaInitBasicType("unsignedInt");;
    xmlSchemaTypeUnsignedShortDef = xmlSchemaInitBasicType("insignedShort");;
    xmlSchemaTypeUnsignedByteDef = xmlSchemaInitBasicType("unsignedByte");;
    xmlSchemaTypePositiveIntegerDef = xmlSchemaInitBasicType("positiveInteger");
    xmlSchemaTypeNCNameDef = xmlSchemaInitBasicType("NCName");

    xmlSchemaTypesInitialized = 1;
}

/**
 * xmlSchemaCleanupTypes:
 *
 * Cleanup the default XML Schemas type library
 */
void	
xmlSchemaCleanupTypes(void) {
    if (xmlSchemaTypesInitialized == 0)
	return;
    xmlHashFree(xmlSchemaTypesBank, (xmlHashDeallocator) xmlSchemaFreeType);
    xmlSchemaTypesInitialized = 0;
}

/**
 * xmlSchemaNewValue:
 * @type:  the value type
 *
 * Allocate a new simple type value
 *
 * Returns a pointer to the new value or NULL in case of error
 */
static xmlSchemaValPtr
xmlSchemaNewValue(xmlSchemaValType type) {
    xmlSchemaValPtr value;

    value = (xmlSchemaValPtr) xmlMalloc(sizeof(xmlSchemaVal));
    if (value == NULL) {
	return(NULL);
    }
    memset(value, 0, sizeof(xmlSchemaVal));
    value->type = type;
    return(value);
}

/**
 * xmlSchemaFreeValue:
 * @value:  the value to free
 *
 * Cleanup the default XML Schemas type library
 */
void	
xmlSchemaFreeValue(xmlSchemaValPtr value) {
    if (value == NULL)
	return;
    xmlFree(value);
}

/**
 * xmlSchemaGetPredefinedType:
 * @name: the type name
 * @ns:  the URI of the namespace usually "http://www.w3.org/2001/XMLSchema"
 *
 * Lookup a type in the default XML Schemas type library
 *
 * Returns the type if found, NULL otherwise
 */
xmlSchemaTypePtr
xmlSchemaGetPredefinedType(const xmlChar *name, const xmlChar *ns) {
    if (xmlSchemaTypesInitialized == 0)
	xmlSchemaInitTypes();
    if (name == NULL)
	return(NULL);
    return((xmlSchemaTypePtr) xmlHashLookup2(xmlSchemaTypesBank, name, ns));
}

/****************************************************************
 *								*
 *		Convenience macros and functions		*
 *								*
 ****************************************************************/

#define IS_TZO_CHAR(c)						\
	((c == 0) || (c == 'Z') || (c == '+') || (c == '-'))

#define VALID_YEAR(yr)          (yr != 0)
#define VALID_MONTH(mon)        ((mon >= 1) && (mon <= 12))
/* VALID_DAY should only be used when month is unknown */
#define VALID_DAY(day)          ((day >= 1) && (day <= 31))
#define VALID_HOUR(hr)          ((hr >= 0) && (hr <= 23))
#define VALID_MIN(min)          ((min >= 0) && (min <= 59))
#define VALID_SEC(sec)          ((sec >= 0) && (sec < 60))
#define VALID_TZO(tzo)          ((tzo > -1440) && (tzo < 1440))
#define IS_LEAP(y)						\
	(((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))

static const long daysInMonth[12] =
	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static const long daysInMonthLeap[12] =
	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

#define MAX_DAYINMONTH(yr,mon)                                  \
        (IS_LEAP(yr) ? daysInMonthLeap[mon - 1] : daysInMonth[mon - 1])

#define VALID_MDAY(dt)						\
	(IS_LEAP(dt->year) ?				        \
	    (dt->day <= daysInMonthLeap[dt->mon - 1]) :	        \
	    (dt->day <= daysInMonth[dt->mon - 1]))

#define VALID_DATE(dt)						\
	(VALID_YEAR(dt->year) && VALID_MONTH(dt->mon) && VALID_MDAY(dt))

#define VALID_TIME(dt)						\
	(VALID_HOUR(dt->hour) && VALID_MIN(dt->min) &&		\
	 VALID_SEC(dt->sec) && VALID_TZO(dt->tzo))

#define VALID_DATETIME(dt)					\
	(VALID_DATE(dt) && VALID_TIME(dt))

#define SECS_PER_MIN            (60)
#define SECS_PER_HOUR           (60 * SECS_PER_MIN)
#define SECS_PER_DAY            (24 * SECS_PER_HOUR)

static const long dayInYearByMonth[12] =
	{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
static const long dayInLeapYearByMonth[12] =
	{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };

#define DAY_IN_YEAR(day, month, year)				\
        ((IS_LEAP(year) ?					\
                dayInLeapYearByMonth[month - 1] :		\
                dayInYearByMonth[month - 1]) + day)

#ifdef DEBUG
#define DEBUG_DATE(dt)                                                  \
    xmlGenericError(xmlGenericErrorContext,                             \
        "type=%o %04ld-%02u-%02uT%02u:%02u:%03f",                       \
        dt->type,dt->value.date.year,dt->value.date.mon,                \
        dt->value.date.day,dt->value.date.hour,dt->value.date.min,      \
        dt->value.date.sec);                                            \
    if (dt->value.date.tz_flag)                                         \
        if (dt->value.date.tzo != 0)                                    \
            xmlGenericError(xmlGenericErrorContext,                     \
                "%+05d\n",dt->value.date.tzo);                          \
        else                                                            \
            xmlGenericError(xmlGenericErrorContext, "Z\n");             \
    else                                                                \
        xmlGenericError(xmlGenericErrorContext,"\n")
#else
#define DEBUG_DATE(dt)
#endif

/**
 * _xmlSchemaParseGYear:
 * @dt:  pointer to a date structure
 * @str: pointer to the string to analyze
 *
 * Parses a xs:gYear without time zone and fills in the appropriate
 * field of the @dt structure. @str is updated to point just after the
 * xs:gYear. It is supposed that @dt->year is big enough to contain
 * the year.
 *
 * Returns 0 or the error code
 */
static int
_xmlSchemaParseGYear (xmlSchemaValDatePtr dt, const xmlChar **str) {
    const xmlChar *cur = *str, *firstChar;
    int isneg = 0, digcnt = 0;

    if (((*cur < '0') || (*cur > '9')) &&
	(*cur != '-') && (*cur != '+'))
	return -1;

    if (*cur == '-') {
	isneg = 1;
	cur++;
    }

    firstChar = cur;

    while ((*cur >= '0') && (*cur <= '9')) {
	dt->year = dt->year * 10 + (*cur - '0');
	cur++;
	digcnt++;
    }

    /* year must be at least 4 digits (CCYY); over 4
     * digits cannot have a leading zero. */
    if ((digcnt < 4) || ((digcnt > 4) && (*firstChar == '0')))
	return 1;

    if (isneg)
	dt->year = - dt->year;

    if (!VALID_YEAR(dt->year))
	return 2;

    *str = cur;
    return 0;
}

/**
 * PARSE_2_DIGITS:
 * @num:  the integer to fill in
 * @cur:  an #xmlChar *
 * @invalid: an integer
 *
 * Parses a 2-digits integer and updates @num with the value. @cur is
 * updated to point just after the integer.
 * In case of error, @invalid is set to %TRUE, values of @num and
 * @cur are undefined.
 */
#define PARSE_2_DIGITS(num, cur, invalid)			\
	if ((cur[0] < '0') || (cur[0] > '9') ||			\
	    (cur[1] < '0') || (cur[1] > '9'))			\
	    invalid = 1;					\
	else							\
	    num = (cur[0] - '0') * 10 + (cur[1] - '0');		\
	cur += 2;

/**
 * PARSE_FLOAT:
 * @num:  the double to fill in
 * @cur:  an #xmlChar *
 * @invalid: an integer
 *
 * Parses a float and updates @num with the value. @cur is
 * updated to point just after the float. The float must have a
 * 2-digits integer part and may or may not have a decimal part.
 * In case of error, @invalid is set to %TRUE, values of @num and
 * @cur are undefined.
 */
#define PARSE_FLOAT(num, cur, invalid)				\
	PARSE_2_DIGITS(num, cur, invalid);			\
	if (!invalid && (*cur == '.')) {			\
	    double mult = 1;				        \
	    cur++;						\
	    if ((*cur < '0') || (*cur > '9'))			\
		invalid = 1;					\
	    while ((*cur >= '0') && (*cur <= '9')) {		\
		mult /= 10;					\
		num += (*cur - '0') * mult;			\
		cur++;						\
	    }							\
	}

/**
 * _xmlSchemaParseGMonth:
 * @dt:  pointer to a date structure
 * @str: pointer to the string to analyze
 *
 * Parses a xs:gMonth without time zone and fills in the appropriate
 * field of the @dt structure. @str is updated to point just after the
 * xs:gMonth.
 *
 * Returns 0 or the error code
 */
static int
_xmlSchemaParseGMonth (xmlSchemaValDatePtr dt, const xmlChar **str) {
    const xmlChar *cur = *str;
    int ret = 0;

    PARSE_2_DIGITS(dt->mon, cur, ret);
    if (ret != 0)
	return ret;

    if (!VALID_MONTH(dt->mon))
	return 2;

    *str = cur;
    return 0;
}

/**
 * _xmlSchemaParseGDay:
 * @dt:  pointer to a date structure
 * @str: pointer to the string to analyze
 *
 * Parses a xs:gDay without time zone and fills in the appropriate
 * field of the @dt structure. @str is updated to point just after the
 * xs:gDay.
 *
 * Returns 0 or the error code
 */
static int
_xmlSchemaParseGDay (xmlSchemaValDatePtr dt, const xmlChar **str) {
    const xmlChar *cur = *str;
    int ret = 0;

    PARSE_2_DIGITS(dt->day, cur, ret);
    if (ret != 0)
	return ret;

    if (!VALID_DAY(dt->day))
	return 2;

    *str = cur;
    return 0;
}

/**
 * _xmlSchemaParseTime:
 * @dt:  pointer to a date structure
 * @str: pointer to the string to analyze
 *
 * Parses a xs:time without time zone and fills in the appropriate
 * fields of the @dt structure. @str is updated to point just after the
 * xs:time.
 * In case of error, values of @dt fields are undefined.
 *
 * Returns 0 or the error code
 */
static int
_xmlSchemaParseTime (xmlSchemaValDatePtr dt, const xmlChar **str) {
    const xmlChar *cur = *str;
    unsigned int hour = 0; /* use temp var in case str is not xs:time */
    int ret = 0;

    PARSE_2_DIGITS(hour, cur, ret);
    if (ret != 0)
	return ret;

    if (*cur != ':')
	return 1;
    cur++;

    /* the ':' insures this string is xs:time */
    dt->hour = hour;

    PARSE_2_DIGITS(dt->min, cur, ret);
    if (ret != 0)
	return ret;

    if (*cur != ':')
	return 1;
    cur++;

    PARSE_FLOAT(dt->sec, cur, ret);
    if (ret != 0)
	return ret;

    if (!VALID_TIME(dt))
	return 2;

    *str = cur;
    return 0;
}

/**
 * _xmlSchemaParseTimeZone:
 * @dt:  pointer to a date structure
 * @str: pointer to the string to analyze
 *
 * Parses a time zone without time zone and fills in the appropriate
 * field of the @dt structure. @str is updated to point just after the
 * time zone.
 *
 * Returns 0 or the error code
 */
static int
_xmlSchemaParseTimeZone (xmlSchemaValDatePtr dt, const xmlChar **str) {
    const xmlChar *cur = *str;
    int ret = 0;

    if (str == NULL)
	return -1;

    switch (*cur) {
    case 0:
	dt->tz_flag = 0;
	dt->tzo = 0;
	break;

    case 'Z':
	dt->tz_flag = 1;
	dt->tzo = 0;
	cur++;
	break;

    case '+':
    case '-': {
	int isneg = 0, tmp = 0;
	isneg = (*cur == '-');

	cur++;

	PARSE_2_DIGITS(tmp, cur, ret);
	if (ret != 0)
	    return ret;
	if (!VALID_HOUR(tmp))
	    return 2;

	if (*cur != ':')
	    return 1;
	cur++;

	dt->tzo = tmp * 60;

	PARSE_2_DIGITS(tmp, cur, ret);
	if (ret != 0)
	    return ret;
	if (!VALID_MIN(tmp))
	    return 2;

	dt->tzo += tmp;
	if (isneg)
	    dt->tzo = - dt->tzo;

	if (!VALID_TZO(dt->tzo))
	    return 2;

	dt->tz_flag = 1;
	break;
      }
    default:
	return 1;
    }

    *str = cur;
    return 0;
}

/****************************************************************
 *								*
 *	XML Schema Dates/Times Datatypes Handling		*
 *								*
 ****************************************************************/

/**
 * PARSE_DIGITS:
 * @num:  the integer to fill in
 * @cur:  an #xmlChar *
 * @num_type: an integer flag
 *
 * Parses a digits integer and updates @num with the value. @cur is
 * updated to point just after the integer.
 * In case of error, @num_type is set to -1, values of @num and
 * @cur are undefined.
 */
#define PARSE_DIGITS(num, cur, num_type)	                \
	if ((*cur < '0') || (*cur > '9'))			\
	    num_type = -1;					\
        else                                                    \
	    while ((*cur >= '0') && (*cur <= '9')) {		\
	        num = num * 10 + (*cur - '0');		        \
	        cur++;                                          \
            }

/**
 * PARSE_NUM:
 * @num:  the double to fill in
 * @cur:  an #xmlChar *
 * @num_type: an integer flag
 *
 * Parses a float or integer and updates @num with the value. @cur is
 * updated to point just after the number. If the number is a float,
 * then it must have an integer part and a decimal part; @num_type will
 * be set to 1. If there is no decimal part, @num_type is set to zero.
 * In case of error, @num_type is set to -1, values of @num and
 * @cur are undefined.
 */
#define PARSE_NUM(num, cur, num_type)				\
        num = 0;                                                \
	PARSE_DIGITS(num, cur, num_type);	                \
	if (!num_type && (*cur == '.')) {			\
	    double mult = 1;				        \
	    cur++;						\
	    if ((*cur < '0') || (*cur > '9'))			\
		num_type = -1;					\
            else                                                \
                num_type = 1;                                   \
	    while ((*cur >= '0') && (*cur <= '9')) {		\
		mult /= 10;					\
		num += (*cur - '0') * mult;			\
		cur++;						\
	    }							\
	}

/**
 * xmlSchemaValidateDates:
 * @type: the predefined type
 * @dateTime:  string to analyze
 * @val:  the return computed value
 *
 * Check that @dateTime conforms to the lexical space of one of the date types.
 * if true a value is computed and returned in @val.
 *
 * Returns 0 if this validates, a positive error code number otherwise
 *         and -1 in case of internal or API error.
 */
static int
xmlSchemaValidateDates (xmlSchemaTypePtr type ATTRIBUTE_UNUSED,
	                const xmlChar *dateTime, xmlSchemaValPtr *val) {
    xmlSchemaValPtr dt;
    int ret;
    const xmlChar *cur = dateTime;

#define RETURN_TYPE_IF_VALID(t)					\
    if (IS_TZO_CHAR(*cur)) {					\
	ret = _xmlSchemaParseTimeZone(&(dt->value.date), &cur);	\
	if (ret == 0) {						\
	    if (*cur != 0)					\
		goto error;					\
	    dt->type = t;					\
            if (val != NULL)                                    \
                *val = dt;                                      \
	    return 0;						\
	}							\
    }

    if (dateTime == NULL)
	return -1;

    if ((*cur != '-') && (*cur < '0') && (*cur > '9'))
	return 1;

    dt = xmlSchemaNewValue(XML_SCHEMAS_UNKNOWN);
    if (dt == NULL)
	return -1;

    if ((cur[0] == '-') && (cur[1] == '-')) {
	/*
	 * It's an incomplete date (xs:gMonthDay, xs:gMonth or
	 * xs:gDay)
	 */
	cur += 2;

	/* is it an xs:gDay? */
	if (*cur == '-') {
	  ++cur;
	    ret = _xmlSchemaParseGDay(&(dt->value.date), &cur);
	    if (ret != 0)
		goto error;

	    RETURN_TYPE_IF_VALID(XML_SCHEMAS_GDAY);

	    goto error;
	}

	/*
	 * it should be an xs:gMonthDay or xs:gMonth
	 */
	ret = _xmlSchemaParseGMonth(&(dt->value.date), &cur);
	if (ret != 0)
	    goto error;

	if (*cur != '-')
	    goto error;
	cur++;

	/* is it an xs:gMonth? */
	if (*cur == '-') {
	    cur++;
	    RETURN_TYPE_IF_VALID(XML_SCHEMAS_GMONTH);
	    goto error;
	}

	/* it should be an xs:gMonthDay */
	ret = _xmlSchemaParseGDay(&(dt->value.date), &cur);
	if (ret != 0)
	    goto error;

	RETURN_TYPE_IF_VALID(XML_SCHEMAS_GMONTHDAY);

	goto error;
    }

    /*
     * It's a right-truncated date or an xs:time.
     * Try to parse an xs:time then fallback on right-truncated dates.
     */
    if ((*cur >= '0') && (*cur <= '9')) {
	ret = _xmlSchemaParseTime(&(dt->value.date), &cur);
	if (ret == 0) {
	    /* it's an xs:time */
	    RETURN_TYPE_IF_VALID(XML_SCHEMAS_TIME);
	}
    }

    /* fallback on date parsing */
    cur = dateTime;

    ret = _xmlSchemaParseGYear(&(dt->value.date), &cur);
    if (ret != 0)
	goto error;

    /* is it an xs:gYear? */
    RETURN_TYPE_IF_VALID(XML_SCHEMAS_GYEAR);

    if (*cur != '-')
	goto error;
    cur++;

    ret = _xmlSchemaParseGMonth(&(dt->value.date), &cur);
    if (ret != 0)
	goto error;

    /* is it an xs:gYearMonth? */
    RETURN_TYPE_IF_VALID(XML_SCHEMAS_GYEARMONTH);

    if (*cur != '-')
	goto error;
    cur++;

    ret = _xmlSchemaParseGDay(&(dt->value.date), &cur);
    if ((ret != 0) || !VALID_DATE((&(dt->value.date))))
	goto error;

    /* is it an xs:date? */
    RETURN_TYPE_IF_VALID(XML_SCHEMAS_DATE);

    if (*cur != 'T')
	goto error;
    cur++;

    /* it should be an xs:dateTime */
    ret = _xmlSchemaParseTime(&(dt->value.date), &cur);
    if (ret != 0)
	goto error;

    ret = _xmlSchemaParseTimeZone(&(dt->value.date), &cur);
    if ((ret != 0) || (*cur != 0) || !VALID_DATETIME((&(dt->value.date))))
	goto error;

    dt->type = XML_SCHEMAS_DATETIME;

    if (val != NULL)
        *val = dt;

    return 0;

error:
    if (dt != NULL)
	xmlSchemaFreeValue(dt);
    return 1;
}

/**
 * xmlSchemaValidateDuration:
 * @type: the predefined type
 * @duration:  string to analyze
 * @val:  the return computed value
 *
 * Check that @duration conforms to the lexical space of the duration type.
 * if true a value is computed and returned in @val.
 *
 * Returns 0 if this validates, a positive error code number otherwise
 *         and -1 in case of internal or API error.
 */
static int
xmlSchemaValidateDuration (xmlSchemaTypePtr type ATTRIBUTE_UNUSED,
	                   const xmlChar *duration, xmlSchemaValPtr *val) {
    const xmlChar  *cur = duration;
    xmlSchemaValPtr dur;
    int isneg = 0;
    unsigned int seq = 0;

    if (duration == NULL)
	return -1;

    if (*cur == '-') {
        isneg = 1;
        cur++;
    }

    /* duration must start with 'P' (after sign) */
    if (*cur++ != 'P')
	return 1;

    dur = xmlSchemaNewValue(XML_SCHEMAS_DURATION);
    if (dur == NULL)
	return -1;

    while (*cur != 0) {
        double         num;
        int            num_type = 0;  /* -1 = invalid, 0 = int, 1 = floating */
        const xmlChar  desig[] = {'Y', 'M', 'D', 'H', 'M', 'S'};
        const double   multi[] = { 0.0, 0.0, 86400.0, 3600.0, 60.0, 1.0, 0.0};

        /* input string should be empty or invalid date/time item */
        if (seq >= sizeof(desig))
            goto error;

        /* T designator must be present for time items */
        if (*cur == 'T') {
            if (seq <= 3) {
                seq = 3;
                cur++;
            } else
                return 1;
        } else if (seq == 3)
            goto error;

        /* parse the number portion of the item */
        PARSE_NUM(num, cur, num_type);

        if ((num_type == -1) || (*cur == 0))
            goto error;

        /* update duration based on item type */
        while (seq < sizeof(desig)) {
            if (*cur == desig[seq]) {

                /* verify numeric type; only seconds can be float */
                if ((num_type != 0) && (seq < (sizeof(desig)-1)))
                    goto error;

                switch (seq) {
                    case 0:
                        dur->value.dur.mon = (long)num * 12;
                        break;
                    case 1:
                        dur->value.dur.mon += (long)num;
                        break;
                    default:
                        /* convert to seconds using multiplier */
                        dur->value.dur.sec += num * multi[seq];
                        seq++;
                        break;
                }

                break;          /* exit loop */
            }
            /* no date designators found? */
            if (++seq == 3)
                goto error;
        }
        cur++;
    }

    if (isneg) {
        dur->value.dur.mon = -dur->value.dur.mon;
        dur->value.dur.day = -dur->value.dur.day;
        dur->value.dur.sec = -dur->value.dur.sec;
    }

    if (val != NULL)
        *val = dur;

    return 0;

error:
    if (dur != NULL)
	xmlSchemaFreeValue(dur);
    return 1;
}


/**
 * xmlSchemaValidatePredefinedType:
 * @type: the predefined type
 * @value: the value to check
 * @val:  the return computed value
 *
 * Check that a value conforms to the lexical space of the predefined type.
 * if true a value is computed and returned in @val.
 *
 * Returns 0 if this validates, a positive error code number otherwise
 *         and -1 in case of internal or API error.
 */
int
xmlSchemaValidatePredefinedType(xmlSchemaTypePtr type, const xmlChar *value,
	                        xmlSchemaValPtr *val) {
    xmlSchemaValPtr v;
    int ret;

    if (xmlSchemaTypesInitialized == 0)
	return(-1);
    if (type == NULL)
	return(-1);

    if (val != NULL)
	*val = NULL;
    if (type == xmlSchemaTypeStringDef) { 
	return(0);
    } else if (type == xmlSchemaTypeAnyTypeDef) {
	return(0);
    } else if (type == xmlSchemaTypeAnySimpleTypeDef) {
	return(0);
    } else if (type == xmlSchemaTypeNmtoken) {
	if (xmlValidateNmtokenValue(value))
	    return(0);
	return(1);
    } else if (type == xmlSchemaTypeDecimalDef) {
	const xmlChar *cur = value, *tmp;
	int frac = 0, len, neg = 0;
	unsigned long base = 0;
	if (cur == NULL)
	    return(1);
	if (*cur == '+')
	    cur++;
	else if (*cur == '-') {
	    neg = 1;
	    cur++;
	}
	tmp = cur;
	while ((*cur >= '0') && (*cur <= '9')) {
	    base = base * 10 + (*cur - '0');
	    cur++;
	}
	len = cur - tmp;
	if (*cur == '.') {
	    cur++;
	    tmp = cur;
	    while ((*cur >= '0') && (*cur <= '9')) {
		base = base * 10 + (*cur - '0');
		cur++;
	    }
	    frac = cur - tmp;
	}
	if (*cur != 0)
	    return(1);
	if (val != NULL) {
	    v = xmlSchemaNewValue(XML_SCHEMAS_DECIMAL);
	    if (v != NULL) {
		v->value.decimal.base = base;
		v->value.decimal.sign = neg;
		v->value.decimal.frac = frac;
		v->value.decimal.total = frac + len;
		*val = v;
	    }
	}
	return(0);
    } else if (type == xmlSchemaTypeDurationDef) {
        return xmlSchemaValidateDuration(type, value, val);
    } else if ((type == xmlSchemaTypeDatetimeDef) ||
               (type == xmlSchemaTypeTimeDef) ||
               (type == xmlSchemaTypeDateDef) ||
               (type == xmlSchemaTypeGYearDef) ||
               (type == xmlSchemaTypeGYearMonthDef) ||
               (type == xmlSchemaTypeGMonthDef) ||
               (type == xmlSchemaTypeGMonthDayDef) ||
               (type == xmlSchemaTypeGDayDef))         {
        return xmlSchemaValidateDates(type, value, val);
    } else if (type == xmlSchemaTypePositiveIntegerDef) {
	const xmlChar *cur = value;
	unsigned long base = 0;
	int total = 0;
	if (cur == NULL)
	    return(1);
	if (*cur == '+')
	    cur++;
	while ((*cur >= '0') && (*cur <= '9')) {
	    base = base * 10 + (*cur - '0');
	    total++;
	    cur++;
	}
	if (*cur != 0)
	    return(1);
	if (val != NULL) {
	    v = xmlSchemaNewValue(XML_SCHEMAS_DECIMAL);
	    if (v != NULL) {
		v->value.decimal.base = base;
		v->value.decimal.sign = 0;
		v->value.decimal.frac = 0;
		v->value.decimal.total = total;
		*val = v;
	    }
	}
	return(0);
    } else if (type == xmlSchemaTypeNonNegativeIntegerDef) {
	const xmlChar *cur = value;
	unsigned long base = 0;
	int total = 0;
	int sign = 0;
	if (cur == NULL)
	    return(1);
	if (*cur == '-') {
	    sign = 1;
	    cur++;
	} else if (*cur == '+')
	    cur++;
	while ((*cur >= '0') && (*cur <= '9')) {
	    base = base * 10 + (*cur - '0');
	    total++;
	    cur++;
	}
	if (*cur != 0)
	    return(1);
	if ((sign == 1) && (base != 0))
	    return(1);
	if (val != NULL) {
	    v = xmlSchemaNewValue(XML_SCHEMAS_DECIMAL);
	    if (v != NULL) {
		v->value.decimal.base = base;
		v->value.decimal.sign = 0;
		v->value.decimal.frac = 0;
		v->value.decimal.total = total;
		*val = v;
	    }
	}
	return(0);
    } else if (type == xmlSchemaTypeIntDef) {
	const xmlChar *cur = value;
	unsigned long base = 0;
	int total = 0;
	int sign = 0;
	if (cur == NULL)
	    return(1);
	if (*cur == '-') {
	    sign = 1;
	    cur++;
	} else if (*cur == '+')
	    cur++;
	while (*cur == '0') {
	    total++;
	    cur++;
	}
	while ((*cur >= '0') && (*cur <= '9')) {
	    base = base * 10 + (*cur - '0');
	    total++;
	    cur++;
	}
	if (*cur != 0)
	    return(1);
	if ((sign == 1) && (total == 0))
	    return(1);
	if (val != NULL) {
	    v = xmlSchemaNewValue(XML_SCHEMAS_INT);
	    if (v != NULL) {
		v->value.decimal.base = base;
		v->value.decimal.sign = sign;
		v->value.decimal.frac = 0;
		v->value.decimal.total = total;
		*val = v;
	    }
	}
	return(0);
    } else if ((type == xmlSchemaTypeFloatDef) ||
	       (type == xmlSchemaTypeDoubleDef)) {
	const xmlChar *cur = value;
	int neg = 0;
	if (cur == NULL)
	    return(1);
	if ((cur[0] == 'N') && (cur[1] == 'a') && (cur[2] == 'N')) {
	    cur += 3;
	    if (*cur != 0)
		return(1);
	    if (val != NULL) {
		if (type == xmlSchemaTypeFloatDef) {
		    v = xmlSchemaNewValue(XML_SCHEMAS_FLOAT);
		    if (v != NULL) {
			v->value.f = (float) xmlXPathNAN;
		    } else {
			xmlSchemaFreeValue(v);
			return(-1);
		    }
		} else {
		    v = xmlSchemaNewValue(XML_SCHEMAS_DOUBLE);
		    if (v != NULL) {
			v->value.d = xmlXPathNAN;
		    } else {
			xmlSchemaFreeValue(v);
			return(-1);
		    }
		}
		*val = v;
	    }
	    return(0);
	}
	if (*cur == '+')
	    cur++;
	else if (*cur == '-') {
	    neg = 1;
	    cur++;
	}
	if (cur[0] == 0)
	    return(1);
	if ((cur[0] == 'I') && (cur[1] == 'N') && (cur[2] == 'F')) {
	    cur += 3;
	    if (*cur != 0)
		return(1);
	    if (val != NULL) {
		if (type == xmlSchemaTypeFloatDef) {
		    v = xmlSchemaNewValue(XML_SCHEMAS_FLOAT);
		    if (v != NULL) {
			if (neg)
			    v->value.f = (float) xmlXPathNINF;
			else
			    v->value.f = (float) xmlXPathPINF;
		    } else {
			xmlSchemaFreeValue(v);
			return(-1);
		    }
		} else {
		    v = xmlSchemaNewValue(XML_SCHEMAS_DOUBLE);
		    if (v != NULL) {
			if (neg)
			    v->value.d = xmlXPathNINF;
			else
			    v->value.d = xmlXPathPINF;
		    } else {
			xmlSchemaFreeValue(v);
			return(-1);
		    }
		}
		*val = v;
	    }
	    return(0);
	}
	while ((*cur >= '0') && (*cur <= '9')) {
	    cur++;
	}
	if (*cur == '.') {
	    cur++;
	    while ((*cur >= '0') && (*cur <= '9')) 
		cur++;
	}
	if ((*cur == 'e') || (*cur == 'E')) {
	    cur++;
	    if (*cur == '-')
		cur++;
	    while ((*cur >= '0') && (*cur <= '9')) 
		cur++;
	}
	if (*cur != 0)
	    return(1);
	if (val != NULL) {
	    if (type == xmlSchemaTypeFloatDef) {
		v = xmlSchemaNewValue(XML_SCHEMAS_FLOAT);
		if (v != NULL) {
		    if (sscanf((const char *)value, "%f", &(v->value.f))==1) {
			*val = v;
		    } else {
			xmlGenericError(xmlGenericErrorContext,
				"failed to scanf float %s\n", value);
			xmlSchemaFreeValue(v);
			return(1);
		    }
		} else {
		    return(-1);
		}
	    } else {
		v = xmlSchemaNewValue(XML_SCHEMAS_DOUBLE);
		if (v != NULL) {
		    if (sscanf((const char *)value, "%lf", &(v->value.d))==1) {
			*val = v;
		    } else {
			xmlGenericError(xmlGenericErrorContext,
				"failed to scanf double %s\n", value);
			xmlSchemaFreeValue(v);
			return(1);
		    }
		} else {
		    return(-1);
		}
	    }
	}
	return(0);
    } else if (type == xmlSchemaTypeNameDef) {
	ret = xmlValidateName(value, 1);
	if ((ret == 0) && (val != NULL)) {
	    TODO;
	}
	return(ret);
    } else if (type == xmlSchemaTypeQNameDef) {
	ret = xmlValidateQName(value, 1);
	if ((ret == 0) && (val != NULL)) {
	    TODO;
	}
	return(ret);
    } else if (type == xmlSchemaTypeNCNameDef) {
	ret = xmlValidateNCName(value, 1);
	if ((ret == 0) && (val != NULL)) {
	    TODO;
	}
	return(ret);
    } else if (type == xmlSchemaTypeAnyURIDef) {
	xmlURIPtr uri;

	uri = xmlParseURI((const char *) value);
	if (uri == NULL)
	    return(1);
	if (val != NULL) {
	    TODO;
	}
	xmlFreeURI(uri);
	return(0);
    } else if (type == xmlSchemaTypeBooleanDef) {
	const xmlChar *cur = value;

	if ((cur[0] == '0') && (cur[1] == 0))
	    ret = 0;
	else if ((cur[0] == '1') && (cur[1] == 0))
	    ret = 1;
	else if ((cur[0] == 't') && (cur[1] == 'r') && (cur[2] == 'u') &&
	         (cur[3] == 'e') && (cur[4] == 0))
	    ret = 1;
	else if ((cur[0] == 'f') && (cur[1] == 'a') && (cur[2] == 'l') &&
	         (cur[3] == 's') && (cur[4] == 'e') && (cur[5] == 0))
	    ret = 0;
	else 
	    return(1);
	if (val != NULL) {
	    v = xmlSchemaNewValue(XML_SCHEMAS_BOOLEAN);
	    if (v != NULL) {
		v->value.b = ret;
		*val = v;
	    } else {
		return(-1);
	    }
	}
	return(0);
    } else {
	TODO
	return(0);
    }
    return(-1);
}

/**
 * xmlSchemaCompareDecimals:
 * @x:  a first decimal value
 * @y:  a second decimal value
 *
 * Compare 2 decimals
 *
 * Returns -1 if x < y, 0 if x == y, 1 if x > y and -2 in case of error
 */
static int
xmlSchemaCompareDecimals(xmlSchemaValPtr x, xmlSchemaValPtr y)
{
    xmlSchemaValPtr swp;
    int order = 1;
    unsigned long tmp;

    if ((x->value.decimal.sign) && (x->value.decimal.sign))
        order = -1;
    else if (x->value.decimal.sign)
        return (-1);
    else if (y->value.decimal.sign)
        return (1);
    if (x->value.decimal.frac == y->value.decimal.frac) {
        if (x->value.decimal.base < y->value.decimal.base)
            return (-1);
        return (x->value.decimal.base > y->value.decimal.base);
    }
    if (y->value.decimal.frac > x->value.decimal.frac) {
        swp = y;
        y = x;
        x = swp;
        order = -order;
    }
    tmp =
        x->value.decimal.base / powten[x->value.decimal.frac -
                                       y->value.decimal.frac];
    if (tmp > y->value.decimal.base)
        return (order);
    if (tmp < y->value.decimal.base)
        return (-order);
    tmp =
        y->value.decimal.base * powten[x->value.decimal.frac -
                                       y->value.decimal.frac];
    if (x->value.decimal.base < tmp)
        return (-order);
    if (x->value.decimal.base == tmp)
        return (0);
    return (order);
}

/**
 * xmlSchemaCompareDurations:
 * @x:  a first duration value
 * @y:  a second duration value
 *
 * Compare 2 durations
 *
 * Returns -1 if x < y, 0 if x == y, 1 if x > y, 2 if x <> y, and -2 in
 * case of error
 */
static int
xmlSchemaCompareDurations(xmlSchemaValPtr x, xmlSchemaValPtr y)
{
    long carry, mon, day;
    double sec;
    long xmon, xday, myear, lyear, minday, maxday;
    static const long dayRange [2][12] = {
        { 0, 28, 59, 89, 120, 150, 181, 212, 242, 273, 303, 334, },
        { 0, 31, 62, 92, 123, 153, 184, 215, 245, 276, 306, 337} };

    if ((x == NULL) || (y == NULL))
        return -2;

    /* months */
    mon = x->value.dur.mon - y->value.dur.mon;

    /* seconds */
    sec = x->value.dur.sec - y->value.dur.sec;
    carry = (long)sec / SECS_PER_DAY;
    sec -= (double)(carry * SECS_PER_DAY);

    /* days */
    day = x->value.dur.day - y->value.dur.day + carry;

    /* easy test */
    if (mon == 0) {
        if (day == 0)
            if (sec == 0.0)
                return 0;
            else if (sec < 0.0)
                return -1;
            else
                return 1;
        else if (day < 0)
            return -1;
        else
            return 1;
    }

    if (mon > 0) {
        if ((day >= 0) && (sec >= 0.0))
            return 1;
        else {
            xmon = mon;
            xday = -day;
        }
    } else if ((day <= 0) && (sec <= 0.0)) {
        return -1;
    } else {
        xmon = -mon;
        xday = day;
    }

    myear = xmon / 12;
    lyear = myear / 4;
    minday = (myear * 365) + (lyear != 0 ? lyear - 1 : 0);
    maxday = (myear * 365) + (lyear != 0 ? lyear + 1 : 0);
        
    xmon = xmon % 12;
    minday += dayRange[0][xmon];
    maxday += dayRange[1][xmon];

    if (maxday < xday)
        return 1;
    else if (minday > xday)
        return -1;

    /* indeterminate */
    return 2;
}

/*
 * macros for adding date/times and durations
 */
#define FQUOTIENT(a,b)                  (floor(((double)a/(double)b)))
#define MODULO(a,b)                     (a - FQUOTIENT(a,b) * b)
#define FQUOTIENT_RANGE(a,low,high)     (FQUOTIENT((a-low),(high-low)))
#define MODULO_RANGE(a,low,high)        ((MODULO((a-low),(high-low)))+low)

/**
 * _xmlSchemaDateAdd:
 * @dt: an #xmlSchemaValPtr
 * @dur: an #xmlSchemaValPtr of type #XS_DURATION
 *
 * Compute a new date/time from @dt and @dur. This function assumes @dt
 * is either #XML_SCHEMAS_DATETIME, #XML_SCHEMAS_DATE, #XML_SCHEMAS_GYEARMONTH,
 * or #XML_SCHEMAS_GYEAR.
 *
 * Returns date/time pointer or NULL.
 */
static xmlSchemaValPtr
_xmlSchemaDateAdd (xmlSchemaValPtr dt, xmlSchemaValPtr dur)
{
    xmlSchemaValPtr ret;
    long carry, tempdays, temp;
    xmlSchemaValDatePtr r, d;
    xmlSchemaValDurationPtr u;

    if ((dt == NULL) || (dur == NULL))
        return NULL;

    ret = xmlSchemaNewValue(dt->type);
    if (ret == NULL)
        return NULL;

    r = &(ret->value.date);
    d = &(dt->value.date);
    u = &(dur->value.dur);

    /* normalization */
    if (d->mon == 0)
        d->mon = 1;

    /* normalize for time zone offset */
    u->sec -= (d->tzo * 60);
    d->tzo = 0;

    /* normalization */
    if (d->day == 0)
        d->day = 1;

    /* month */
    carry  = d->mon + u->mon;
    r->mon = MODULO_RANGE(carry, 1, 13);
    carry  = FQUOTIENT_RANGE(carry, 1, 13);

    /* year (may be modified later) */
    r->year = d->year + carry;
    if (r->year == 0) {
        if (d->year > 0)
            r->year--;
        else
            r->year++;
    }

    /* time zone */
    r->tzo     = d->tzo;
    r->tz_flag = d->tz_flag;

    /* seconds */
    r->sec = d->sec + u->sec;
    carry  = FQUOTIENT((long)r->sec, 60);
    if (r->sec != 0.0) {
        r->sec = MODULO(r->sec, 60.0);
    }

    /* minute */
    carry += d->min;
    r->min = MODULO(carry, 60);
    carry  = FQUOTIENT(carry, 60);

    /* hours */
    carry  += d->hour;
    r->hour = MODULO(carry, 24);
    carry   = FQUOTIENT(carry, 24);

    /*
     * days
     * Note we use tempdays because the temporary values may need more
     * than 5 bits
     */
    if ((VALID_YEAR(r->year)) && (VALID_MONTH(r->mon)) &&
                  (d->day > MAX_DAYINMONTH(r->year, r->mon)))
        tempdays = MAX_DAYINMONTH(r->year, r->mon);
    else if (d->day < 1)
        tempdays = 1;
    else
        tempdays = d->day;

    tempdays += u->day + carry;

    while (1) {
        if (tempdays < 1) {
            long tmon = MODULO_RANGE(r->mon-1, 1, 13);
            long tyr  = r->year + FQUOTIENT_RANGE(r->mon-1, 1, 13);
            if (tyr == 0)
                tyr--;
            tempdays += MAX_DAYINMONTH(tyr, tmon);
            carry = -1;
        } else if (tempdays > MAX_DAYINMONTH(r->year, r->mon)) {
            tempdays = tempdays - MAX_DAYINMONTH(r->year, r->mon);
            carry = 1;
        } else
            break;

        temp = r->mon + carry;
        r->mon = MODULO_RANGE(temp, 1, 13);
        r->year = r->year + FQUOTIENT_RANGE(temp, 1, 13);
        if (r->year == 0) {
            if (temp < 1)
                r->year--;
            else
                r->year++;
	}
    }
    
    r->day = tempdays;

    /*
     * adjust the date/time type to the date values
     */
    if (ret->type != XML_SCHEMAS_DATETIME) {
        if ((r->hour) || (r->min) || (r->sec))
            ret->type = XML_SCHEMAS_DATETIME;
        else if (ret->type != XML_SCHEMAS_DATE) {
            if ((r->mon != 1) && (r->day != 1))
                ret->type = XML_SCHEMAS_DATE;
            else if ((ret->type != XML_SCHEMAS_GYEARMONTH) && (r->mon != 1))
                ret->type = XML_SCHEMAS_GYEARMONTH;
        }
    }

    return ret;
}

/**
 * xmlSchemaDupVal:
 * @v: value to duplicate
 *
 * returns a duplicated value.
 */
static xmlSchemaValPtr
xmlSchemaDupVal (xmlSchemaValPtr v)
{
    xmlSchemaValPtr ret = xmlSchemaNewValue(v->type);
    if (ret == NULL)
        return ret;
    
    memcpy(ret, v, sizeof(xmlSchemaVal));
    return ret;
}

/**
 * xmlSchemaDateNormalize:
 * @dt: an #xmlSchemaValPtr
 *
 * Normalize @dt to GMT time.
 *
 */
static xmlSchemaValPtr
xmlSchemaDateNormalize (xmlSchemaValPtr dt, double offset)
{
    xmlSchemaValPtr dur, ret;

    if (dt == NULL)
        return NULL;

    if (((dt->type != XML_SCHEMAS_TIME) &&
         (dt->type != XML_SCHEMAS_DATETIME)) || (dt->value.date.tzo == 0))
        return xmlSchemaDupVal(dt);

    dur = xmlSchemaNewValue(XML_SCHEMAS_DURATION);
    if (dur == NULL)
        return NULL;

    dur->value.date.sec -= offset;

    ret = _xmlSchemaDateAdd(dt, dur);
    if (ret == NULL)
        return NULL;

    xmlSchemaFreeValue(dur);

    /* ret->value.date.tzo = 0; */
    return ret;
}

/**
 * _xmlSchemaDateCastYMToDays:
 * @dt: an #xmlSchemaValPtr
 *
 * Convert mon and year of @dt to total number of days. Take the 
 * number of years since (or before) 1 AD and add the number of leap
 * years. This is a function  because negative
 * years must be handled a little differently and there is no zero year.
 *
 * Returns number of days.
 */
static long
_xmlSchemaDateCastYMToDays (const xmlSchemaValPtr dt)
{
    long ret;

    if (dt->value.date.year < 0)
        ret = (dt->value.date.year * 365) +
              (((dt->value.date.year+1)/4)-((dt->value.date.year+1)/100)+
               ((dt->value.date.year+1)/400)) +
              DAY_IN_YEAR(0, dt->value.date.mon, dt->value.date.year);
    else
        ret = ((dt->value.date.year-1) * 365) +
              (((dt->value.date.year-1)/4)-((dt->value.date.year-1)/100)+
               ((dt->value.date.year-1)/400)) +
              DAY_IN_YEAR(0, dt->value.date.mon, dt->value.date.year);

    return ret;
}

/**
 * TIME_TO_NUMBER:
 * @dt:  an #xmlSchemaValPtr
 *
 * Calculates the number of seconds in the time portion of @dt.
 *
 * Returns seconds.
 */
#define TIME_TO_NUMBER(dt)                              \
    ((double)((dt->value.date.hour * SECS_PER_HOUR) +   \
              (dt->value.date.min * SECS_PER_MIN)) + dt->value.date.sec)

/**
 * xmlSchemaCompareDates:
 * @x:  a first date/time value
 * @y:  a second date/time value
 *
 * Compare 2 date/times
 *
 * Returns -1 if x < y, 0 if x == y, 1 if x > y, 2 if x <> y, and -2 in
 * case of error
 */
static int
xmlSchemaCompareDates (xmlSchemaValPtr x, xmlSchemaValPtr y)
{
    unsigned char xmask, ymask, xor_mask, and_mask;
    xmlSchemaValPtr p1, p2, q1, q2;
    long p1d, p2d, q1d, q2d;

    if ((x == NULL) || (y == NULL))
        return -2;

    if (x->value.date.tz_flag) {

        if (!y->value.date.tz_flag) {
            p1 = xmlSchemaDateNormalize(x, 0);
            p1d = _xmlSchemaDateCastYMToDays(p1) + p1->value.date.day;
            /* normalize y + 14:00 */
            q1 = xmlSchemaDateNormalize(y, (14 * SECS_PER_HOUR));

            q1d = _xmlSchemaDateCastYMToDays(q1) + q1->value.date.day;
            if (p1d < q1d) {
		xmlSchemaFreeValue(p1);
		xmlSchemaFreeValue(q1);
                return -1;
	    } else if (p1d == q1d) {
                double sec;

                sec = TIME_TO_NUMBER(p1) - TIME_TO_NUMBER(q1);
                if (sec < 0.0) {
		    xmlSchemaFreeValue(p1);
		    xmlSchemaFreeValue(q1);
                    return -1;
		} else {
                    /* normalize y - 14:00 */
                    q2 = xmlSchemaDateNormalize(y, -(14 * SECS_PER_HOUR));
                    q2d = _xmlSchemaDateCastYMToDays(q2) + q2->value.date.day;
		    xmlSchemaFreeValue(p1);
		    xmlSchemaFreeValue(q1);
		    xmlSchemaFreeValue(q2);
                    if (p1d > q2d)
                        return 1;
                    else if (p1d == q2d) {
                        sec = TIME_TO_NUMBER(p1) - TIME_TO_NUMBER(q2);
                        if (sec > 0.0)
                            return 1;
                        else
                            return 2; /* indeterminate */
                    }
                }
            } else {
		xmlSchemaFreeValue(p1);
		xmlSchemaFreeValue(q1);
	    }
        }
    } else if (y->value.date.tz_flag) {
        q1 = xmlSchemaDateNormalize(y, 0);
        q1d = _xmlSchemaDateCastYMToDays(q1) + q1->value.date.day;

        /* normalize x - 14:00 */
        p1 = xmlSchemaDateNormalize(x, -(14 * SECS_PER_HOUR));
        p1d = _xmlSchemaDateCastYMToDays(p1) + p1->value.date.day;

        if (p1d < q1d) {
	    xmlSchemaFreeValue(p1);
	    xmlSchemaFreeValue(q1);
            return -1;
	} else if (p1d == q1d) {
            double sec;

            sec = TIME_TO_NUMBER(p1) - TIME_TO_NUMBER(q1);
            if (sec < 0.0) {
		xmlSchemaFreeValue(p1);
		xmlSchemaFreeValue(q1);
                return -1;
	    } else {
                /* normalize x + 14:00 */
                p2 = xmlSchemaDateNormalize(x, (14 * SECS_PER_HOUR));
                p2d = _xmlSchemaDateCastYMToDays(p2) + p2->value.date.day;

		xmlSchemaFreeValue(p1);
		xmlSchemaFreeValue(q1);
		xmlSchemaFreeValue(p2);
                if (p2d > q1d)
                    return 1;
                else if (p2d == q1d) {
                    sec = TIME_TO_NUMBER(p2) - TIME_TO_NUMBER(q1);
                    if (sec > 0.0)
                        return 1;
                    else
                        return 2; /* indeterminate */
                }
            }
	} else {
	    xmlSchemaFreeValue(p1);
	    xmlSchemaFreeValue(q1);
        }
    }

    /*
     * if the same type then calculate the difference
     */
    if (x->type == y->type) {
        q1 = xmlSchemaDateNormalize(y, 0);
        q1d = _xmlSchemaDateCastYMToDays(q1) + q1->value.date.day;

        p1 = xmlSchemaDateNormalize(x, 0);
        p1d = _xmlSchemaDateCastYMToDays(p1) + p1->value.date.day;

        if (p1d < q1d) {
	    xmlSchemaFreeValue(p1);
	    xmlSchemaFreeValue(q1);
            return -1;
	} else if (p1d > q1d) {
	    xmlSchemaFreeValue(p1);
	    xmlSchemaFreeValue(q1);
            return 1;
	} else {
            double sec;

            sec = TIME_TO_NUMBER(p1) - TIME_TO_NUMBER(q1);
	    xmlSchemaFreeValue(p1);
	    xmlSchemaFreeValue(q1);
            if (sec < 0.0)
                return -1;
            else if (sec > 0.0)
                return 1;
            
        }
        return 0;
    }

    switch (x->type) {
        case XML_SCHEMAS_DATETIME:
            xmask = 0xf;
            break;
        case XML_SCHEMAS_DATE:
            xmask = 0x7;
            break;
        case XML_SCHEMAS_GYEAR:
            xmask = 0x1;
            break;
        case XML_SCHEMAS_GMONTH:
            xmask = 0x2;
            break;
        case XML_SCHEMAS_GDAY:
            xmask = 0x3;
            break;
        case XML_SCHEMAS_GYEARMONTH:
            xmask = 0x3;
            break;
        case XML_SCHEMAS_GMONTHDAY:
            xmask = 0x6;
            break;
        case XML_SCHEMAS_TIME:
            xmask = 0x8;
            break;
        default:
            xmask = 0;
            break;
    }

    switch (y->type) {
        case XML_SCHEMAS_DATETIME:
            ymask = 0xf;
            break;
        case XML_SCHEMAS_DATE:
            ymask = 0x7;
            break;
        case XML_SCHEMAS_GYEAR:
            ymask = 0x1;
            break;
        case XML_SCHEMAS_GMONTH:
            ymask = 0x2;
            break;
        case XML_SCHEMAS_GDAY:
            ymask = 0x3;
            break;
        case XML_SCHEMAS_GYEARMONTH:
            ymask = 0x3;
            break;
        case XML_SCHEMAS_GMONTHDAY:
            ymask = 0x6;
            break;
        case XML_SCHEMAS_TIME:
            ymask = 0x8;
            break;
        default:
            ymask = 0;
            break;
    }

    xor_mask = xmask ^ ymask;           /* mark type differences */
    and_mask = xmask & ymask;           /* mark field specification */

    /* year */
    if (xor_mask & 1)
        return 2; /* indeterminate */
    else if (and_mask & 1) {
        if (x->value.date.year < y->value.date.year)
            return -1;
        else if (x->value.date.year > y->value.date.year)
            return 1;
    }

    /* month */
    if (xor_mask & 2)
        return 2; /* indeterminate */
    else if (and_mask & 2) {
        if (x->value.date.mon < y->value.date.mon)
            return -1;
        else if (x->value.date.mon > y->value.date.mon)
            return 1;
    }

    /* day */
    if (xor_mask & 4)
        return 2; /* indeterminate */
    else if (and_mask & 4) {
        if (x->value.date.day < y->value.date.day)
            return -1;
        else if (x->value.date.day > y->value.date.day)
            return 1;
    }

    /* time */
    if (xor_mask & 8)
        return 2; /* indeterminate */
    else if (and_mask & 8) {
        if (x->value.date.hour < y->value.date.hour)
            return -1;
        else if (x->value.date.hour > y->value.date.hour)
            return 1;
        else if (x->value.date.min < y->value.date.min)
            return -1;
        else if (x->value.date.min > y->value.date.min)
            return 1;
        else if (x->value.date.sec < y->value.date.sec)
            return -1;
        else if (x->value.date.sec > y->value.date.sec)
            return 1;
    }

    return 0;
}

/**
 * xmlSchemaCompareValues:
 * @x:  a first value
 * @y:  a second value
 *
 * Compare 2 values
 *
 * Returns -1 if x < y, 0 if x == y, 1 if x > y, 2 if x <> y, and -2 in
 * case of error
 */
static int
xmlSchemaCompareValues(xmlSchemaValPtr x, xmlSchemaValPtr y) {
    if ((x == NULL) || (y == NULL))
	return(-2);

    switch (x->type) {
	case XML_SCHEMAS_STRING:
	    TODO
	case XML_SCHEMAS_DECIMAL:
	    if (y->type == XML_SCHEMAS_DECIMAL)
		return(xmlSchemaCompareDecimals(x, y));
	    return(-2);
        case XML_SCHEMAS_DURATION:
	    if (y->type == XML_SCHEMAS_DURATION)
                return(xmlSchemaCompareDurations(x, y));
            return(-2);
        case XML_SCHEMAS_TIME:
        case XML_SCHEMAS_GDAY:
        case XML_SCHEMAS_GMONTH:
        case XML_SCHEMAS_GMONTHDAY:
        case XML_SCHEMAS_GYEAR:
        case XML_SCHEMAS_GYEARMONTH:
        case XML_SCHEMAS_DATE:
        case XML_SCHEMAS_DATETIME:
            if ((y->type == XML_SCHEMAS_DATETIME)  ||
                (y->type == XML_SCHEMAS_TIME)      ||
                (y->type == XML_SCHEMAS_GDAY)      ||
                (y->type == XML_SCHEMAS_GMONTH)    ||
                (y->type == XML_SCHEMAS_GMONTHDAY) ||
                (y->type == XML_SCHEMAS_GYEAR)     ||
                (y->type == XML_SCHEMAS_DATE)      ||
                (y->type == XML_SCHEMAS_GYEARMONTH))
                return (xmlSchemaCompareDates(x, y));

            return (-2);
	default:
	    TODO
    }
    return -2;
}

/**
 * xmlSchemaValidateFacet:
 * @base:  the base type
 * @facet:  the facet to check
 * @value:  the lexical repr of the value to validate
 * @val:  the precomputed value
 *
 * Check a value against a facet condition
 *
 * Returns 0 if the element is schemas valid, a positive error code
 *     number otherwise and -1 in case of internal or API error.
 */
int
xmlSchemaValidateFacet(xmlSchemaTypePtr base ATTRIBUTE_UNUSED,
	               xmlSchemaFacetPtr facet,
	               const xmlChar *value, xmlSchemaValPtr val)
{
    int ret;

    switch (facet->type) {
	case XML_SCHEMA_FACET_PATTERN:
	    ret = xmlRegexpExec(facet->regexp, value);
	    if (ret == 1)
		return(0);
	    if (ret == 0) {
		TODO /* error code */
		return(1);
	    }
	    return(ret);
	case XML_SCHEMA_FACET_MAXEXCLUSIVE:
	    ret = xmlSchemaCompareValues(val, facet->val);
	    if (ret == -2) {
		TODO /* error code */
		return(-1);
	    }
	    if (ret == -1)
		return(0);
	    /* error code */
	    return(1);
	case XML_SCHEMA_FACET_MAXINCLUSIVE:
	    ret = xmlSchemaCompareValues(val, facet->val);
	    if (ret == -2) {
		TODO /* error code */
		return(-1);
	    }
	    if ((ret == -1) || (ret == 0))
		return(0);
	    /* error code */
	    return(1);
	case XML_SCHEMA_FACET_MINEXCLUSIVE:
	    ret = xmlSchemaCompareValues(val, facet->val);
	    if (ret == -2) {
		TODO /* error code */
		return(-1);
	    }
	    if (ret == 1)
		return(0);
	    /* error code */
	    return(1);
	case XML_SCHEMA_FACET_MININCLUSIVE:
	    ret = xmlSchemaCompareValues(val, facet->val);
	    if (ret == -2) {
		TODO /* error code */
		return(-1);
	    }
	    if ((ret == 1) || (ret == 0))
		return(0);
	    /* error code */
	    return(1);
	case XML_SCHEMA_FACET_WHITESPACE:
	    TODO /* whitespaces */
	    return(0);
	case XML_SCHEMA_FACET_MAXLENGTH:
	    if ((facet->val != NULL) &&
		(facet->val->type == XML_SCHEMAS_DECIMAL) &&
		(facet->val->value.decimal.frac == 0)) {
		unsigned int len;

		if (facet->val->value.decimal.sign == 1)
		    return(1);
                len = xmlUTF8Strlen(value);
		if (len > facet->val->value.decimal.base)
		    return(1);
		return(0);
	    }
	    TODO /* error code */
	    return(1);
	case  XML_SCHEMA_FACET_ENUMERATION:
	    if ((facet->value != NULL) &&
		(xmlStrEqual(facet->value, value)))
		return(0);
	    return(1);
	default:
	    TODO
    }
    return(0);
}

#endif /* LIBXML_SCHEMAS_ENABLED */