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#token</td>
 60      *     <td>string</td>
 61      *   </tr>
 62      *   <tr>
 63      *     <td>http://www.w3.org/2001/XMLSchema#NCName</td>
 64      *     <td>string</td>
 65      *   </tr>
 66      *   <tr>
 67      *     <td>http://www.w3.org/2001/XMLSchema#boolean</td>
 68      *     <td>bool</td>
 69      *   </tr>
 70      *   <tr>
 71      *     <td>http://www.w3.org/2001/XMLSchema#decimal</td>
 72      *     <td>string</td>
 73      *   </tr>
 74      *   <tr>
 75      *     <td>http://www.w3.org/2001/XMLSchema#integer</td>
 76      *     <td>int</td>
 77      *   </tr>
 78      *   <tr>
 79      *     <td>http://www.w3.org/2001/XMLSchema#int</td>
 80      *     <td>int</td>
 81      *   </tr>
 82      *   <tr>
 83      *     <td>http://www.w3.org/2001/XMLSchema#float</td>
 84      *     <td>float</td>
 85      *   </tr>
 86      *   <tr>
 87      *     <td>http://www.w3.org/2001/XMLSchema#double</td>
 88      *     <td>float</td>
 89      *   </tr>
 90      *   <tr>
 91      *     <td>http://www.w3.org/2001/XMLSchema#dateTime</td>
 92      *     <td>string</td>
 93      *   </tr>
 94      *   <tr>
 95      *     <td>http://www.w3.org/2001/XMLSchema#date</td>
 96      *     <td>string</td>
 97      *   </tr>
 98      *   <tr>
 99      *     <td>http://www.w3.org/2001/XMLSchema#gYear</td>
100      *     <td>int</td>
101      *   </tr>
102      *   <tr>
103      *     <td>http://www.w3.org/2001/XMLSchema#gMonthDay</td>
104      *     <td>string</td>
105      *   </tr>
106      *   <tr>
107      *     <td>http://www.w3.org/2001/XMLSchema#anyURI</td>
108      *     <td>{@link jQuery.uri}</td>
109      *   </tr>
110      * </table>
111      * @memberOf jQuery.typedValue#
112      */
113     value: undefined,
114     /**
115      * The XML Schema datatype URI for the value's datatype
116      * @memberOf jQuery.typedValue#
117      */
118     datatype: undefined,
119 
120     init: function (value, datatype) {
121       var d = $.typedValue.types[datatype];
122       if ($.typedValue.valid(value, datatype)) {
123         this.representation = value;
124         this.datatype = datatype;
125         this.value = d === undefined ? strip(value) : d.value(d.strip ? strip(value) : value);
126         return this;
127       } else {
128         throw {
129           name: 'InvalidValue',
130           message: value + ' is not a valid ' + datatype + ' value'
131         };
132       }
133     }
134   };
135 
136   $.typedValue.fn.init.prototype = $.typedValue.fn;
137 
138   /**
139    * 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:
140    * <dl>
141    *   <dt>strip</dt>
142    *   <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>
143    *   <dt>regex</dt>
144    *   <dd>A regular expression that valid values of the type must match.</dd>
145    *   <dt>validate</dt>
146    *   <dd>Optional. A function that performs further testing on the value.</dd>
147    *   <dt>value</dt>
148    *   <dd>A function that returns a Javascript object equivalent for the value.</dd>
149    * </dl>
150    * 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.
151    * @see jQuery.typedValue
152    * @see jQuery.typedValue.valid
153    */
154   $.typedValue.types = {};
155 
156   $.typedValue.types['http://www.w3.org/2001/XMLSchema#string'] = {
157     regex: /^.*$/,
158     strip: false,
159     /** @ignore */
160     value: function (v) {
161       return v;
162     }
163   };
164 
165   $.typedValue.types['http://www.w3.org/2001/XMLSchema#token'] = {
166     regex: /^.*$/,
167     strip: true,
168     /** @ignore */
169     value: function (v) {
170       return strip(v);
171     }
172   };
173 
174   $.typedValue.types['http://www.w3.org/2001/XMLSchema#NCName'] = {
175     regex: /^[a-z_][-\.a-z0-9]+$/i,
176     strip: true,
177     /** @ignore */
178     value: function (v) {
179       return strip(v);
180     }
181   };
182 
183   $.typedValue.types['http://www.w3.org/2001/XMLSchema#boolean'] = {
184     regex: /^(?:true|false|1|0)$/,
185     strip: true,
186     /** @ignore */
187     value: function (v) {
188       return v === 'true' || v === '1';
189     }
190   };
191 
192   $.typedValue.types['http://www.w3.org/2001/XMLSchema#decimal'] = {
193     regex: /^[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)$/,
194     strip: true,
195     /** @ignore */
196     value: function (v) {
197       v = v.replace(/^0+/, '')
198         .replace(/0+$/, '');
199       if (v === '') {
200         v = '0.0';
201       }
202       if (v.substring(0, 1) === '.') {
203         v = '0' + v;
204       }
205       if (/\.$/.test(v)) {
206         v = v + '0';
207       } else if (!/\./.test(v)) {
208         v = v + '.0';
209       }
210       return v;
211     }
212   };
213 
214   $.typedValue.types['http://www.w3.org/2001/XMLSchema#integer'] = {
215     regex: /^[\-\+]?[0-9]+$/,
216     strip: true,
217     /** @ignore */
218     value: function (v) {
219       return parseInt(v, 10);
220     }
221   };
222 
223   $.typedValue.types['http://www.w3.org/2001/XMLSchema#int'] = {
224     regex: /^[\-\+]?[0-9]+$/,
225     strip: true,
226     /** @ignore */
227     value: function (v) {
228       return parseInt(v, 10);
229     }
230   };
231 
232   $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'] = {
233     regex: /^(?:[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)(?:[eE][\-\+]?[0-9]+)?|[\-\+]?INF|NaN)$/,
234     strip: true,
235     /** @ignore */
236     value: function (v) {
237       if (v === '-INF') {
238         return -1 / 0;
239       } else if (v === 'INF' || v === '+INF') {
240         return 1 / 0;
241       } else {
242         return parseFloat(v);
243       }
244     }
245   };
246 
247   $.typedValue.types['http://www.w3.org/2001/XMLSchema#double'] = {
248     regex: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].regex,
249     strip: true,
250     value: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].value
251   };
252 
253   $.typedValue.types['http://www.w3.org/2001/XMLSchema#duration'] = {
254     regex: /^([\-\+])?P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(?:T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+(?:\.[0-9]+)?)?S)?)$/,
255     /** @ignore */
256     validate: function (v) {
257       var m = this.regex.exec(v);
258       return m[2] || m[3] || m[4] || m[5] || m[6] || m[7];
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#yearMonthDuration'] = {
268     regex: /^([\-\+])?P(?:([0-9]+)Y)?(?:([0-9]+)M)?$/,
269     /** @ignore */
270     validate: function (v) {
271       var m = this.regex.exec(v);
272       return m[2] || m[3];
273     },
274     strip: true,
275     /** @ignore */
276     value: function (v) {
277       var m = this.regex.exec(v),
278         years = m[2] || 0,
279         months = m[3] || 0;
280       months += years * 12;
281       return m[1] === '-' ? -1 * months : months;
282     }
283   };
284 
285   $.typedValue.types['http://www.w3.org/2001/XMLSchema#dateTime'] = {
286     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)?$/,
287     /** @ignore */
288     validate: function (v) {
289       var
290         m = this.regex.exec(v),
291         year = parseInt(m[1], 10),
292         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, ''),
293         date;
294       if (year === 0 ||
295           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
296         return false;
297       }
298       try {
299         year = year < 100 ? Math.abs(year) + 1000 : year;
300         month = parseInt(m[2], 10);
301         day = parseInt(m[3], 10);
302         if (day > 31) {
303           return false;
304         } else if (day > 30 && !(month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12)) {
305           return false;
306         } else if (month === 2) {
307           if (day > 29) {
308             return false;
309           } else if (day === 29 && (year % 4 !== 0 || (year % 100 === 0 && year % 400 !== 0))) {
310             return false;
311           }
312         }
313         date = '' + year + '/' + m[2] + '/' + m[3] + ' ' + m[4] + ':' + m[5] + ':' + m[7] + ' ' + tz;
314         date = new Date(date);
315         return true;
316       } catch (e) {
317         return false;
318       }
319     },
320     strip: true,
321     /** @ignore */
322     value: function (v) {
323       return v;
324     }
325   };
326 
327   $.typedValue.types['http://www.w3.org/2001/XMLSchema#date'] = {
328     regex: /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
329     /** @ignore */
330     validate: function (v) {
331       var
332         m = this.regex.exec(v),
333         year = parseInt(m[1], 10),
334         month = parseInt(m[2], 10),
335         day = parseInt(m[3], 10),
336         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, '');
337       if (year === 0 ||
338           month > 12 ||
339           day > 31 ||
340           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
341         return false;
342       } else {
343         return true;
344       }
345     },
346     strip: true,
347     /** @ignore */
348     value: function (v) {
349       return v;
350     }
351   };
352 
353   $.typedValue.types['http://www.w3.org/2001/XMLSchema#gYear'] = {
354     regex: /^-?([0-9]{4,})$/,
355     /** @ignore */
356     validate: function (v) {
357       var i = parseInt(v, 10);
358       return i !== 0;
359     },
360     strip: true,
361     /** @ignore */
362     value: function (v) {
363       return parseInt(v, 10);
364     }
365   };
366 
367   $.typedValue.types['http://www.w3.org/2001/XMLSchema#gMonthDay'] = {
368     regex: /^--([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
369     /** @ignore */
370     validate: function (v) {
371       var
372         m = this.regex.exec(v),
373         month = parseInt(m[1], 10),
374         day = parseInt(m[2], 10),
375         tz = m[3] === undefined || m[3] === 'Z' ? '+0000' : m[3].replace(/:/, '');
376       if (month > 12 ||
377           day > 31 ||
378           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
379         return false;
380       } else if (month === 2 && day > 29) {
381         return false;
382       } else if ((month === 4 || month === 6 || month === 9 || month === 11) && day > 30) {
383         return false;
384       } else {
385         return true;
386       }
387     },
388     strip: true,
389     /** @ignore */
390     value: function (v) {
391       return v;
392     }
393   };
394 
395   $.typedValue.types['http://www.w3.org/2001/XMLSchema#anyURI'] = {
396     regex: /^.*$/,
397     strip: true,
398     /** @ignore */
399     value: function (v, options) {
400       var opts = $.extend({}, $.typedValue.defaults, options);
401       return $.uri.resolve(v, opts.base);
402     }
403   };
404 
405   $.typedValue.defaults = {
406     base: $.uri.base(),
407     namespaces: {}
408   };
409 
410   /**
411    * Checks whether a value is valid according to a given datatype. The datatype must be held in the {@link jQuery.typedValue.types} object.
412    * @param {String} value The value to validate.
413    * @param {String} datatype The URI for the datatype against which the value will be validated.
414    * @returns {boolean} True if the value is valid or the datatype is not recognised.
415    * @example validDate = $.typedValue.valid(date, 'http://www.w3.org/2001/XMLSchema#date');
416    */
417   $.typedValue.valid = function (value, datatype) {
418     var d = $.typedValue.types[datatype];
419     if (d === undefined) {
420       return true;
421     } else {
422       value = d.strip ? strip(value) : value;
423       if (d.regex.test(value)) {
424         return d.validate === undefined ? true : d.validate(value);
425       } else {
426         return false;
427       }
428     }
429   };
430 
431 })(jQuery);
432