/* Part of SWI-Prolog Author: Jan Wielemaker E-mail: J.Wielemaker@vu.nl WWW: http://www.swi-prolog.org Copyright (c) 2016, VU University Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define _CRT_SECURE_NO_WARNINGS 1 #include #include #include #include #define URL_xsd "http://www.w3.org/2001/XMLSchema#" #define issign(c) (c == '-' || c == '+') #define isdigit(c) (c >= '0' && c <= '9') #define isexp(c) (c == 'e' || c == 'E') #define isdot(c) (c == '.') /* BUG: goes wrong if locale is switched at runtime. But, doing this dynamically is a more than 100% overhead on xsd_number_string/2 and changing locale after process initialization is uncommon and a bad idea anyway. */ static int decimal_dot(void) { static int ddot = '\0'; if ( ddot ) return ddot; char buf[10]; sprintf(buf, "%f", 1.0); ddot = buf[1]; return ddot; } static foreign_t xsd_number_string(term_t number, term_t string) { char *in; size_t len; if ( PL_get_nchars(string, &len, &in, CVT_ATOM|CVT_STRING|CVT_LIST) ) { char *s = in; if ( strlen(s) == len ) /* no 0-characters */ { int isfloat = FALSE; int hasdot=FALSE; if ( strcmp(s, "NaN") != 0 ) { int decl = 0, dect = 0; if ( issign(*s) ) s++; /* [+-]? */ if ( strcmp(s, "INF") == 0 ) { isfloat = TRUE; goto ok; } while(isdigit(*s)) decl++, s++; /* [0-9]* */ if ( isdot(*s) ) /* [.]? */ { s++; isfloat = TRUE; hasdot = TRUE; while(isdigit(*s)) dect++, s++; /* [0-9]* */ } if ( decl+dect == 0 ) goto syntax_error; if ( isexp(*s) ) { int exp = 0; s++; isfloat = TRUE; if ( issign(*s) ) s++; /* [+-]? */ while(isdigit(*s)) exp++, s++; /* [0-9]+ */ if ( exp == 0 ) goto syntax_error; } if ( *s ) goto syntax_error; } else { isfloat = TRUE; } ok: if ( isfloat ) { int dot; int rc; if ( hasdot && (dot=decimal_dot()) != '.' ) { char fast[64]; char *fs = len < sizeof(fast) ? fast : malloc(len+1); char *o; char *end; if ( !fs ) return PL_resource_error("memory"); for(s=in,o=fs; *s; s++,o++) { if ( (*o=*s) == '.' ) *o = dot; } *o = '\0'; rc = PL_unify_float(number, strtod(fs, &end)); if ( fs != fast ) free(fs); assert(*end == '\0'); } else { char *end; rc = PL_unify_float(number, strtod(in, &end)); assert(*end == '\0'); } return rc; } else { term_t n = PL_new_term_ref(); return ( PL_chars_to_term(in, n) && PL_unify(number, n) ); } } else { syntax_error: return PL_syntax_error("xsd_number", NULL); } } else if ( PL_get_nchars(number, &len, &in, CVT_NUMBER) ) { if ( PL_is_float(number) ) { char buf[32]; char *s, *e; int exp_shift = 0; if ( len > 3 && strcmp(&in[len-3], "Inf") == 0 ) return PL_unify_chars(string, PL_STRING, (size_t)-1, in[0] == '-' ? "-INF" : "INF"); if ( len > 3 && strcmp(&in[len-3], "NaN") == 0 ) return PL_unify_chars(string, PL_STRING, (size_t)-1, "NaN"); assert(len < 32); strcpy(buf, in); s = buf; if ( s[0] == '-' ) s++; if ( s[0] == '0' ) { assert(s[1] == '.'); s += 2; if ( *s == '0' && s[1] ) { for(e=s; *e=='0'; e++) exp_shift--; memmove(&s[0], &s[-exp_shift], strlen(&s[-exp_shift])+1); } } else { char *dp = strchr(s, '.'); if ( dp-s > 1 ) { exp_shift = (int)(dp-s-1); memmove(&s[2], &s[1], exp_shift); s[1] = '.'; } } if ( (e=strchr(buf, 'e')) ) { *e++ = 'E'; if ( e[0] == '+' ) memmove(&e[0], &e[1], strlen(&e[1])+1); if ( exp_shift ) sprintf(e, "%d", atoi(e)+exp_shift); } else { e = &buf[strlen(buf)]; if ( exp_shift > 0 ) { while(e[-1] == '0' && e[-2] != '.') e--; } sprintf(e, "E%d", exp_shift); } return PL_unify_chars(string, PL_STRING, (size_t)-1, buf); } else { return PL_unify_chars(string, PL_STRING, len, in); } } else if ( !PL_is_variable(number) ) { return PL_type_error("number", number); } else { return PL_type_error("text", string); } } /******************************* * DATE AND TIME * *******************************/ static functor_t FUNCTOR_date3; static functor_t FUNCTOR_time3; static functor_t FUNCTOR_date_time6; static functor_t FUNCTOR_date_time7; static functor_t FUNCTOR_month_day2; static functor_t FUNCTOR_year_month2; static functor_t FUNCTOR_error2; static functor_t FUNCTOR_syntax_error1; static functor_t FUNCTOR_domain_error2; static functor_t FUNCTOR_xsd_time1; static atom_t URL_date; static atom_t URL_dateTime; static atom_t URL_gDay; static atom_t URL_gMonth; static atom_t URL_gMonthDay; static atom_t URL_gYear; static atom_t URL_gYearMonth; static atom_t URL_time; typedef struct { int hour; int minute; int sec_is_float; union { int i; double f; } second; } time; static int get_int_arg(term_t t, int n, int *ip) { term_t a = PL_new_term_ref(); _PL_get_arg(n, t, a); return PL_get_integer_ex(a, ip); } static int get_int_args(term_t t, int n, int *av) { term_t a = PL_new_term_ref(); int i; for(i=0; ihour) ) return FALSE; _PL_get_arg(offset+2, t, a); if ( !PL_get_integer_ex(a, &tm->minute) ) return FALSE; _PL_get_arg(offset+3, t, a); if ( PL_get_integer(a, &tm->second.i) ) { tm->sec_is_float = FALSE; } else if ( PL_get_float_ex(a, &tm->second.f) ) { tm->sec_is_float = TRUE; } else return FALSE; return TRUE; } typedef enum { END, INT2, INTYEAR, DECIMAL, MINUS, PLUS, COLON, TT, TZ } time_field; static int parse_date_parts(const char *in, int *av, size_t avlen) { size_t ca = 0; #define ADDINT(i) \ do { if ( ca < avlen ) { av[ca++] = (i); } else { return 1; } } while(0) #define DV(i) (in[i]-'0') while(*in) { if ( isdigit(in[0]) && isdigit(in[1]) ) { if ( isdigit(in[2]) && isdigit(in[3]) ) { int v = DV(0)*1000+DV(1)*100+DV(2)*10+DV(3); int n = 4; for(n=4; isdigit(in[n]); n++) { v = v*10+DV(n); } ADDINT(INTYEAR); ADDINT(v); in += n; } else { ADDINT(INT2); ADDINT(DV(0)*10+DV(1)); in += 2; } } else { switch(in[0]) { case '-': ADDINT(MINUS); in++; break; case '+': ADDINT(PLUS); in++; break; case ':': ADDINT(COLON); in++; break; case 'T': ADDINT(TT); in++; break; case 'Z': ADDINT(TZ); in++; break; case '.': ADDINT(DECIMAL); in++; { int v = 0; int dl = 0; if ( !isdigit(in[0]) ) return 2; while(in[0] == '0') { dl++; in++; } while(isdigit(in[0])) { v = v*10+DV(0); dl++; in++; } ADDINT(dl); ADDINT(v); break; } default: return 2; } } } ADDINT(END); #undef DV #undef ADDINT return 0; } static int int_domain(const char *domain, int i) { term_t t = PL_new_term_ref(); return ( PL_put_integer(t, i) && PL_domain_error(domain, t) ); } static int float_domain(const char *domain, double f) { term_t t = PL_new_term_ref(); return ( PL_put_float(t, f) && PL_domain_error(domain, t) ); } /* In XSD 1.0 the year property was not permitted to have the value 0. The year before the year 1 in the proleptic Gregorian calendar, traditionally referred to as 1 BC(E), was represented by a year value of -1, 2 BCE by -2, etc. Many, perhaps most, references to 1 BC(E) actually refer not to a year in the proleptic Gregorian calendar but to a year in the Julian or “old style” calendar; the two correspond approximately but not exactly to each other. Use the following preprocessor directive in order to make use of XSD 1.0 behavior: #define XSD_VERSION_1_0 In XSD 1.1, two changes are made in order to agree with existing usage. First, year is permitted to have the value 0. Second, the interpretation of year values is changed accordingly: a year value of 0 represents 1 BCE, -1 represents 2 BCE, etc. This representation simplifies interval arithmetic and leap-year calculation for dates before the common era (which may be why astronomers and others interested in such calculations with the proleptic Gregorian calendar have adopted it), and is consistent with the current edition of ISO 8601. */ static int valid_year(int i) { #if XSD_VERSION_1_0 if ( i != 0 ) return TRUE; return int_domain("year", i); #else return TRUE; #endif } static int mkyear(int v, int sign) { #if XSD_VERSION_1_0 if ( sign == 1 ) { if ( v == 0 ) return -1; return v; } return -(v+1); #else return v*sign; #endif } static int valid_month(int i) { if ( i >= 1 && i <= 12 ) return TRUE; return int_domain("month", i); } static int valid_day(int i) { if ( i >= 1 && i <= 31 ) return TRUE; return int_domain("day", i); } static int valid_hour(int i) { if ( i >= 0 && i <= 23 ) return TRUE; return int_domain("hour", i); } static int valid_minute(int i) { if ( i >= 0 && i <= 59 ) return TRUE; return int_domain("minute", i); } static int valid_second(int i) { if ( i >= 0 && i <= 59 ) return TRUE; return int_domain("second", i); } static int valid_second_f(double f) { if ( f >= 0.0 && f < 60.0 ) return TRUE; return float_domain("second", f); } static int valid_date(int v[3]) { return (valid_year(v[0]) && valid_month(v[1]) && valid_day(v[2])); } static int valid_time(const time *t) { if ( t->hour == 24 && t->minute == 0 && (t->sec_is_float ? t->second.f == 0.0 : t->second.i == 0) ) return TRUE; /* 24:00:00[.0+] */ if ( valid_hour(t->hour) && valid_minute(t->minute) ) { if ( t->sec_is_float ) return valid_second_f(t->second.f); else return valid_second(t->second.i); } return FALSE; } static int valid_tz(int hoff, int moff) { if ( hoff >= 0 && hoff <= 13 ) return valid_minute(moff); else if ( hoff == 14 && moff == 0 ) return TRUE; else return int_domain("tz_hour", hoff); } static int valid_tzoffset(int sec) { if ( sec < -12*3600 || sec > 12*3600 ) return int_domain("tz_offset", sec); return TRUE; } static int is_time_seq(const int av[], time *t) { if ( av[0] == INT2 && av[2] == COLON && av[3] == INT2 && av[5] == COLON && av[6] == INT2 ) { t->hour = av[1]; t->minute = av[4]; if ( av[8] == DECIMAL ) { int dl, div = 1; t->sec_is_float = TRUE; for(dl=av[9]; dl > 0; dl--) div *= 10; t->second.f = (double)av[7] + (double)av[10]/(double)div; return 11; } else { t->sec_is_float = FALSE; t->second.i = av[7]; return 8; } } return 0; } static char * time_sec_chars(time *t, char *buf) { if ( !t->sec_is_float ) { sprintf(buf, "%02d", t->second.i); return buf; } else { char *s, *e; buf[0] = '0'; sprintf(&buf[1], "%f", t->second.f); if ( !isdigit(buf[2]) ) { buf[2] = '.'; s = buf; } else { assert(!isdigit(buf[3])); buf[3] = '.'; s = buf+1; } e = s+strlen(s); while(e[-1] == '0' && e[-2] != '.' ) e--; e[0] = 0; return s; } } /** xsd_time_string(+Term, ?Type, -String) is det. xsd_time_string(-Term, ?Type, +String) is det. */ static int is_time_url(atom_t url) { return ( url == URL_date || url == URL_dateTime || url == URL_gDay || url == URL_gMonth || url == URL_gMonthDay || url == URL_gYear || url == URL_gYearMonth || url == URL_time ); } static int maybe_invalid_time_url(term_t type) { atom_t url; if ( PL_get_atom_ex(type, &url) ) { if ( is_time_url(url) ) return FALSE; return PL_domain_error("xsd_time_url", type); } return FALSE; } static int unify_parsed_type(term_t t, atom_t type) { if ( PL_unify_atom(t, type) ) { return TRUE; } else { if ( PL_is_atom(t) ) { term_t ex; return ( (ex=PL_new_term_ref()) && PL_unify_term(ex, PL_FUNCTOR, FUNCTOR_error2, PL_FUNCTOR, FUNCTOR_syntax_error1, PL_FUNCTOR, FUNCTOR_xsd_time1, PL_ATOM, type, PL_VARIABLE) && PL_raise_exception(ex) ); } return FALSE; } } static int incompatible_time_term(term_t term, atom_t type_atom) { term_t ex; return ( (ex=PL_new_term_ref()) && PL_unify_term(ex, PL_FUNCTOR, FUNCTOR_error2, PL_FUNCTOR, FUNCTOR_domain_error2, PL_FUNCTOR, FUNCTOR_xsd_time1, PL_ATOM, type_atom, PL_TERM, term, PL_VARIABLE) && PL_raise_exception(ex) ); } static int unify_prolog_type(term_t term, term_t type_term, atom_t type_atom) { if ( PL_unify_atom(type_term, type_atom) ) { return TRUE; } else { if ( PL_is_atom(type_term) ) incompatible_time_term(term, type_atom); return FALSE; } } static foreign_t xsd_time_string(term_t term, term_t type, term_t string) { if ( PL_is_variable(string) ) /* +, ?, - */ { char buf[100], b2[20]; atom_t at = 0; int v; if ( PL_is_functor(term, FUNCTOR_date3) ) /* date(Y,M,D) */ { int v[3]; char *sign = ""; if ( !get_int_args(term, 3, v) || !valid_date(v) ) return FALSE; at = URL_date; if ( v[0] < 0 ) { sign = "-"; v[0] = mkyear(v[0], -1); } sprintf(buf, "%s%04d-%02d-%02d", sign, v[0],v[1],v[2]); } else if ( PL_is_functor(term, FUNCTOR_date_time6) ) { int v[3]; time t; char *sign = ""; if ( !get_int_args(term, 3, v) || !get_time_args(term, 3, &t) || !valid_date(v) || !valid_time(&t) ) return FALSE; at = URL_dateTime; if ( v[0] < 0 ) { sign = "-"; v[0] = mkyear(v[0], -1); } sprintf(buf, "%s%04d-%02d-%02dT%02d:%02d:%s", sign, v[0], v[1], v[2], t.hour, t.minute, time_sec_chars(&t, b2)); } else if ( PL_is_functor(term, FUNCTOR_date_time7) ) { int v[3], tz; time t; char *sign = ""; if ( !get_int_args(term, 3, v) || !get_time_args(term, 3, &t) || !get_int_arg(term, 7, &tz) || !valid_date(v) || !valid_time(&t) || !valid_tzoffset(tz) ) return FALSE; at = URL_dateTime; if ( v[0] < 0 ) { sign = "-"; v[0] = mkyear(v[0], -1); } sprintf(buf, "%s%04d-%02d-%02dT%02d:%02d:%s", sign, v[0], v[1], v[2], t.hour, t.minute, time_sec_chars(&t, b2)); if ( tz == 0 ) { strcat(buf, "Z"); } else { char sign = tz < 0 ? '-' : '+'; int tza = tz < 0 ? -tz : tz; char *out = buf+strlen(buf); sprintf(out, "%c%02d:%02d", sign, tza/3600, (tza % 3600)/60); } } else if ( PL_is_functor(term, FUNCTOR_time3) ) { time t; if ( !get_time_args(term, 0, &t) || !valid_time(&t) ) return FALSE; at = URL_time; sprintf(buf, "%02d:%02d:%s", t.hour, t.minute, time_sec_chars(&t, b2)); } else if ( PL_is_functor(term, FUNCTOR_month_day2) ) { int v[2]; if ( !get_int_args(term, 2, v) ) return FALSE; at = URL_gMonthDay; sprintf(buf, "%02d-%02d", v[0], v[1]); } else if ( PL_is_functor(term, FUNCTOR_year_month2) ) { int v[2]; char *sign = ""; if ( !get_int_args(term, 2, v) ) return FALSE; if ( v[0] < 0 ) { sign = "-"; v[0] = mkyear(v[0], -1); } at = URL_gYearMonth; sprintf(buf, "%s%04d-%02d", sign, v[0], v[1]); } else if ( PL_get_integer(term, &v) ) { atom_t url; if ( !PL_get_atom_ex(type, &url) ) return FALSE; if ( url == URL_gDay ) { if ( !valid_day(v) ) return FALSE; sprintf(buf, "%02d", v); } else if ( url == URL_gMonth ) { if ( !valid_month(v) ) return FALSE; sprintf(buf, "%02d", v); } else if ( url == URL_gYear ) { char *sign = ""; if ( !valid_year(v) ) return FALSE; if ( v < 0 ) { sign = "-"; v = mkyear(v, -1); } sprintf(buf, "%s%04d", sign, v); } else if ( is_time_url(url) ) { return incompatible_time_term(term, url); } else return PL_domain_error("xsd_time_url", type); } else { return PL_domain_error("xsd_time", term); } if ( at && !unify_prolog_type(term, type, at) ) return FALSE; return PL_unify_chars(string, PL_STRING, (size_t)-1, buf); } else /* -, ?, + */ { char *in; size_t len; int avb[30]; int *av = avb; time t; int tlen; int yearsign = 1; if ( !PL_get_nchars(string, &len, &in, CVT_ATOM|CVT_STRING|CVT_LIST|CVT_EXCEPTION) ) return FALSE; if ( parse_date_parts(in, avb, sizeof(avb)/sizeof(*avb)) ) return PL_syntax_error("xsd_time", NULL); if ( av[0] == MINUS ) { av++; yearsign = -1; } if ( av[0] == INTYEAR && av[2] == MINUS && av[3] == INT2 && av[5] == MINUS && av[6] == INT2 ) /* YYYY-MM-DD */ { int v[3] = {mkyear(av[1],yearsign),av[4],av[7]}; if ( !valid_date(v) ) return FALSE; if ( av[8] == END ) { return (unify_parsed_type(type, URL_date) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_date3, PL_INT, mkyear(av[1],yearsign), PL_INT, av[4], PL_INT, av[7]) ); } else if ( av[8] == TT && (tlen=is_time_seq(av+9, &t)) ) /* THH:MM:SS[.DDD] */ { int at = 9+tlen; term_t sec = PL_new_term_ref(); if ( !valid_time(&t) ) return FALSE; if ( !(t.sec_is_float ? PL_put_float(sec, t.second.f) : PL_put_integer(sec, t.second.i)) ) return FALSE; if ( av[at] == END ) return (unify_parsed_type(type, URL_dateTime) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_date_time6, PL_INT, mkyear(av[1],yearsign), PL_INT, av[ 4], PL_INT, av[ 7], PL_INT, t.hour, PL_INT, t.minute, PL_TERM, sec)); if ( av[at] == TZ && av[at+1] == END ) return (unify_parsed_type(type, URL_dateTime) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_date_time7, PL_INT, mkyear(av[1],yearsign), PL_INT, av[ 4], PL_INT, av[ 7], PL_INT, t.hour, PL_INT, t.minute, PL_TERM, sec, PL_INT, 0)); if ( (av[at] == MINUS || av[at] == PLUS) && av[at+1] == INT2 && av[at+3] == COLON && av[at+4] == INT2 && av[at+6] == END ) { int tz = av[at+2]*3600 + av[at+5]*60; if ( av[at] == MINUS ) tz = -tz; if ( !valid_tz(av[at+2], av[at+5]) ) return FALSE; return (unify_parsed_type(type, URL_dateTime) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_date_time7, PL_INT, av[1]*yearsign, PL_INT, av[ 4], PL_INT, av[ 7], PL_INT, t.hour, PL_INT, t.minute, PL_TERM, sec, PL_INT, tz)); } } } if ( (tlen=is_time_seq(av, &t)) && av[tlen] == END ) { term_t sec = PL_new_term_ref(); if ( !valid_time(&t) ) return FALSE; if ( yearsign == -1 ) return PL_syntax_error("xsd_time", NULL); if ( !(t.sec_is_float ? PL_put_float(sec, t.second.f) : PL_put_integer(sec, t.second.i)) ) return FALSE; return (valid_time(&t) && unify_parsed_type(type, URL_time) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_time3, PL_INT, t.hour, PL_INT, t.minute, PL_TERM, sec) ); } if ( av[0] == INT2 && av[2] == MINUS && av[3] == INT2 && av[5] == END ) { if ( yearsign == -1 ) return PL_syntax_error("xsd_time", NULL); return (valid_month(av[1]) && valid_day(av[4]) && unify_parsed_type(type, URL_gMonthDay) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_month_day2, PL_INT, av[1], PL_INT, av[4]) ); } if ( av[0] == INTYEAR && av[2] == MINUS && av[3] == INT2 && av[5] == END ) { return (valid_year(mkyear(av[1],yearsign)) && valid_month(av[4]) && unify_parsed_type(type, URL_gYearMonth) && PL_unify_term(term, PL_FUNCTOR, FUNCTOR_year_month2, PL_INT, mkyear(av[1],yearsign), PL_INT, av[4]) ); } if ( av[0] == INTYEAR && av[2] == END ) { return (valid_year(mkyear(av[1],yearsign)) && unify_parsed_type(type, URL_gYear) && PL_unify_integer(term, mkyear(av[1],yearsign))); } if ( av[0] == INT2 && av[2] == END ) { atom_t url; if ( yearsign == -1 ) return PL_syntax_error("xsd_time", NULL); if ( !PL_get_atom_ex(type, &url) ) return FALSE; if ( url == URL_gDay ) return valid_day(av[1]) && PL_unify_integer(term, av[1]); if ( url == URL_gMonth ) return valid_month(av[1]) && PL_unify_integer(term, av[1]); return maybe_invalid_time_url(type); } return PL_syntax_error("xsd_time", NULL); } } #define MKFUNCTOR(n, a) \ FUNCTOR_ ## n ## a = PL_new_functor(PL_new_atom(#n), a) #define MKURL(n) \ URL_ ## n = PL_new_atom(URL_xsd #n); install_t install_xsd(void) { MKFUNCTOR(date, 3); MKFUNCTOR(date_time, 6); MKFUNCTOR(date_time, 7); MKFUNCTOR(time, 3); MKFUNCTOR(month_day, 2); MKFUNCTOR(year_month, 2); MKFUNCTOR(error, 2); MKFUNCTOR(syntax_error, 1); MKFUNCTOR(domain_error, 2); MKFUNCTOR(xsd_time, 1); MKURL(date); MKURL(dateTime); MKURL(gDay); MKURL(gMonth); MKURL(gMonthDay); MKURL(gYear); MKURL(gYearMonth); MKURL(time); PL_register_foreign("xsd_number_string", 2, xsd_number_string, 0); PL_register_foreign("xsd_time_string", 3, xsd_time_string, 0); }