6c93141f20c6e1e0f8c11e613fbcb5deda28d1ff
[platform/framework/web/crosswalk-tizen.git] /
1 'use strict';
2
3
4 var common              = require('./common');
5 var YAMLException       = require('./exception');
6 var DEFAULT_FULL_SCHEMA = require('./schema/default_full');
7 var DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');
8
9
10 var _toString       = Object.prototype.toString;
11 var _hasOwnProperty = Object.prototype.hasOwnProperty;
12
13
14 var CHAR_TAB                  = 0x09; /* Tab */
15 var CHAR_LINE_FEED            = 0x0A; /* LF */
16 var CHAR_CARRIAGE_RETURN      = 0x0D; /* CR */
17 var CHAR_SPACE                = 0x20; /* Space */
18 var CHAR_EXCLAMATION          = 0x21; /* ! */
19 var CHAR_DOUBLE_QUOTE         = 0x22; /* " */
20 var CHAR_SHARP                = 0x23; /* # */
21 var CHAR_PERCENT              = 0x25; /* % */
22 var CHAR_AMPERSAND            = 0x26; /* & */
23 var CHAR_SINGLE_QUOTE         = 0x27; /* ' */
24 var CHAR_ASTERISK             = 0x2A; /* * */
25 var CHAR_COMMA                = 0x2C; /* , */
26 var CHAR_MINUS                = 0x2D; /* - */
27 var CHAR_COLON                = 0x3A; /* : */
28 var CHAR_GREATER_THAN         = 0x3E; /* > */
29 var CHAR_QUESTION             = 0x3F; /* ? */
30 var CHAR_COMMERCIAL_AT        = 0x40; /* @ */
31 var CHAR_LEFT_SQUARE_BRACKET  = 0x5B; /* [ */
32 var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
33 var CHAR_GRAVE_ACCENT         = 0x60; /* ` */
34 var CHAR_LEFT_CURLY_BRACKET   = 0x7B; /* { */
35 var CHAR_VERTICAL_LINE        = 0x7C; /* | */
36 var CHAR_RIGHT_CURLY_BRACKET  = 0x7D; /* } */
37
38
39 var ESCAPE_SEQUENCES = {};
40
41 ESCAPE_SEQUENCES[0x00]   = '\\0';
42 ESCAPE_SEQUENCES[0x07]   = '\\a';
43 ESCAPE_SEQUENCES[0x08]   = '\\b';
44 ESCAPE_SEQUENCES[0x09]   = '\\t';
45 ESCAPE_SEQUENCES[0x0A]   = '\\n';
46 ESCAPE_SEQUENCES[0x0B]   = '\\v';
47 ESCAPE_SEQUENCES[0x0C]   = '\\f';
48 ESCAPE_SEQUENCES[0x0D]   = '\\r';
49 ESCAPE_SEQUENCES[0x1B]   = '\\e';
50 ESCAPE_SEQUENCES[0x22]   = '\\"';
51 ESCAPE_SEQUENCES[0x5C]   = '\\\\';
52 ESCAPE_SEQUENCES[0x85]   = '\\N';
53 ESCAPE_SEQUENCES[0xA0]   = '\\_';
54 ESCAPE_SEQUENCES[0x2028] = '\\L';
55 ESCAPE_SEQUENCES[0x2029] = '\\P';
56
57
58 var DEPRECATED_BOOLEANS_SYNTAX = [
59   'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',
60   'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'
61 ];
62
63
64 function compileStyleMap(schema, map) {
65   var result, keys, index, length, tag, style, type;
66
67   if (null === map) {
68     return {};
69   }
70
71   result = {};
72   keys = Object.keys(map);
73
74   for (index = 0, length = keys.length; index < length; index += 1) {
75     tag = keys[index];
76     style = String(map[tag]);
77
78     if ('!!' === tag.slice(0, 2)) {
79       tag = 'tag:yaml.org,2002:' + tag.slice(2);
80     }
81
82     type = schema.compiledTypeMap[tag];
83
84     if (type && _hasOwnProperty.call(type.styleAliases, style)) {
85       style = type.styleAliases[style];
86     }
87
88     result[tag] = style;
89   }
90
91   return result;
92 }
93
94
95 function encodeHex(character) {
96   var string, handle, length;
97
98   string = character.toString(16).toUpperCase();
99
100   if (character <= 0xFF) {
101     handle = 'x';
102     length = 2;
103   } else if (character <= 0xFFFF) {
104     handle = 'u';
105     length = 4;
106   } else if (character <= 0xFFFFFFFF) {
107     handle = 'U';
108     length = 8;
109   } else {
110     throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');
111   }
112
113   return '\\' + handle + common.repeat('0', length - string.length) + string;
114 }
115
116
117 function State(options) {
118   this.schema      = options['schema'] || DEFAULT_FULL_SCHEMA;
119   this.indent      = Math.max(1, (options['indent'] || 2));
120   this.skipInvalid = options['skipInvalid'] || false;
121   this.flowLevel   = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);
122   this.styleMap    = compileStyleMap(this.schema, options['styles'] || null);
123
124   this.implicitTypes = this.schema.compiledImplicit;
125   this.explicitTypes = this.schema.compiledExplicit;
126
127   this.tag = null;
128   this.result = '';
129
130   this.duplicates = [];
131   this.usedDuplicates = null;
132 }
133
134
135 function generateNextLine(state, level) {
136   return '\n' + common.repeat(' ', state.indent * level);
137 }
138
139 function testImplicitResolving(state, str) {
140   var index, length, type;
141
142   for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
143     type = state.implicitTypes[index];
144
145     if (type.resolve(str)) {
146       return true;
147     }
148   }
149
150   return false;
151 }
152
153 function writeScalar(state, object) {
154   var isQuoted, checkpoint, position, length, character, first;
155
156   state.dump = '';
157   isQuoted = false;
158   checkpoint = 0;
159   first = object.charCodeAt(0) || 0;
160
161   if (-1 !== DEPRECATED_BOOLEANS_SYNTAX.indexOf(object)) {
162     // Ensure compatibility with YAML 1.0/1.1 loaders.
163     isQuoted = true;
164   } else if (0 === object.length) {
165     // Quote empty string
166     isQuoted = true;
167   } else if (CHAR_SPACE    === first ||
168              CHAR_SPACE    === object.charCodeAt(object.length - 1)) {
169     isQuoted = true;
170   } else if (CHAR_MINUS    === first ||
171              CHAR_QUESTION === first) {
172     // Don't check second symbol for simplicity
173     isQuoted = true;
174   }
175
176   for (position = 0, length = object.length; position < length; position += 1) {
177     character = object.charCodeAt(position);
178
179     if (!isQuoted) {
180       if (CHAR_TAB                  === character ||
181           CHAR_LINE_FEED            === character ||
182           CHAR_CARRIAGE_RETURN      === character ||
183           CHAR_COMMA                === character ||
184           CHAR_LEFT_SQUARE_BRACKET  === character ||
185           CHAR_RIGHT_SQUARE_BRACKET === character ||
186           CHAR_LEFT_CURLY_BRACKET   === character ||
187           CHAR_RIGHT_CURLY_BRACKET  === character ||
188           CHAR_SHARP                === character ||
189           CHAR_AMPERSAND            === character ||
190           CHAR_ASTERISK             === character ||
191           CHAR_EXCLAMATION          === character ||
192           CHAR_VERTICAL_LINE        === character ||
193           CHAR_GREATER_THAN         === character ||
194           CHAR_SINGLE_QUOTE         === character ||
195           CHAR_DOUBLE_QUOTE         === character ||
196           CHAR_PERCENT              === character ||
197           CHAR_COMMERCIAL_AT        === character ||
198           CHAR_COLON                === character ||
199           CHAR_GRAVE_ACCENT         === character) {
200         isQuoted = true;
201       }
202     }
203
204     if (ESCAPE_SEQUENCES[character] ||
205         !((0x00020 <= character && character <= 0x00007E) ||
206           (0x00085 === character)                         ||
207           (0x000A0 <= character && character <= 0x00D7FF) ||
208           (0x0E000 <= character && character <= 0x00FFFD) ||
209           (0x10000 <= character && character <= 0x10FFFF))) {
210       state.dump += object.slice(checkpoint, position);
211       state.dump += ESCAPE_SEQUENCES[character] || encodeHex(character);
212       checkpoint = position + 1;
213       isQuoted = true;
214     }
215   }
216
217   if (checkpoint < position) {
218     state.dump += object.slice(checkpoint, position);
219   }
220
221   if (!isQuoted && testImplicitResolving(state, state.dump)) {
222     isQuoted = true;
223   }
224
225   if (isQuoted) {
226     state.dump = '"' + state.dump + '"';
227   }
228 }
229
230 function writeFlowSequence(state, level, object) {
231   var _result = '',
232       _tag    = state.tag,
233       index,
234       length;
235
236   for (index = 0, length = object.length; index < length; index += 1) {
237     // Write only valid elements.
238     if (writeNode(state, level, object[index], false, false)) {
239       if (0 !== index) {
240         _result += ', ';
241       }
242       _result += state.dump;
243     }
244   }
245
246   state.tag = _tag;
247   state.dump = '[' + _result + ']';
248 }
249
250 function writeBlockSequence(state, level, object, compact) {
251   var _result = '',
252       _tag    = state.tag,
253       index,
254       length;
255
256   for (index = 0, length = object.length; index < length; index += 1) {
257     // Write only valid elements.
258     if (writeNode(state, level + 1, object[index], true, true)) {
259       if (!compact || 0 !== index) {
260         _result += generateNextLine(state, level);
261       }
262       _result += '- ' + state.dump;
263     }
264   }
265
266   state.tag = _tag;
267   state.dump = _result || '[]'; // Empty sequence if no valid values.
268 }
269
270 function writeFlowMapping(state, level, object) {
271   var _result       = '',
272       _tag          = state.tag,
273       objectKeyList = Object.keys(object),
274       index,
275       length,
276       objectKey,
277       objectValue,
278       pairBuffer;
279
280   for (index = 0, length = objectKeyList.length; index < length; index += 1) {
281     pairBuffer = '';
282
283     if (0 !== index) {
284       pairBuffer += ', ';
285     }
286
287     objectKey = objectKeyList[index];
288     objectValue = object[objectKey];
289
290     if (!writeNode(state, level, objectKey, false, false)) {
291       continue; // Skip this pair because of invalid key;
292     }
293
294     if (state.dump.length > 1024) {
295       pairBuffer += '? ';
296     }
297
298     pairBuffer += state.dump + ': ';
299
300     if (!writeNode(state, level, objectValue, false, false)) {
301       continue; // Skip this pair because of invalid value.
302     }
303
304     pairBuffer += state.dump;
305
306     // Both key and value are valid.
307     _result += pairBuffer;
308   }
309
310   state.tag = _tag;
311   state.dump = '{' + _result + '}';
312 }
313
314 function writeBlockMapping(state, level, object, compact) {
315   var _result       = '',
316       _tag          = state.tag,
317       objectKeyList = Object.keys(object),
318       index,
319       length,
320       objectKey,
321       objectValue,
322       explicitPair,
323       pairBuffer;
324
325   for (index = 0, length = objectKeyList.length; index < length; index += 1) {
326     pairBuffer = '';
327
328     if (!compact || 0 !== index) {
329       pairBuffer += generateNextLine(state, level);
330     }
331
332     objectKey = objectKeyList[index];
333     objectValue = object[objectKey];
334
335     if (!writeNode(state, level + 1, objectKey, true, true)) {
336       continue; // Skip this pair because of invalid key.
337     }
338
339     explicitPair = (null !== state.tag && '?' !== state.tag) ||
340                    (state.dump && state.dump.length > 1024);
341
342     if (explicitPair) {
343       if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
344         pairBuffer += '?';
345       } else {
346         pairBuffer += '? ';
347       }
348     }
349
350     pairBuffer += state.dump;
351
352     if (explicitPair) {
353       pairBuffer += generateNextLine(state, level);
354     }
355
356     if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
357       continue; // Skip this pair because of invalid value.
358     }
359
360     if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
361       pairBuffer += ':';
362     } else {
363       pairBuffer += ': ';
364     }
365
366     pairBuffer += state.dump;
367
368     // Both key and value are valid.
369     _result += pairBuffer;
370   }
371
372   state.tag = _tag;
373   state.dump = _result || '{}'; // Empty mapping if no valid pairs.
374 }
375
376 function detectType(state, object, explicit) {
377   var _result, typeList, index, length, type, style;
378
379   typeList = explicit ? state.explicitTypes : state.implicitTypes;
380
381   for (index = 0, length = typeList.length; index < length; index += 1) {
382     type = typeList[index];
383
384     if ((type.instanceOf  || type.predicate) &&
385         (!type.instanceOf || (('object' === typeof object) && (object instanceof type.instanceOf))) &&
386         (!type.predicate  || type.predicate(object))) {
387
388       state.tag = explicit ? type.tag : '?';
389
390       if (type.represent) {
391         style = state.styleMap[type.tag] || type.defaultStyle;
392
393         if ('[object Function]' === _toString.call(type.represent)) {
394           _result = type.represent(object, style);
395         } else if (_hasOwnProperty.call(type.represent, style)) {
396           _result = type.represent[style](object, style);
397         } else {
398           throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style');
399         }
400
401         state.dump = _result;
402       }
403
404       return true;
405     }
406   }
407
408   return false;
409 }
410
411 // Serializes `object` and writes it to global `result`.
412 // Returns true on success, or false on invalid object.
413 //
414 function writeNode(state, level, object, block, compact) {
415   state.tag = null;
416   state.dump = object;
417
418   if (!detectType(state, object, false)) {
419     detectType(state, object, true);
420   }
421
422   var type = _toString.call(state.dump);
423
424   if (block) {
425     block = (0 > state.flowLevel || state.flowLevel > level);
426   }
427
428   if ((null !== state.tag && '?' !== state.tag) || (2 !== state.indent && level > 0)) {
429     compact = false;
430   }
431
432   var objectOrArray = '[object Object]' === type || '[object Array]' === type,
433       duplicateIndex,
434       duplicate;
435
436   if (objectOrArray) {
437     duplicateIndex = state.duplicates.indexOf(object);
438     duplicate = duplicateIndex !== -1;
439   }
440
441   if (duplicate && state.usedDuplicates[duplicateIndex]) {
442     state.dump = '*ref_' + duplicateIndex;
443   } else {
444     if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
445       state.usedDuplicates[duplicateIndex] = true;
446     }
447     if ('[object Object]' === type) {
448       if (block && (0 !== Object.keys(state.dump).length)) {
449         writeBlockMapping(state, level, state.dump, compact);
450         if (duplicate) {
451           state.dump = '&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
452         }
453       } else {
454         writeFlowMapping(state, level, state.dump);
455         if (duplicate) {
456           state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
457         }
458       }
459     } else if ('[object Array]' === type) {
460       if (block && (0 !== state.dump.length)) {
461         writeBlockSequence(state, level, state.dump, compact);
462         if (duplicate) {
463           state.dump = '&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
464         }
465       } else {
466         writeFlowSequence(state, level, state.dump);
467         if (duplicate) {
468           state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
469         }
470       }
471     } else if ('[object String]' === type) {
472       if ('?' !== state.tag) {
473         writeScalar(state, state.dump);
474       }
475     } else if (state.skipInvalid) {
476       return false;
477     } else {
478       throw new YAMLException('unacceptable kind of an object to dump ' + type);
479     }
480
481     if (null !== state.tag && '?' !== state.tag) {
482       state.dump = '!<' + state.tag + '> ' + state.dump;
483     }
484   }
485
486   return true;
487 }
488
489 function getDuplicateReferences(object, state) {
490   var objects = [],
491       duplicatesIndexes = [],
492       index,
493       length;
494
495   inspectNode(object, objects, duplicatesIndexes);
496
497   for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
498     state.duplicates.push(objects[duplicatesIndexes[index]]);
499   }
500   state.usedDuplicates = new Array(length);
501 }
502
503 function inspectNode(object, objects, duplicatesIndexes) {
504   var type = _toString.call(object),
505       objectKeyList,
506       index,
507       length;
508
509   if (null !== object && 'object' === typeof object) {
510     index = objects.indexOf(object);
511     if (-1 !== index) {
512       if (-1 === duplicatesIndexes.indexOf(index)) {
513         duplicatesIndexes.push(index);
514       }
515     } else {
516       objects.push(object);
517     
518       if(Array.isArray(object)) {
519         for (index = 0, length = object.length; index < length; index += 1) {
520           inspectNode(object[index], objects, duplicatesIndexes);
521         }
522       } else {
523         objectKeyList = Object.keys(object);
524
525         for (index = 0, length = objectKeyList.length; index < length; index += 1) {
526           inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
527         }
528       }
529     }
530   }
531 }
532
533 function dump(input, options) {
534   options = options || {};
535
536   var state = new State(options);
537
538   getDuplicateReferences(input, state);
539
540   if (writeNode(state, 0, input, true, true)) {
541     return state.dump + '\n';
542   } else {
543     return '';
544   }
545 }
546
547
548 function safeDump(input, options) {
549   return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
550 }
551
552
553 module.exports.dump     = dump;
554 module.exports.safeDump = safeDump;