1 /*
  2  * jQuery CURIE @VERSION
  3  *
  4  * Copyright (c) 2008,2009 Jeni Tennison
  5  * Licensed under the MIT (MIT-LICENSE.txt)
  6  *
  7  * Depends:
  8  *  jquery.uri.js
  9  */
 10 /**
 11  * @fileOverview XML Schema datatype handling
 12  * @author <a href="mailto:jeni@jenitennison.com">Jeni Tennison</a>
 13  * @copyright (c) 2008,2009 Jeni Tennison
 14  * @license MIT license (MIT-LICENSE.txt)
 15  * @version 1.0
 16  * @requires jquery.uri.js
 17  */
 18 
 19 (function ($) {
 20 
 21   var strip = function (value) {
 22     return value.replace(/[ \t\n\r]+/, ' ').replace(/^ +/, '').replace(/ +$/, '');
 23   };
 24 
 25   /**
 26    * Creates a new jQuery.typedValue object. This should be invoked as a method
 27    * rather than constructed using new.
 28    * @class Represents a value with an XML Schema datatype
 29    * @param {String} value The string representation of the value
 30    * @param {String} datatype The XML Schema datatype URI
 31    * @returns {jQuery.typedValue}
 32    * @example intValue = jQuery.typedValue('42', 'http://www.w3.org/2001/XMLSchema#integer');
 33    */
 34   $.typedValue = function (value, datatype) {
 35     return $.typedValue.fn.init(value, datatype);
 36   };
 37 
 38   $.typedValue.fn = $.typedValue.prototype = {
 39     /**
 40      * The string representation of the value
 41      * @memberOf jQuery.typedValue#
 42      */
 43     representation: undefined,
 44     /**
 45      * The value as an object. The type of the object will
 46      * depend on the XML Schema datatype URI specified
 47      * in the constructor. The following table lists the mappings
 48      * currently supported:
 49      * <table>
 50      *   <tr>
 51      *   <th>XML Schema Datatype</th>
 52      *   <th>Value type</th>
 53      *   </tr>
 54      *   <tr>
 55      *     <td>http://www.w3.org/2001/XMLSchema#string</td>
 56      *     <td>string</td>
 57      *   </tr>
 58      *   <tr>
 59      *     <td>http://www.w3.org/2001/XMLSchema#boolean</td>
 60      *     <td>bool</td>
 61      *   </tr>
 62      *   <tr>
 63      *     <td>http://www.w3.org/2001/XMLSchema#decimal</td>
 64      *     <td>string</td>
 65      *   </tr>
 66      *   <tr>
 67      *     <td>http://www.w3.org/2001/XMLSchema#integer</td>
 68      *     <td>int</td>
 69      *   </tr>
 70      *   <tr>
 71      *     <td>http://www.w3.org/2001/XMLSchema#int</td>
 72      *     <td>int</td>
 73      *   </tr>
 74      *   <tr>
 75      *     <td>http://www.w3.org/2001/XMLSchema#float</td>
 76      *     <td>float</td>
 77      *   </tr>
 78      *   <tr>
 79      *     <td>http://www.w3.org/2001/XMLSchema#double</td>
 80      *     <td>float</td>
 81      *   </tr>
 82      *   <tr>
 83      *     <td>http://www.w3.org/2001/XMLSchema#dateTime</td>
 84      *     <td>string</td>
 85      *   </tr>
 86      *   <tr>
 87      *     <td>http://www.w3.org/2001/XMLSchema#date</td>
 88      *     <td>string</td>
 89      *   </tr>
 90      *   <tr>
 91      *     <td>http://www.w3.org/2001/XMLSchema#gMonthDay</td>
 92      *     <td>string</td>
 93      *   </tr>
 94      *   <tr>
 95      *     <td>http://www.w3.org/2001/XMLSchema#anyURI</td>
 96      *     <td>string</td>
 97      *   </tr>
 98      * </table>
 99      * @memberOf jQuery.typedValue#
100      */
101     value: undefined,
102     /**
103      * The XML Schema datatype URI for the value's datatype
104      * @memberOf jQuery.typedValue#
105      */
106     datatype: undefined,
107 
108     init: function (value, datatype) {
109       var d;
110       if ($.typedValue.valid(value, datatype)) {
111         d = $.typedValue.types[datatype];
112         this.representation = value;
113         this.datatype = datatype;
114         this.value = d.value(d.strip ? strip(value) : value);
115         return this;
116       } else {
117         throw {
118           name: 'InvalidValue',
119           message: value + ' is not a valid ' + datatype + ' value'
120         };
121       }
122     }
123   };
124 
125   $.typedValue.fn.init.prototype = $.typedValue.fn;
126 
127   /**
128    * An object that holds the datatypes supported by the script. The properties of this object are the URIs of the datatypes, and each datatype has four properties:
129    * <dl>
130    *   <dt>strip</dt>
131    *   <dd>A boolean value that indicates whether whitespace should be stripped from the value prior to testing against the regular expression or passing to the value function.</dd>
132    *   <dt>regex</dt>
133    *   <dd>A regular expression that valid values of the type must match.</dd>
134    *   <dt>validate</dt>
135    *   <dd>Optional. A function that performs further testing on the value.</dd>
136    *   <dt>value</dt>
137    *   <dd>A function that returns a Javascript object equivalent for the value.</dd>
138    * </dl>
139    * You can add to this object as necessary for your own datatypes, and {@link jQuery.typedValue} and {@link jQuery.typedValue.valid} will work with them.
140    * @see jQuery.typedValue
141    * @see jQuery.typedValue.valid
142    */
143   $.typedValue.types = {};
144 
145   $.typedValue.types['http://www.w3.org/2001/XMLSchema#string'] = {
146     regex: /^.*$/,
147     strip: false,
148     /** @ignore */
149     value: function (v) {
150       return v;
151     }
152   };
153 
154   $.typedValue.types['http://www.w3.org/2001/XMLSchema#boolean'] = {
155     regex: /^(?:true|false|1|0)$/,
156     strip: true,
157     /** @ignore */
158     value: function (v) {
159       return v === 'true' || v === '1';
160     }
161   };
162 
163   $.typedValue.types['http://www.w3.org/2001/XMLSchema#decimal'] = {
164     regex: /^[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)$/,
165     strip: true,
166     /** @ignore */
167     value: function (v) {
168       return v;
169     }
170   };
171 
172   $.typedValue.types['http://www.w3.org/2001/XMLSchema#integer'] = {
173     regex: /^[\-\+]?[0-9]+$/,
174     strip: true,
175     /** @ignore */
176     value: function (v) {
177       return parseInt(v, 10);
178     }
179   };
180 
181   $.typedValue.types['http://www.w3.org/2001/XMLSchema#int'] = {
182     regex: /^[\-\+]?[0-9]+$/,
183     strip: true,
184     /** @ignore */
185     value: function (v) {
186       return parseInt(v, 10);
187     }
188   };
189 
190   $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'] = {
191     regex: /^(?:[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)(?:[eE][\-\+]?[0-9]+)?|[\-\+]?INF|NaN)$/,
192     strip: true,
193     /** @ignore */
194     value: function (v) {
195       if (v === '-INF') {
196         return -1 / 0;
197       } else if (v === 'INF' || v === '+INF') {
198         return 1 / 0;
199       } else {
200         return parseFloat(v);
201       }
202     }
203   };
204 
205   $.typedValue.types['http://www.w3.org/2001/XMLSchema#double'] = {
206     regex: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].regex,
207     strip: true,
208     value: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].value
209   };
210 
211   $.typedValue.types['http://www.w3.org/2001/XMLSchema#duration'] = {
212     regex: /^([\-\+])?P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(?:T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+(?:\.[0-9]+))?S)?)$/,
213     /** @ignore */
214     validate: function (v) {
215       var m = this.regex.exec(v);
216       return m[2] || m[3] || m[4] || m[5] || m[6] || m[7];
217     },
218     strip: true,
219     /** @ignore */
220     value: function (v) {
221       return v;
222     }
223   };
224 
225   $.typedValue.types['http://www.w3.org/2001/XMLSchema#dateTime'] = {
226     regex: /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):(([0-9]{2})(\.([0-9]+))?)((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
227     /** @ignore */
228     validate: function (v) {
229       var
230         m = this.regex.exec(v),
231         year = parseInt(m[1], 10),
232         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, ''),
233         date;
234       if (year === 0 ||
235           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
236         return false;
237       }
238       try {
239         year = year < 100 ? Math.abs(year) + 1000 : year;
240         month = parseInt(m[2], 10);
241         day = parseInt(m[3], 10);
242         if (day > 31) {
243           return false;
244         } else if (day > 30 && !(month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12)) {
245           return false;
246         } else if (month === 2) {
247           if (day > 29) {
248             return false;
249           } else if (day === 29 && (year % 4 !== 0 || (year % 100 === 0 && year % 400 !== 0))) {
250             return false;
251           }
252         }
253         date = '' + year + '/' + m[2] + '/' + m[3] + ' ' + m[4] + ':' + m[5] + ':' + m[7] + ' ' + tz;
254         date = new Date(date);
255         return true;
256       } catch (e) {
257         return false;
258       }
259     },
260     strip: true,
261     /** @ignore */
262     value: function (v) {
263       return v;
264     }
265   };
266 
267   $.typedValue.types['http://www.w3.org/2001/XMLSchema#date'] = {
268     regex: /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
269     /** @ignore */
270     validate: function (v) {
271       var
272         m = this.regex.exec(v),
273         year = parseInt(m[1], 10),
274         month = parseInt(m[2], 10),
275         day = parseInt(m[3], 10),
276         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, '');
277       if (year === 0 ||
278           month > 12 ||
279           day > 31 ||
280           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
281         return false;
282       } else {
283         return true;
284       }
285     },
286     strip: true,
287     /** @ignore */
288     value: function (v) {
289       return v;
290     }
291   };
292 
293   $.typedValue.types['http://www.w3.org/2001/XMLSchema#gMonthDay'] = {
294     regex: /^--([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
295     /** @ignore */
296     validate: function (v) {
297       var
298         m = this.regex.exec(v),
299         month = parseInt(m[1], 10),
300         day = parseInt(m[2], 10),
301         tz = m[3] === undefined || m[3] === 'Z' ? '+0000' : m[3].replace(/:/, '');
302       if (month > 12 ||
303           day > 31 ||
304           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
305         return false;
306       } else if (month === 2 && day > 29) {
307         return false;
308       } else if ((month === 4 || month === 6 || month === 9 || month === 11) && day > 30) {
309         return false;
310       } else {
311         return true;
312       }
313     },
314     strip: true,
315     /** @ignore */
316     value: function (v) {
317       return v;
318     }
319   };
320 
321   $.typedValue.types['http://www.w3.org/2001/XMLSchema#anyURI'] = {
322     regex: /^.*$/,
323     strip: true,
324     /** @ignore */
325     value: function (v, options) {
326       var opts = $.extend({}, $.typedValue.defaults, options);
327       return $.uri.resolve(v, opts.base);
328     }
329   };
330 
331   $.typedValue.defaults = {
332     base: $.uri.base(),
333     namespaces: {}
334   };
335 
336   /**
337    * Checks whether a value is valid according to a given datatype. The datatype must be held in the {@link jQuery.typedValue.types} object.
338    * @param {String} value The value to validate.
339    * @param {String} datatype The URI for the datatype against which the value will be validated.
340    * @returns {boolean} True if the value is valid.
341    * @throws {String} Errors if the datatype has not been specified in the {@link jQuery.typedValue.types} object.
342    * @example validDate = $.typedValue.valid(date, 'http://www.w3.org/2001/XMLSchema#date');
343    */
344   $.typedValue.valid = function (value, datatype) {
345     var d = $.typedValue.types[datatype];
346     if (d === undefined) {
347       throw "InvalidDatatype: The datatype " + datatype + " can't be recognised";
348     } else {
349       value = d.strip ? strip(value) : value;
350       if (d.regex.test(value)) {
351         return d.validate === undefined ? true : d.validate(value);
352       } else {
353         return false;
354       }
355     }
356   };
357 
358 })(jQuery);
359