UNPKG

52.1 kBJavaScriptView Raw
1/*
2Copyright (c) 2010 Jeremy Faivre
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is furnished
9to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21*/
22(function(){
23/**
24 * Exception class thrown when an error occurs during parsing.
25 *
26 * @author Fabien Potencier <fabien@symfony.com>
27 *
28 * @api
29 */
30
31/**
32 * Constructor.
33 *
34 * @param string message The error message
35 * @param integer parsedLine The line where the error occurred
36 * @param integer snippet The snippet of code near the problem
37 * @param string parsedFile The file name where the error occurred
38 */
39
40var YamlParseException = function(message, parsedLine, snippet, parsedFile){
41
42 this.rawMessage = message;
43 this.parsedLine = (parsedLine !== undefined) ? parsedLine : -1;
44 this.snippet = (snippet !== undefined) ? snippet : null;
45 this.parsedFile = (parsedFile !== undefined) ? parsedFile : null;
46
47 this.updateRepr();
48
49 this.message = message;
50
51};
52YamlParseException.prototype =
53{
54
55 name: 'YamlParseException',
56 message: null,
57
58 parsedFile: null,
59 parsedLine: -1,
60 snippet: null,
61 rawMessage: null,
62
63 isDefined: function(input)
64 {
65 return input != undefined && input != null;
66 },
67
68 /**
69 * Gets the snippet of code near the error.
70 *
71 * @return string The snippet of code
72 */
73 getSnippet: function()
74 {
75 return this.snippet;
76 },
77
78 /**
79 * Sets the snippet of code near the error.
80 *
81 * @param string snippet The code snippet
82 */
83 setSnippet: function(snippet)
84 {
85 this.snippet = snippet;
86
87 this.updateRepr();
88 },
89
90 /**
91 * Gets the filename where the error occurred.
92 *
93 * This method returns null if a string is parsed.
94 *
95 * @return string The filename
96 */
97 getParsedFile: function()
98 {
99 return this.parsedFile;
100 },
101
102 /**
103 * Sets the filename where the error occurred.
104 *
105 * @param string parsedFile The filename
106 */
107 setParsedFile: function(parsedFile)
108 {
109 this.parsedFile = parsedFile;
110
111 this.updateRepr();
112 },
113
114 /**
115 * Gets the line where the error occurred.
116 *
117 * @return integer The file line
118 */
119 getParsedLine: function()
120 {
121 return this.parsedLine;
122 },
123
124 /**
125 * Sets the line where the error occurred.
126 *
127 * @param integer parsedLine The file line
128 */
129 setParsedLine: function(parsedLine)
130 {
131 this.parsedLine = parsedLine;
132
133 this.updateRepr();
134 },
135
136 updateRepr: function()
137 {
138 this.message = this.rawMessage;
139
140 var dot = false;
141 if ('.' === this.message.charAt(this.message.length - 1)) {
142 this.message = this.message.substring(0, this.message.length - 1);
143 dot = true;
144 }
145
146 if (null !== this.parsedFile) {
147 this.message += ' in ' + JSON.stringify(this.parsedFile);
148 }
149
150 if (this.parsedLine >= 0) {
151 this.message += ' at line ' + this.parsedLine;
152 }
153
154 if (this.snippet) {
155 this.message += ' (near "' + this.snippet + '")';
156 }
157
158 if (dot) {
159 this.message += '.';
160 }
161 }
162}
163/**
164 * Yaml offers convenience methods to parse and dump YAML.
165 *
166 * @author Fabien Potencier <fabien@symfony.com>
167 *
168 * @api
169 */
170
171var YamlRunningUnderNode = false;
172var Yaml = function(){};
173Yaml.prototype =
174{
175
176 /**
177 * Parses YAML into a JS representation.
178 *
179 * The parse method, when supplied with a YAML stream (file),
180 * will do its best to convert YAML in a file into a JS representation.
181 *
182 * Usage:
183 * <code>
184 * obj = yaml.parseFile('config.yml');
185 * </code>
186 *
187 * @param string input Path of YAML file
188 *
189 * @return array The YAML converted to a JS representation
190 *
191 * @throws YamlParseException If the YAML is not valid
192 */
193 parseFile: function(file /* String */, callback /* Function */)
194 {
195 if ( callback == null )
196 {
197 var input = this.getFileContents(file);
198 var ret = null;
199 try
200 {
201 ret = this.parse(input);
202 }
203 catch ( e )
204 {
205 if ( e instanceof YamlParseException ) {
206 e.setParsedFile(file);
207 }
208 throw e;
209 }
210 return ret;
211 }
212
213 this.getFileContents(file, function(data)
214 {
215 callback(new Yaml().parse(data));
216 });
217 },
218
219 /**
220 * Parses YAML into a JS representation.
221 *
222 * The parse method, when supplied with a YAML stream (string),
223 * will do its best to convert YAML into a JS representation.
224 *
225 * Usage:
226 * <code>
227 * obj = yaml.parse(...);
228 * </code>
229 *
230 * @param string input string containing YAML
231 *
232 * @return array The YAML converted to a JS representation
233 *
234 * @throws YamlParseException If the YAML is not valid
235 */
236 parse: function(input /* String */)
237 {
238 var yaml = new YamlParser();
239
240 return yaml.parse(input);
241 },
242
243 /**
244 * Dumps a JS representation to a YAML string.
245 *
246 * The dump method, when supplied with an array, will do its best
247 * to convert the array into friendly YAML.
248 *
249 * @param array array JS representation
250 * @param integer inline The level where you switch to inline YAML
251 *
252 * @return string A YAML string representing the original JS representation
253 *
254 * @api
255 */
256 dump: function(array, inline, spaces)
257 {
258 if ( inline == null ) inline = 2;
259
260 var yaml = new YamlDumper();
261 if (spaces) {
262 yaml.numSpacesForIndentation = spaces;
263 }
264
265 return yaml.dump(array, inline);
266 },
267
268 getXHR: function()
269 {
270 if ( window.XMLHttpRequest )
271 return new XMLHttpRequest();
272
273 if ( window.ActiveXObject )
274 {
275 var names = [
276 "Msxml2.XMLHTTP.6.0",
277 "Msxml2.XMLHTTP.3.0",
278 "Msxml2.XMLHTTP",
279 "Microsoft.XMLHTTP"
280 ];
281
282 for ( var i = 0; i < 4; i++ )
283 {
284 try{ return new ActiveXObject(names[i]); }
285 catch(e){}
286 }
287 }
288 return null;
289 },
290
291 getFileContents: function(file, callback)
292 {
293 if ( YamlRunningUnderNode )
294 {
295 var fs = require('fs');
296 if ( callback == null )
297 {
298 var data = fs.readFileSync(file);
299 if (data == null) return null;
300 return ''+data;
301 }
302 else
303 {
304 fs.readFile(file, function(err, data)
305 {
306 if (err)
307 callback(null);
308 else
309 callback(data);
310 });
311 }
312 }
313 else
314 {
315 var request = this.getXHR();
316
317 // Sync
318 if ( callback == null )
319 {
320 request.open('GET', file, false);
321 request.send(null);
322
323 if ( request.status == 200 || request.status == 0 )
324 return request.responseText;
325
326 return null;
327 }
328
329 // Async
330 request.onreadystatechange = function()
331 {
332 if ( request.readyState == 4 )
333 if ( request.status == 200 || request.status == 0 )
334 callback(request.responseText);
335 else
336 callback(null);
337 };
338 request.open('GET', file, true);
339 request.send(null);
340 }
341 }
342};
343
344var YAML =
345{
346 /*
347 * @param integer inline The level where you switch to inline YAML
348 */
349
350 stringify: function(input, inline, spaces)
351 {
352 return new Yaml().dump(input, inline, spaces);
353 },
354
355 parse: function(input)
356 {
357 return new Yaml().parse(input);
358 },
359
360 load: function(file, callback)
361 {
362 return new Yaml().parseFile(file, callback);
363 }
364};
365
366// Handle node.js case
367if (typeof exports !== 'undefined') {
368 if (typeof module !== 'undefined' && module.exports) {
369 exports = module.exports = YAML;
370 YamlRunningUnderNode = true;
371
372 // Add require handler
373 (function () {
374 var require_handler = function (module, filename) {
375 // fill in result
376 module.exports = YAML.load(filename);
377 };
378
379 // register require extensions only if we're on node.js
380 // hack for browserify
381 if ( undefined !== require.extensions ) {
382 require.extensions['.yml'] = require_handler;
383 require.extensions['.yaml'] = require_handler;
384 }
385 }());
386 }
387}
388
389// Handle browser case
390if ( typeof(window) != "undefined" )
391{
392 window.YAML = YAML;
393}
394
395/**
396 * YamlInline implements a YAML parser/dumper for the YAML inline syntax.
397 */
398var YamlInline = function(){};
399YamlInline.prototype =
400{
401 i: null,
402
403 /**
404 * Convert a YAML string to a JS object.
405 *
406 * @param string value A YAML string
407 *
408 * @return object A JS object representing the YAML string
409 */
410 parse: function(value)
411 {
412 var result = null;
413 value = this.trim(value);
414
415 if ( 0 == value.length )
416 {
417 return '';
418 }
419
420 switch ( value.charAt(0) )
421 {
422 case '[':
423 result = this.parseSequence(value);
424 break;
425 case '{':
426 result = this.parseMapping(value);
427 break;
428 default:
429 result = this.parseScalar(value);
430 }
431
432 // some comment can end the scalar
433 if ( value.substr(this.i+1).replace(/^\s*#.*$/, '') != '' ) {
434 console.log("oups "+value.substr(this.i+1));
435 throw new YamlParseException('Unexpected characters near "'+value.substr(this.i)+'".');
436 }
437
438 return result;
439 },
440
441 /**
442 * Dumps a given JS variable to a YAML string.
443 *
444 * @param mixed value The JS variable to convert
445 *
446 * @return string The YAML string representing the JS object
447 */
448 dump: function(value)
449 {
450 if ( undefined == value || null == value )
451 return 'null';
452 if ( value instanceof Date)
453 return value.toISOString();
454 if ( typeof(value) == 'object')
455 return this.dumpObject(value);
456 if ( typeof(value) == 'boolean' )
457 return value ? 'true' : 'false';
458 if ( /^\d+$/.test(value) )
459 return typeof(value) == 'string' ? "'"+value+"'" : parseInt(value);
460 if ( this.isNumeric(value) )
461 return typeof(value) == 'string' ? "'"+value+"'" : parseFloat(value);
462 if ( typeof(value) == 'number' )
463 return value == Infinity ? '.Inf' : ( value == -Infinity ? '-.Inf' : ( isNaN(value) ? '.NAN' : value ) );
464 var yaml = new YamlEscaper();
465 if ( yaml.requiresDoubleQuoting(value) )
466 return yaml.escapeWithDoubleQuotes(value);
467 if ( yaml.requiresSingleQuoting(value) )
468 return yaml.escapeWithSingleQuotes(value);
469 if ( '' == value )
470 return '""';
471 if ( this.getTimestampRegex().test(value) )
472 return "'"+value+"'";
473 if ( this.inArray(value.toLowerCase(), ['null','~','true','false']) )
474 return "'"+value+"'";
475 // default
476 return value;
477 },
478
479 /**
480 * Dumps a JS object to a YAML string.
481 *
482 * @param object value The JS array to dump
483 *
484 * @return string The YAML string representing the JS object
485 */
486 dumpObject: function(value)
487 {
488 var keys = this.getKeys(value);
489 var output = null;
490 var i;
491 var len = keys.length;
492
493 // array
494 if ( value instanceof Array )
495 /*( 1 == len && '0' == keys[0] )
496 ||
497 ( len > 1 && this.reduceArray(keys, function(v,w){return Math.floor(v+w);}, 0) == len * (len - 1) / 2) )*/
498 {
499 output = [];
500 for ( i = 0; i < len; i++ )
501 {
502 output.push(this.dump(value[keys[i]]));
503 }
504
505 return '['+output.join(', ')+']';
506 }
507
508 // mapping
509 output = [];
510 for ( i = 0; i < len; i++ )
511 {
512 output.push(this.dump(keys[i])+': '+this.dump(value[keys[i]]));
513 }
514
515 return '{ '+output.join(', ')+' }';
516 },
517
518 /**
519 * Parses a scalar to a YAML string.
520 *
521 * @param scalar scalar
522 * @param string delimiters
523 * @param object stringDelimiters
524 * @param integer i
525 * @param boolean evaluate
526 *
527 * @return string A YAML string
528 *
529 * @throws YamlParseException When malformed inline YAML string is parsed
530 */
531 parseScalar: function(scalar, delimiters, stringDelimiters, i, evaluate)
532 {
533 if ( delimiters == undefined ) delimiters = null;
534 if ( stringDelimiters == undefined ) stringDelimiters = ['"', "'"];
535 if ( i == undefined ) i = 0;
536 if ( evaluate == undefined ) evaluate = true;
537
538 var output = null;
539 var pos = null;
540 var matches = null;
541
542 if ( this.inArray(scalar[i], stringDelimiters) )
543 {
544 // quoted scalar
545 output = this.parseQuotedScalar(scalar, i);
546 i = this.i;
547 if (null !== delimiters) {
548 var tmp = scalar.substr(i).replace(/^\s+/, '');
549 if (!this.inArray(tmp.charAt(0), delimiters)) {
550 throw new YamlParseException('Unexpected characters ('+scalar.substr(i)+').');
551 }
552 }
553 }
554 else
555 {
556 // "normal" string
557 if ( !delimiters )
558 {
559 output = (scalar+'').substring(i);
560
561 i += output.length;
562
563 // remove comments
564 pos = output.indexOf(' #');
565 if ( pos != -1 )
566 {
567 output = output.substr(0, pos).replace(/\s+$/g,'');
568 }
569 }
570 else if ( matches = new RegExp('^(.+?)('+delimiters.join('|')+')').exec((scalar+'').substring(i)) )
571 {
572 output = matches[1];
573 i += output.length;
574 }
575 else
576 {
577 throw new YamlParseException('Malformed inline YAML string ('+scalar+').');
578 }
579 output = evaluate ? this.evaluateScalar(output) : output;
580 }
581
582 this.i = i;
583
584 return output;
585 },
586
587 /**
588 * Parses a quoted scalar to YAML.
589 *
590 * @param string scalar
591 * @param integer i
592 *
593 * @return string A YAML string
594 *
595 * @throws YamlParseException When malformed inline YAML string is parsed
596 */
597 parseQuotedScalar: function(scalar, i)
598 {
599 var matches = null;
600 //var item = /^(.*?)['"]\s*(?:[,:]|[}\]]\s*,)/.exec((scalar+'').substring(i))[1];
601
602 if ( !(matches = new RegExp('^'+YamlInline.REGEX_QUOTED_STRING).exec((scalar+'').substring(i))) )
603 {
604 throw new YamlParseException('Malformed inline YAML string ('+(scalar+'').substring(i)+').');
605 }
606
607 var output = matches[0].substr(1, matches[0].length - 2);
608
609 var unescaper = new YamlUnescaper();
610
611 if ( '"' == (scalar+'').charAt(i) )
612 {
613 output = unescaper.unescapeDoubleQuotedString(output);
614 }
615 else
616 {
617 output = unescaper.unescapeSingleQuotedString(output);
618 }
619
620 i += matches[0].length;
621
622 this.i = i;
623 return output;
624 },
625
626 /**
627 * Parses a sequence to a YAML string.
628 *
629 * @param string sequence
630 * @param integer i
631 *
632 * @return string A YAML string
633 *
634 * @throws YamlParseException When malformed inline YAML string is parsed
635 */
636 parseSequence: function(sequence, i)
637 {
638 if ( i == undefined ) i = 0;
639
640 var output = [];
641 var len = sequence.length;
642 i += 1;
643
644 // [foo, bar, ...]
645 while ( i < len )
646 {
647 switch ( sequence.charAt(i) )
648 {
649 case '[':
650 // nested sequence
651 output.push(this.parseSequence(sequence, i));
652 i = this.i;
653 break;
654 case '{':
655 // nested mapping
656 output.push(this.parseMapping(sequence, i));
657 i = this.i;
658 break;
659 case ']':
660 this.i = i;
661 return output;
662 case ',':
663 case ' ':
664 break;
665 default:
666 var isQuoted = this.inArray(sequence.charAt(i), ['"', "'"]);
667 var value = this.parseScalar(sequence, [',', ']'], ['"', "'"], i);
668 i = this.i;
669
670 if ( !isQuoted && (value+'').indexOf(': ') != -1 )
671 {
672 // embedded mapping?
673 try
674 {
675 value = this.parseMapping('{'+value+'}');
676 }
677 catch ( e )
678 {
679 if ( !(e instanceof YamlParseException ) ) throw e;
680 // no, it's not
681 }
682 }
683
684 output.push(value);
685
686 i--;
687 }
688
689 i++;
690 }
691
692 throw new YamlParseException('Malformed inline YAML string "'+sequence+'"');
693 },
694
695 /**
696 * Parses a mapping to a YAML string.
697 *
698 * @param string mapping
699 * @param integer i
700 *
701 * @return string A YAML string
702 *
703 * @throws YamlParseException When malformed inline YAML string is parsed
704 */
705 parseMapping: function(mapping, i)
706 {
707 if ( i == undefined ) i = 0;
708 var output = {};
709 var len = mapping.length;
710 i += 1;
711 var done = false;
712 var doContinue = false;
713
714 // {foo: bar, bar:foo, ...}
715 while ( i < len )
716 {
717 doContinue = false;
718
719 switch ( mapping.charAt(i) )
720 {
721 case ' ':
722 case ',':
723 i++;
724 doContinue = true;
725 break;
726 case '}':
727 this.i = i;
728 return output;
729 }
730
731 if ( doContinue ) continue;
732
733 // key
734 var key = this.parseScalar(mapping, [':', ' '], ['"', "'"], i, false);
735 i = this.i;
736
737 // value
738 done = false;
739 while ( i < len )
740 {
741 switch ( mapping.charAt(i) )
742 {
743 case '[':
744 // nested sequence
745 output[key] = this.parseSequence(mapping, i);
746 i = this.i;
747 done = true;
748 break;
749 case '{':
750 // nested mapping
751 output[key] = this.parseMapping(mapping, i);
752 i = this.i;
753 done = true;
754 break;
755 case ':':
756 case ' ':
757 break;
758 default:
759 output[key] = this.parseScalar(mapping, [',', '}'], ['"', "'"], i);
760 i = this.i;
761 done = true;
762 i--;
763 }
764
765 ++i;
766
767 if ( done )
768 {
769 doContinue = true;
770 break;
771 }
772 }
773
774 if ( doContinue ) continue;
775 }
776
777 throw new YamlParseException('Malformed inline YAML string "'+mapping+'"');
778 },
779
780 /**
781 * Evaluates scalars and replaces magic values.
782 *
783 * @param string scalar
784 *
785 * @return string A YAML string
786 */
787 evaluateScalar: function(scalar)
788 {
789 scalar = this.trim(scalar);
790
791 var raw = null;
792 var cast = null;
793
794 if ( ( 'null' == scalar.toLowerCase() ) ||
795 ( '' == scalar ) ||
796 ( '~' == scalar ) )
797 return null;
798 if ( (scalar+'').indexOf('!str ') == 0 )
799 return (''+scalar).substring(5);
800 if ( (scalar+'').indexOf('! ') == 0 )
801 return parseInt(this.parseScalar((scalar+'').substr(2)));
802 if ( /^\d+$/.test(scalar) )
803 {
804 raw = scalar;
805 cast = parseInt(scalar);
806 return '0' == scalar.charAt(0) ? this.octdec(scalar) : (( ''+raw == ''+cast ) ? cast : raw);
807 }
808 if ( 'true' == (scalar+'').toLowerCase() )
809 return true;
810 if ( 'false' == (scalar+'').toLowerCase() )
811 return false;
812 if ( this.isNumeric(scalar) )
813 return '0x' == (scalar+'').substr(0, 2) ? this.hexdec(scalar) : parseFloat(scalar);
814 if ( scalar.toLowerCase() == '.inf' )
815 return Infinity;
816 if ( scalar.toLowerCase() == '.nan' )
817 return NaN;
818 if ( scalar.toLowerCase() == '-.inf' )
819 return -Infinity;
820 if ( /^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(scalar) )
821 return parseFloat(scalar.split(',').join(''));
822 if ( this.getTimestampRegex().test(scalar) )
823 return new Date(this.strtotime(scalar));
824 //else
825 return ''+scalar;
826 },
827
828 /**
829 * Gets a regex that matches an unix timestamp
830 *
831 * @return string The regular expression
832 */
833 getTimestampRegex: function()
834 {
835 return new RegExp('^'+
836 '([0-9][0-9][0-9][0-9])'+
837 '-([0-9][0-9]?)'+
838 '-([0-9][0-9]?)'+
839 '(?:(?:[Tt]|[ \t]+)'+
840 '([0-9][0-9]?)'+
841 ':([0-9][0-9])'+
842 ':([0-9][0-9])'+
843 '(?:\.([0-9]*))?'+
844 '(?:[ \t]*(Z|([-+])([0-9][0-9]?)'+
845 '(?::([0-9][0-9]))?))?)?'+
846 '$','gi');
847 },
848
849 trim: function(str /* String */)
850 {
851 return (str+'').replace(/^\s+/,'').replace(/\s+$/,'');
852 },
853
854 isNumeric: function(input)
855 {
856 return (input - 0) == input && input.length > 0 && input.replace(/\s+/g,'') != '';
857 },
858
859 inArray: function(key, tab)
860 {
861 var i;
862 var len = tab.length;
863 for ( i = 0; i < len; i++ )
864 {
865 if ( key == tab[i] ) return true;
866 }
867 return false;
868 },
869
870 getKeys: function(tab)
871 {
872 var ret = [];
873
874 for ( var name in tab )
875 {
876 if ( tab.hasOwnProperty(name) )
877 {
878 ret.push(name);
879 }
880 }
881
882 return ret;
883 },
884
885 /*reduceArray: function(tab, fun)
886 {
887 var len = tab.length;
888 if (typeof fun != "function")
889 throw new YamlParseException("fun is not a function");
890
891 // no value to return if no initial value and an empty array
892 if (len == 0 && arguments.length == 1)
893 throw new YamlParseException("empty array");
894
895 var i = 0;
896 if (arguments.length >= 2)
897 {
898 var rv = arguments[1];
899 }
900 else
901 {
902 do
903 {
904 if (i in tab)
905 {
906 rv = tab[i++];
907 break;
908 }
909
910 // if array contains no values, no initial value to return
911 if (++i >= len)
912 throw new YamlParseException("no initial value to return");
913 }
914 while (true);
915 }
916
917 for (; i < len; i++)
918 {
919 if (i in tab)
920 rv = fun.call(null, rv, tab[i], i, tab);
921 }
922
923 return rv;
924 },*/
925
926 octdec: function(input)
927 {
928 return parseInt((input+'').replace(/[^0-7]/gi, ''), 8);
929 },
930
931 hexdec: function(input)
932 {
933 input = this.trim(input);
934 if ( (input+'').substr(0, 2) == '0x' ) input = (input+'').substring(2);
935 return parseInt((input+'').replace(/[^a-f0-9]/gi, ''), 16);
936 },
937
938 /**
939 * @see http://phpjs.org/functions/strtotime
940 * @note we need timestamp with msecs so /1000 removed
941 * @note original contained binary | 0 (wtf?!) everywhere, which messes everything up
942 */
943 strtotime: function (h,b){var f,c,g,k,d="";h=(h+"").replace(/\s{2,}|^\s|\s$/g," ").replace(/[\t\r\n]/g,"");if(h==="now"){return b===null||isNaN(b)?new Date().getTime()||0:b||0}else{if(!isNaN(d=Date.parse(h))){return d||0}else{if(b){b=new Date(b)}else{b=new Date()}}}h=h.toLowerCase();var e={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]};var a=function(i){var o=(i[2]&&i[2]==="ago");var n=(n=i[0]==="last"?-1:1)*(o?-1:1);switch(i[0]){case"last":case"next":switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break;case"mon":if(i[1]==="month"){b.setMonth(b.getMonth()+n);break}default:var l=e.day[i[1].substring(0,3)];if(typeof l!=="undefined"){var p=l-b.getDay();if(p===0){p=7*n}else{if(p>0){if(i[0]==="last"){p-=7}}else{if(i[0]==="next"){p+=7}}}b.setDate(b.getDate()+p);b.setHours(0,0,0,0)}}break;default:if(/\d+/.test(i[0])){n*=parseInt(i[0],10);switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"mon":b.setMonth(b.getMonth()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break}}else{return false}break}return true};g=h.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(g!==null){if(!g[2]){g[2]="00:00:00"}else{if(!g[3]){g[2]+=":00"}}k=g[1].split(/-/g);k[1]=e.mon[k[1]-1]||k[1];k[0]=+k[0];k[0]=(k[0]>=0&&k[0]<=69)?"20"+(k[0]<10?"0"+k[0]:k[0]+""):(k[0]>=70&&k[0]<=99)?"19"+k[0]:k[0]+"";return parseInt(this.strtotime(k[2]+" "+k[1]+" "+k[0]+" "+g[2])+(g[4]?g[4]:""),10)}var j="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";g=h.match(new RegExp(j,"gi"));if(g===null){return false}for(f=0,c=g.length;f<c;f++){if(!a(g[f].split(" "))){return false}}return b.getTime()||0}
944
945};
946
947/*
948 * @note uses only non-capturing sub-patterns (unlike PHP original)
949 */
950YamlInline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
951
952
953/**
954 * YamlParser parses YAML strings to convert them to JS objects
955 * (port of Yaml Symfony Component)
956 */
957var YamlParser = function(offset /* Integer */)
958{
959 this.offset = (offset !== undefined) ? offset : 0;
960};
961YamlParser.prototype =
962{
963 offset: 0,
964 lines: [],
965 currentLineNb: -1,
966 currentLine: '',
967 refs: {},
968
969 /**
970 * Parses a YAML string to a JS value.
971 *
972 * @param String value A YAML string
973 *
974 * @return mixed A JS value
975 */
976 parse: function(value /* String */)
977 {
978 this.currentLineNb = -1;
979 this.currentLine = '';
980 this.lines = this.cleanup(value).split("\n");
981
982 var data = null;
983 var context = null;
984
985 while ( this.moveToNextLine() )
986 {
987 if ( this.isCurrentLineEmpty() )
988 {
989 continue;
990 }
991
992 // tab?
993 if ( this.currentLine.charAt(0) == '\t' )
994 {
995 throw new YamlParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
996 }
997
998 var isRef = false;
999 var isInPlace = false;
1000 var isProcessed = false;
1001 var values = null;
1002 var matches = null;
1003 var c = null;
1004 var parser = null;
1005 var block = null;
1006 var key = null;
1007 var parsed = null;
1008 var len = null;
1009 var reverse = null;
1010
1011 if ( values = /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine) )
1012 {
1013
1014 if (context && 'mapping' == context) {
1015 throw new YamlParseException('You cannot define a sequence item when in a mapping', this.getRealCurrentLineNb() + 1, this.currentLine);
1016 }
1017 context = 'sequence';
1018
1019 if ( !this.isDefined(data) ) data = [];
1020 //if ( !(data instanceof Array) ) throw new YamlParseException("Non array entry", this.getRealCurrentLineNb() + 1, this.currentLine);
1021
1022 values = {leadspaces: values[2], value: values[3]};
1023
1024 if ( this.isDefined(values.value) && ( matches = /^&([^ ]+) *(.*)/.exec(values.value) ) )
1025 {
1026 matches = {ref: matches[1], value: matches[2]};
1027 isRef = matches.ref;
1028 values.value = matches.value;
1029 }
1030
1031 // array
1032 if ( !this.isDefined(values.value) || '' == this.trim(values.value) || values.value.replace(/^ +/,'').charAt(0) == '#' )
1033 {
1034 c = this.getRealCurrentLineNb() + 1;
1035 parser = new YamlParser(c);
1036 parser.refs = this.refs;
1037 data.push(parser.parse(this.getNextEmbedBlock()));
1038 this.refs = parser.refs;
1039 }
1040 else
1041 {
1042 if ( this.isDefined(values.leadspaces) &&
1043 ' ' == values.leadspaces &&
1044 ( matches = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\{\[].*?) *\:(\\s+(.+?))?\\s*$').exec(values.value) )
1045 ) {
1046 matches = {key: matches[1], value: matches[3]};
1047 // this is a compact notation element, add to next block and parse
1048 c = this.getRealCurrentLineNb();
1049 parser = new YamlParser(c);
1050 parser.refs = this.refs;
1051 block = values.value;
1052
1053 if ( !this.isNextLineIndented() )
1054 {
1055 block += "\n"+this.getNextEmbedBlock(this.getCurrentLineIndentation() + 2);
1056 }
1057
1058 data.push(parser.parse(block));
1059 this.refs = parser.refs;
1060 }
1061 else
1062 {
1063 data.push(this.parseValue(values.value));
1064 }
1065 }
1066 }
1067 else if ( values = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\[\{].*?) *\:(\\s+(.+?))?\\s*$').exec(this.currentLine) )
1068 {
1069 if ( !this.isDefined(data) ) data = {};
1070 if (context && 'sequence' == context) {
1071 throw new YamlParseException('You cannot define a mapping item when in a sequence', this.getRealCurrentLineNb() + 1, this.currentLine);
1072 }
1073 context = 'mapping';
1074 //if ( data instanceof Array ) throw new YamlParseException("Non mapped entry", this.getRealCurrentLineNb() + 1, this.currentLine);
1075
1076 values = {key: values[1], value: values[3]};
1077
1078 try {
1079 key = new YamlInline().parseScalar(values.key);
1080 } catch (e) {
1081 if ( e instanceof YamlParseException ) {
1082 e.setParsedLine(this.getRealCurrentLineNb() + 1);
1083 e.setSnippet(this.currentLine);
1084 }
1085 throw e;
1086 }
1087
1088
1089 if ( '<<' == key )
1090 {
1091 if ( this.isDefined(values.value) && '*' == (values.value+'').charAt(0) )
1092 {
1093 isInPlace = values.value.substr(1);
1094 if ( this.refs[isInPlace] == undefined )
1095 {
1096 throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine);
1097 }
1098 }
1099 else
1100 {
1101 if ( this.isDefined(values.value) && values.value != '' )
1102 {
1103 value = values.value;
1104 }
1105 else
1106 {
1107 value = this.getNextEmbedBlock();
1108 }
1109
1110 c = this.getRealCurrentLineNb() + 1;
1111 parser = new YamlParser(c);
1112 parser.refs = this.refs;
1113 parsed = parser.parse(value);
1114 this.refs = parser.refs;
1115
1116 var merged = [];
1117 if ( !this.isObject(parsed) )
1118 {
1119 throw new YamlParseException("YAML merge keys used with a scalar value instead of an array", this.getRealCurrentLineNb() + 1, this.currentLine);
1120 }
1121 else if ( this.isDefined(parsed[0]) )
1122 {
1123 // Numeric array, merge individual elements
1124 reverse = this.reverseArray(parsed);
1125 len = reverse.length;
1126 for ( var i = 0; i < len; i++ )
1127 {
1128 var parsedItem = reverse[i];
1129 if ( !this.isObject(reverse[i]) )
1130 {
1131 throw new YamlParseException("Merge items must be arrays", this.getRealCurrentLineNb() + 1, this.currentLine);
1132 }
1133 merged = this.mergeObject(reverse[i], merged);
1134 }
1135 }
1136 else
1137 {
1138 // Associative array, merge
1139 merged = this.mergeObject(merged, parsed);
1140 }
1141
1142 isProcessed = merged;
1143 }
1144 }
1145 else if ( this.isDefined(values.value) && (matches = /^&([^ ]+) *(.*)/.exec(values.value) ) )
1146 {
1147 matches = {ref: matches[1], value: matches[2]};
1148 isRef = matches.ref;
1149 values.value = matches.value;
1150 }
1151
1152 if ( isProcessed )
1153 {
1154 // Merge keys
1155 data = isProcessed;
1156 }
1157 // hash
1158 else if ( !this.isDefined(values.value) || '' == this.trim(values.value) || this.trim(values.value).charAt(0) == '#' )
1159 {
1160 // if next line is less indented or equal, then it means that the current value is null
1161 if ( this.isNextLineIndented() && !this.isNextLineUnIndentedCollection() )
1162 {
1163 data[key] = null;
1164 }
1165 else
1166 {
1167 c = this.getRealCurrentLineNb() + 1;
1168 parser = new YamlParser(c);
1169 parser.refs = this.refs;
1170 data[key] = parser.parse(this.getNextEmbedBlock());
1171 this.refs = parser.refs;
1172 }
1173 }
1174 else
1175 {
1176 if ( isInPlace )
1177 {
1178 data = this.refs[isInPlace];
1179 }
1180 else
1181 {
1182 data[key] = this.parseValue(values.value);
1183 }
1184 }
1185 }
1186 else
1187 {
1188 // 1-liner followed by newline
1189 if ( 2 == this.lines.length && this.isEmpty(this.lines[1]) )
1190 {
1191 try {
1192 value = new YamlInline().parse(this.lines[0]);
1193 } catch (e) {
1194 if ( e instanceof YamlParseException ) {
1195 e.setParsedLine(this.getRealCurrentLineNb() + 1);
1196 e.setSnippet(this.currentLine);
1197 }
1198 throw e;
1199 }
1200
1201 if ( this.isObject(value) )
1202 {
1203 var first = value[0];
1204 if ( typeof(value) == 'string' && '*' == first.charAt(0) )
1205 {
1206 data = [];
1207 len = value.length;
1208 for ( var i = 0; i < len; i++ )
1209 {
1210 data.push(this.refs[value[i].substr(1)]);
1211 }
1212 value = data;
1213 }
1214 }
1215
1216 return value;
1217 }
1218
1219 throw new YamlParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
1220 }
1221
1222 if ( isRef )
1223 {
1224 if ( data instanceof Array )
1225 this.refs[isRef] = data[data.length-1];
1226 else
1227 {
1228 var lastKey = null;
1229 for ( var k in data )
1230 {
1231 if ( data.hasOwnProperty(k) ) lastKey = k;
1232 }
1233 this.refs[isRef] = data[k];
1234 }
1235 }
1236 }
1237
1238 return this.isEmpty(data) ? null : data;
1239 },
1240
1241 /**
1242 * Returns the current line number (takes the offset into account).
1243 *
1244 * @return integer The current line number
1245 */
1246 getRealCurrentLineNb: function()
1247 {
1248 return this.currentLineNb + this.offset;
1249 },
1250
1251 /**
1252 * Returns the current line indentation.
1253 *
1254 * @return integer The current line indentation
1255 */
1256 getCurrentLineIndentation: function()
1257 {
1258 return this.currentLine.length - this.currentLine.replace(/^ +/g, '').length;
1259 },
1260
1261 /**
1262 * Returns the next embed block of YAML.
1263 *
1264 * @param integer indentation The indent level at which the block is to be read, or null for default
1265 *
1266 * @return string A YAML string
1267 *
1268 * @throws YamlParseException When indentation problem are detected
1269 */
1270 getNextEmbedBlock: function(indentation)
1271 {
1272 this.moveToNextLine();
1273 var newIndent = null;
1274 var indent = null;
1275
1276 if ( !this.isDefined(indentation) )
1277 {
1278 newIndent = this.getCurrentLineIndentation();
1279
1280 var unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
1281
1282 if ( !this.isCurrentLineEmpty() && 0 == newIndent && !unindentedEmbedBlock )
1283 {
1284 throw new YamlParseException('Indentation problem A', this.getRealCurrentLineNb() + 1, this.currentLine);
1285 }
1286 }
1287 else
1288 {
1289 newIndent = indentation;
1290 }
1291
1292 var data = [this.currentLine.substr(newIndent)];
1293
1294 var isUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
1295
1296 var continuationIndent = -1;
1297 if (isUnindentedCollection === true) {
1298 continuationIndent = 1 + /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine)[2].length;
1299 }
1300
1301 while ( this.moveToNextLine() )
1302 {
1303
1304 if (isUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && this.getCurrentLineIndentation() != continuationIndent) {
1305 this.moveToPreviousLine();
1306 break;
1307 }
1308
1309 if ( this.isCurrentLineEmpty() )
1310 {
1311 if ( this.isCurrentLineBlank() )
1312 {
1313 data.push(this.currentLine.substr(newIndent));
1314 }
1315
1316 continue;
1317 }
1318
1319 indent = this.getCurrentLineIndentation();
1320 var matches;
1321 if ( matches = /^( *)$/.exec(this.currentLine) )
1322 {
1323 // empty line
1324 data.push(matches[1]);
1325 }
1326 else if ( indent >= newIndent )
1327 {
1328 data.push(this.currentLine.substr(newIndent));
1329 }
1330 else if ( 0 == indent )
1331 {
1332 this.moveToPreviousLine();
1333
1334 break;
1335 }
1336 else
1337 {
1338 throw new YamlParseException('Indentation problem B', this.getRealCurrentLineNb() + 1, this.currentLine);
1339 }
1340 }
1341
1342 return data.join("\n");
1343 },
1344
1345 /**
1346 * Moves the parser to the next line.
1347 *
1348 * @return Boolean
1349 */
1350 moveToNextLine: function()
1351 {
1352 if ( this.currentLineNb >= this.lines.length - 1 )
1353 {
1354 return false;
1355 }
1356
1357 this.currentLineNb++;
1358 this.currentLine = this.lines[this.currentLineNb];
1359
1360 return true;
1361 },
1362
1363 /**
1364 * Moves the parser to the previous line.
1365 */
1366 moveToPreviousLine: function()
1367 {
1368 this.currentLineNb--;
1369 this.currentLine = this.lines[this.currentLineNb];
1370 },
1371
1372 /**
1373 * Parses a YAML value.
1374 *
1375 * @param string value A YAML value
1376 *
1377 * @return mixed A JS value
1378 *
1379 * @throws YamlParseException When reference does not exist
1380 */
1381 parseValue: function(value)
1382 {
1383 if ( '*' == (value+'').charAt(0) )
1384 {
1385 if ( this.trim(value).charAt(0) == '#' )
1386 {
1387 value = (value+'').substr(1, value.indexOf('#') - 2);
1388 }
1389 else
1390 {
1391 value = (value+'').substr(1);
1392 }
1393
1394 if ( this.refs[value] == undefined )
1395 {
1396 throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine);
1397 }
1398 return this.refs[value];
1399 }
1400
1401 var matches = null;
1402 if ( matches = /^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(value) )
1403 {
1404 matches = {separator: matches[1], modifiers: matches[2], comments: matches[3]};
1405 var modifiers = this.isDefined(matches.modifiers) ? matches.modifiers : '';
1406
1407 return this.parseFoldedScalar(matches.separator, modifiers.replace(/\d+/g, ''), Math.abs(parseInt(modifiers)));
1408 }
1409 try {
1410 return new YamlInline().parse(value);
1411 } catch (e) {
1412 if ( e instanceof YamlParseException ) {
1413 e.setParsedLine(this.getRealCurrentLineNb() + 1);
1414 e.setSnippet(this.currentLine);
1415 }
1416 throw e;
1417 }
1418 },
1419
1420 /**
1421 * Parses a folded scalar.
1422 *
1423 * @param string separator The separator that was used to begin this folded scalar (| or >)
1424 * @param string indicator The indicator that was used to begin this folded scalar (+ or -)
1425 * @param integer indentation The indentation that was used to begin this folded scalar
1426 *
1427 * @return string The text value
1428 */
1429 parseFoldedScalar: function(separator, indicator, indentation)
1430 {
1431 if ( indicator == undefined ) indicator = '';
1432 if ( indentation == undefined ) indentation = 0;
1433
1434 separator = '|' == separator ? "\n" : ' ';
1435 var text = '';
1436 var diff = null;
1437
1438 var notEOF = this.moveToNextLine();
1439
1440 while ( notEOF && this.isCurrentLineBlank() )
1441 {
1442 text += "\n";
1443
1444 notEOF = this.moveToNextLine();
1445 }
1446
1447 if ( !notEOF )
1448 {
1449 return '';
1450 }
1451
1452 var matches = null;
1453 if ( !(matches = new RegExp('^('+(indentation ? this.strRepeat(' ', indentation) : ' +')+')(.*)$').exec(this.currentLine)) )
1454 {
1455 this.moveToPreviousLine();
1456
1457 return '';
1458 }
1459
1460 matches = {indent: matches[1], text: matches[2]};
1461
1462 var textIndent = matches.indent;
1463 var previousIndent = 0;
1464
1465 text += matches.text + separator;
1466 while ( this.currentLineNb + 1 < this.lines.length )
1467 {
1468 this.moveToNextLine();
1469
1470 if ( matches = new RegExp('^( {'+textIndent.length+',})(.+)$').exec(this.currentLine) )
1471 {
1472 matches = {indent: matches[1], text: matches[2]};
1473
1474 if ( ' ' == separator && previousIndent != matches.indent )
1475 {
1476 text = text.substr(0, text.length - 1)+"\n";
1477 }
1478
1479 previousIndent = matches.indent;
1480
1481 diff = matches.indent.length - textIndent.length;
1482 text += this.strRepeat(' ', diff) + matches.text + (diff != 0 ? "\n" : separator);
1483 }
1484 else if ( matches = /^( *)$/.exec(this.currentLine) )
1485 {
1486 text += matches[1].replace(new RegExp('^ {1,'+textIndent.length+'}','g'), '')+"\n";
1487 }
1488 else
1489 {
1490 this.moveToPreviousLine();
1491
1492 break;
1493 }
1494 }
1495
1496 if ( ' ' == separator )
1497 {
1498 // replace last separator by a newline
1499 text = text.replace(/ (\n*)$/g, "\n$1");
1500 }
1501
1502 switch ( indicator )
1503 {
1504 case '':
1505 text = text.replace(/\n+$/g, "\n");
1506 break;
1507 case '+':
1508 break;
1509 case '-':
1510 text = text.replace(/\n+$/g, '');
1511 break;
1512 }
1513
1514 return text;
1515 },
1516
1517 /**
1518 * Returns true if the next line is indented.
1519 *
1520 * @return Boolean Returns true if the next line is indented, false otherwise
1521 */
1522 isNextLineIndented: function()
1523 {
1524 var currentIndentation = this.getCurrentLineIndentation();
1525 var notEOF = this.moveToNextLine();
1526
1527 while ( notEOF && this.isCurrentLineEmpty() )
1528 {
1529 notEOF = this.moveToNextLine();
1530 }
1531
1532 if ( false == notEOF )
1533 {
1534 return false;
1535 }
1536
1537 var ret = false;
1538 if ( this.getCurrentLineIndentation() <= currentIndentation )
1539 {
1540 ret = true;
1541 }
1542
1543 this.moveToPreviousLine();
1544
1545 return ret;
1546 },
1547
1548 /**
1549 * Returns true if the current line is blank or if it is a comment line.
1550 *
1551 * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
1552 */
1553 isCurrentLineEmpty: function()
1554 {
1555 return this.isCurrentLineBlank() || this.isCurrentLineComment();
1556 },
1557
1558 /**
1559 * Returns true if the current line is blank.
1560 *
1561 * @return Boolean Returns true if the current line is blank, false otherwise
1562 */
1563 isCurrentLineBlank: function()
1564 {
1565 return '' == this.trim(this.currentLine);
1566 },
1567
1568 /**
1569 * Returns true if the current line is a comment line.
1570 *
1571 * @return Boolean Returns true if the current line is a comment line, false otherwise
1572 */
1573 isCurrentLineComment: function()
1574 {
1575 //checking explicitly the first char of the trim is faster than loops or strpos
1576 var ltrimmedLine = this.currentLine.replace(/^ +/g, '');
1577 return ltrimmedLine.charAt(0) == '#';
1578 },
1579
1580 /**
1581 * Cleanups a YAML string to be parsed.
1582 *
1583 * @param string value The input YAML string
1584 *
1585 * @return string A cleaned up YAML string
1586 */
1587 cleanup: function(value)
1588 {
1589 value = value.split("\r\n").join("\n").split("\r").join("\n");
1590
1591 if ( !/\n$/.test(value) )
1592 {
1593 value += "\n";
1594 }
1595
1596 // strip YAML header
1597 var count = 0;
1598 var regex = /^\%YAML[: ][\d\.]+.*\n/;
1599 while ( regex.test(value) )
1600 {
1601 value = value.replace(regex, '');
1602 count++;
1603 }
1604 this.offset += count;
1605
1606 // remove leading comments
1607 regex = /^(#.*?\n)+/;
1608 if ( regex.test(value) )
1609 {
1610 var trimmedValue = value.replace(regex, '');
1611
1612 // items have been removed, update the offset
1613 this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n");
1614 value = trimmedValue;
1615 }
1616
1617 // remove start of the document marker (---)
1618 regex = /^\-\-\-.*?\n/;
1619 if ( regex.test(value) )
1620 {
1621 trimmedValue = value.replace(regex, '');
1622
1623 // items have been removed, update the offset
1624 this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n");
1625 value = trimmedValue;
1626
1627 // remove end of the document marker (...)
1628 value = value.replace(/\.\.\.\s*$/g, '');
1629 }
1630
1631 return value;
1632 },
1633
1634 /**
1635 * Returns true if the next line starts unindented collection
1636 *
1637 * @return Boolean Returns true if the next line starts unindented collection, false otherwise
1638 */
1639 isNextLineUnIndentedCollection: function()
1640 {
1641 var currentIndentation = this.getCurrentLineIndentation();
1642 var notEOF = this.moveToNextLine();
1643
1644 while (notEOF && this.isCurrentLineEmpty()) {
1645 notEOF = this.moveToNextLine();
1646 }
1647
1648 if (false === notEOF) {
1649 return false;
1650 }
1651
1652 var ret = false;
1653 if (
1654 this.getCurrentLineIndentation() == currentIndentation
1655 &&
1656 this.isStringUnIndentedCollectionItem(this.currentLine)
1657 ) {
1658 ret = true;
1659 }
1660
1661 this.moveToPreviousLine();
1662
1663 return ret;
1664 },
1665
1666 /**
1667 * Returns true if the string is unindented collection item
1668 *
1669 * @return Boolean Returns true if the string is unindented collection item, false otherwise
1670 */
1671 isStringUnIndentedCollectionItem: function(string)
1672 {
1673 return (0 === this.currentLine.indexOf('- '));
1674 },
1675
1676 isObject: function(input)
1677 {
1678 return typeof(input) == 'object' && this.isDefined(input);
1679 },
1680
1681 isEmpty: function(input)
1682 {
1683 return input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false;
1684 },
1685
1686 isDefined: function(input)
1687 {
1688 return input != undefined && input != null;
1689 },
1690
1691 reverseArray: function(input /* Array */)
1692 {
1693 var result = [];
1694 var len = input.length;
1695 for ( var i = len-1; i >= 0; i-- )
1696 {
1697 result.push(input[i]);
1698 }
1699
1700 return result;
1701 },
1702
1703 merge: function(a /* Object */, b /* Object */)
1704 {
1705 var c = {};
1706 var i;
1707
1708 for ( i in a )
1709 {
1710 if ( a.hasOwnProperty(i) )
1711 if ( /^\d+$/.test(i) ) c.push(a);
1712 else c[i] = a[i];
1713 }
1714 for ( i in b )
1715 {
1716 if ( b.hasOwnProperty(i) )
1717 if ( /^\d+$/.test(i) ) c.push(b);
1718 else c[i] = b[i];
1719 }
1720
1721 return c;
1722 },
1723
1724 strRepeat: function(str /* String */, count /* Integer */)
1725 {
1726 var i;
1727 var result = '';
1728 for ( i = 0; i < count; i++ ) result += str;
1729 return result;
1730 },
1731
1732 subStrCount: function(string, subString, start, length)
1733 {
1734 var c = 0;
1735
1736 string = '' + string;
1737 subString = '' + subString;
1738
1739 if ( start != undefined ) string = string.substr(start);
1740 if ( length != undefined ) string = string.substr(0, length);
1741
1742 var len = string.length;
1743 var sublen = subString.length;
1744 for ( var i = 0; i < len; i++ )
1745 {
1746 if ( subString == string.substr(i, sublen) )
1747 c++;
1748 i += sublen - 1;
1749 }
1750
1751 return c;
1752 },
1753
1754 trim: function(str /* String */)
1755 {
1756 return (str+'').replace(/^ +/,'').replace(/ +$/,'');
1757 }
1758};
1759/**
1760 * YamlEscaper encapsulates escaping rules for single and double-quoted
1761 * YAML strings.
1762 *
1763 * @author Matthew Lewinski <matthew@lewinski.org>
1764 */
1765YamlEscaper = function(){};
1766YamlEscaper.prototype =
1767{
1768 /**
1769 * Determines if a JS value would require double quoting in YAML.
1770 *
1771 * @param string value A JS value
1772 *
1773 * @return Boolean True if the value would require double quotes.
1774 */
1775 requiresDoubleQuoting: function(value)
1776 {
1777 return new RegExp(YamlEscaper.REGEX_CHARACTER_TO_ESCAPE).test(value);
1778 },
1779
1780 /**
1781 * Escapes and surrounds a JS value with double quotes.
1782 *
1783 * @param string value A JS value
1784 *
1785 * @return string The quoted, escaped string
1786 */
1787 escapeWithDoubleQuotes: function(value)
1788 {
1789 value = value + '';
1790 var len = YamlEscaper.escapees.length;
1791 var maxlen = YamlEscaper.escaped.length;
1792 var esc = YamlEscaper.escaped;
1793 for (var i = 0; i < len; ++i)
1794 if ( i >= maxlen ) esc.push('');
1795
1796 var ret = '';
1797 ret = value.replace(new RegExp(YamlEscaper.escapees.join('|'),'g'), function(str){
1798 for(var i = 0; i < len; ++i){
1799 if( str == YamlEscaper.escapees[i] )
1800 return esc[i];
1801 }
1802 });
1803 return '"' + ret + '"';
1804 },
1805
1806 /**
1807 * Determines if a JS value would require single quoting in YAML.
1808 *
1809 * @param string value A JS value
1810 *
1811 * @return Boolean True if the value would require single quotes.
1812 */
1813 requiresSingleQuoting: function(value)
1814 {
1815 return /[\s'":{}[\],&*#?]|^[-?|<>=!%@`]/.test(value);
1816 },
1817
1818 /**
1819 * Escapes and surrounds a JS value with single quotes.
1820 *
1821 * @param string value A JS value
1822 *
1823 * @return string The quoted, escaped string
1824 */
1825 escapeWithSingleQuotes : function(value)
1826 {
1827 return "'" + value.replace(/'/g, "''") + "'";
1828 }
1829};
1830
1831// Characters that would cause a dumped string to require double quoting.
1832YamlEscaper.REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
1833
1834// Mapping arrays for escaping a double quoted string. The backslash is
1835// first to ensure proper escaping.
1836YamlEscaper.escapees = ['\\\\', '\\"', '"',
1837 "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
1838 "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
1839 "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
1840 "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
1841 "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"];
1842YamlEscaper.escaped = ['\\"', '\\\\', '\\"',
1843 "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
1844 "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
1845 "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
1846 "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f",
1847 "\\N", "\\_", "\\L", "\\P"];
1848/**
1849 * YamlUnescaper encapsulates unescaping rules for single and double-quoted
1850 * YAML strings.
1851 *
1852 * @author Matthew Lewinski <matthew@lewinski.org>
1853 */
1854var YamlUnescaper = function(){};
1855YamlUnescaper.prototype =
1856{
1857 /**
1858 * Unescapes a single quoted string.
1859 *
1860 * @param string value A single quoted string.
1861 *
1862 * @return string The unescaped string.
1863 */
1864 unescapeSingleQuotedString: function(value)
1865 {
1866 return value.replace(/''/g, "'");
1867 },
1868
1869 /**
1870 * Unescapes a double quoted string.
1871 *
1872 * @param string value A double quoted string.
1873 *
1874 * @return string The unescaped string.
1875 */
1876 unescapeDoubleQuotedString: function(value)
1877 {
1878 var callback = function(m) {
1879 return new YamlUnescaper().unescapeCharacter(m);
1880 };
1881
1882 // evaluate the string
1883 return value.replace(new RegExp(YamlUnescaper.REGEX_ESCAPED_CHARACTER, 'g'), callback);
1884 },
1885
1886 /**
1887 * Unescapes a character that was found in a double-quoted string
1888 *
1889 * @param string value An escaped character
1890 *
1891 * @return string The unescaped character
1892 */
1893 unescapeCharacter: function(value)
1894 {
1895 switch (value.charAt(1)) {
1896 case '0':
1897 return String.fromCharCode(0);
1898 case 'a':
1899 return String.fromCharCode(7);
1900 case 'b':
1901 return String.fromCharCode(8);
1902 case 't':
1903 return "\t";
1904 case "\t":
1905 return "\t";
1906 case 'n':
1907 return "\n";
1908 case 'v':
1909 return String.fromCharCode(11);
1910 case 'f':
1911 return String.fromCharCode(12);
1912 case 'r':
1913 return String.fromCharCode(13);
1914 case 'e':
1915 return "\x1b";
1916 case ' ':
1917 return ' ';
1918 case '"':
1919 return '"';
1920 case '/':
1921 return '/';
1922 case '\\':
1923 return '\\';
1924 case 'N':
1925 // U+0085 NEXT LINE
1926 return "\x00\x85";
1927 case '_':
1928 // U+00A0 NO-BREAK SPACE
1929 return "\x00\xA0";
1930 case 'L':
1931 // U+2028 LINE SEPARATOR
1932 return "\x20\x28";
1933 case 'P':
1934 // U+2029 PARAGRAPH SEPARATOR
1935 return "\x20\x29";
1936 case 'x':
1937 return this.pack('n', new YamlInline().hexdec(value.substr(2, 2)));
1938 case 'u':
1939 return this.pack('n', new YamlInline().hexdec(value.substr(2, 4)));
1940 case 'U':
1941 return this.pack('N', new YamlInline().hexdec(value.substr(2, 8)));
1942 }
1943 },
1944
1945 /**
1946 * @see http://phpjs.org/functions/pack
1947 * @warning only modes used above copied
1948 */
1949 pack: function(B){var g=0,o=1,m="",l="",z=0,p=[],E,s,C,I,h,c;var d,b,x,H,u,e,A,q,D,t,w,a,G,F,y,v,f;while(g<B.length){E=B.charAt(g);s="";g++;while((g<B.length)&&(B.charAt(g).match(/[\d\*]/)!==null)){s+=B.charAt(g);g++}if(s===""){s="1"}switch(E){case"n":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;case"N":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>24&255);m+=String.fromCharCode(arguments[o]>>16&255);m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;default:throw new Error("Warning: pack() Type "+E+": unknown format code")}}if(o<arguments.length){throw new Error("Warning: pack(): "+(arguments.length-o)+" arguments unused")}return m}
1950}
1951
1952// Regex fragment that matches an escaped character in a double quoted
1953// string.
1954// why escape quotes, ffs!
1955YamlUnescaper.REGEX_ESCAPED_CHARACTER = '\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})';
1956
1957/**
1958 * YamlDumper dumps JS variables to YAML strings.
1959 *
1960 * @author Fabien Potencier <fabien@symfony.com>
1961 */
1962var YamlDumper = function(){};
1963YamlDumper.prototype =
1964{
1965 /**
1966 * Dumps a JS value to YAML.
1967 *
1968 * @param mixed input The JS value
1969 * @param integer inline The level where you switch to inline YAML
1970 * @param integer indent The level o indentation indentation (used internally)
1971 *
1972 * @return string The YAML representation of the JS value
1973 */
1974 dump: function(input, inline, indent)
1975 {
1976 if ( inline == null ) inline = 0;
1977 if ( indent == null ) indent = 0;
1978 var output = '';
1979 var prefix = indent ? this.strRepeat(' ', indent) : '';
1980 var yaml;
1981 if (!this.numSpacesForIndentation) this.numSpacesForIndentation = 2;
1982
1983 if ( inline <= 0 || !this.isObject(input) || this.isEmpty(input) )
1984 {
1985 yaml = new YamlInline();
1986 output += prefix + yaml.dump(input);
1987 }
1988 else
1989 {
1990 var isAHash = !this.arrayEquals(this.getKeys(input), this.range(0,input.length - 1));
1991 var willBeInlined;
1992
1993 for ( var key in input )
1994 {
1995 if ( input.hasOwnProperty(key) )
1996 {
1997 willBeInlined = inline - 1 <= 0 || !this.isObject(input[key]) || this.isEmpty(input[key]);
1998
1999 if ( isAHash ) yaml = new YamlInline();
2000
2001 output +=
2002 prefix + '' +
2003 (isAHash ? yaml.dump(key)+':' : '-') + '' +
2004 (willBeInlined ? ' ' : "\n") + '' +
2005 this.dump(input[key], inline - 1, (willBeInlined ? 0 : indent + this.numSpacesForIndentation)) + '' +
2006 (willBeInlined ? "\n" : '');
2007 }
2008 }
2009 }
2010
2011 return output;
2012 },
2013
2014 strRepeat: function(str /* String */, count /* Integer */)
2015 {
2016 var i;
2017 var result = '';
2018 for ( i = 0; i < count; i++ ) result += str;
2019 return result;
2020 },
2021
2022 isObject: function(input)
2023 {
2024 return this.isDefined(input) && typeof(input) == 'object';
2025 },
2026
2027 isEmpty: function(input)
2028 {
2029 var ret = input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false;
2030 if ( !ret && typeof(input) == "object" && !(input instanceof Array)){
2031 var propCount = 0;
2032 for ( var key in input )
2033 if ( input.hasOwnProperty(key) ) propCount++;
2034 ret = !propCount;
2035 }
2036 return ret;
2037 },
2038
2039 isDefined: function(input)
2040 {
2041 return input != undefined && input != null;
2042 },
2043
2044 getKeys: function(tab)
2045 {
2046 var ret = [];
2047
2048 for ( var name in tab )
2049 {
2050 if ( tab.hasOwnProperty(name) )
2051 {
2052 ret.push(name);
2053 }
2054 }
2055
2056 return ret;
2057 },
2058
2059 range: function(start, end)
2060 {
2061 if ( start > end ) return [];
2062
2063 var ret = [];
2064
2065 for ( var i = start; i <= end; i++ )
2066 {
2067 ret.push(i);
2068 }
2069
2070 return ret;
2071 },
2072
2073 arrayEquals: function(a,b)
2074 {
2075 if ( a.length != b.length ) return false;
2076
2077 var len = a.length;
2078
2079 for ( var i = 0; i < len; i++ )
2080 {
2081 if ( a[i] != b[i] ) return false;
2082 }
2083
2084 return true;
2085 }
2086};
2087})();