UNPKG

assetgraph

Version:

An auto discovery dependency graph based optimization framework for web pages and applications

123 lines (113 loc) 6.03 kB
// Javascript object literal parser // Splits an object literal string into a set of top-level key-value pairs // (c) Michael Best (https://github.com/mbest) // License: MIT (http://www.opensource.org/licenses/mit-license.php) // Version 2.1.0 // https://github.com/umdjs/umd // Support AMD, Node.js, and globals ;(function (root, factory) { if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like enviroments that support module.exports, // like Node. module.exports = factory(); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory); } else { // Browser globals (root is window) root.parseObjectLiteral = factory(); } }(this, function(undefined) { // This parser is inspired by json-sans-eval by Mike Samuel (http://code.google.com/p/json-sans-eval/) // These two match strings, either with double quotes or single quotes const stringDouble = '"(?:[^"\\\\]|\\\\.)*"', stringSingle = "'(?:[^'\\\\]|\\\\.)*'", // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions // as a regular expression (this is handled by the parsing loop below). stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*', // These characters have special meaning to the parser and must not appear in the middle of a // token, except as part of a string. specials = ',"\'{}()/:[\\]', // Match text (at least two characters) that does not contain any of the above special characters, // although some of the special characters are allowed to start it (all but the colon and comma). // The text can contain spaces, but leading or trailing spaces are skipped. everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']', // Match any non-space character not matched already. This will match colons and commas, since they're // not matched by "everyThingElse", but will also match any other single character that wasn't already // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace). oneNotSpace = '[^\\s]', // Create the actual regular expression by or-ing the above strings. The order is important. token = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'), // Match end of previous token to determine whether a slash is a division or regex. divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/, keywordRegexLookBehind = {'in':1,'return':1,'typeof':1}; function trim(string) { return string == null ? '' : string.trim ? string.trim() : string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); } return function(objectLiteralString) { // Trim leading and trailing spaces from the string let str = trim(objectLiteralString); // Trim braces '{' surrounding the whole object literal if (str.charCodeAt(0) === 123) str = str.slice(1, -1); // Split into tokens const result = []; let toks = str.match(token); let key; let values = []; let depth = 0; if (toks) { // Append a comma so that we don't need a separate code block to deal with the last item toks.push(','); for (let i = 0, tok; tok = toks[i]; ++i) { const c = tok.charCodeAt(0); // A comma signals the end of a key/value pair if depth is zero if (c === 44) { // "," if (depth <= 0) { if (!key && values.length === 1) { key = values.pop(); } result.push([key, values.length ? values.join('') : undefined]); key = undefined; values = []; depth = 0; continue; } // Simply skip the colon that separates the name and value } else if (c === 58) { // ":" if (!depth && !key && values.length === 1) { key = values.pop(); continue; } // A set of slashes is initially matched as a regular expression, but could be division } else if (c === 47 && i && tok.length > 1) { // "/" // Look at the end of the previous token to determine if the slash is actually division const match = toks[i-1].match(divisionLookBehind); if (match && !keywordRegexLookBehind[match[0]]) { // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash) str = str.substr(str.indexOf(tok) + 1); toks = str.match(token); toks.push(','); i = -1; // Continue with just the slash tok = '/'; } // Increment depth for parentheses, braces, and brackets so that interior commas are ignored } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '[' ++depth; } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']' --depth; // The key will be the first token; if it's a string, trim the quotes } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'" tok = tok.slice(1, -1); } values.push(tok); } } return result; }; }));