yaml.legacy.js 51 KB

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