1/** 2 * CSS-JSON Converter for JavaScript 3 * Converts CSS to JSON and back. 4 * Version 2.1 5 * 6 * Released under the MIT license. 7 * 8 * Copyright (c) 2013 Aram Kocharyan, http://aramk.com/ 9 10 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 11 documentation files (the "Software"), to deal in the Software without restriction, including without limitation 12 the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 13 to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in all copies or substantial portions 16 of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 19 THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 THE SOFTWARE. 23 */ 24 25var CSSJSON = new function () { 26 27 var base = this; 28 29 base.init = function () { 30 // String functions 31 String.prototype.trim = function () { 32 return this.replace(/^\s+|\s+$/g, ''); 33 }; 34 35 String.prototype.repeat = function (n) { 36 return new Array(1 + n).join(this); 37 }; 38 }; 39 base.init(); 40 41 var selX = /([^\s\;\{\}][^\;\{\}]*)\{/g; 42 var endX = /\}/g; 43 var lineX = /([^\;\{\}]*)\;/g; 44 var commentX = /\/\*[\s\S]*?\*\//g; 45 var lineAttrX = /([^\:]+):([^\;]*);/; 46 47 // This is used, a concatenation of all above. We use alternation to 48 // capture. 49 var altX = /(\/\*[\s\S]*?\*\/)|([^\s\;\{\}][^\;\{\}]*(?=\{))|(\})|([^\;\{\}]+\;(?!\s*\*\/))/gmi; 50 51 // Capture groups 52 var capComment = 1; 53 var capSelector = 2; 54 var capEnd = 3; 55 var capAttr = 4; 56 57 var isEmpty = function (x) { 58 return typeof x == 'undefined' || x.length == 0 || x == null; 59 }; 60 61 /** 62 * Input is css string and current pos, returns JSON object 63 * 64 * @param cssString 65 * The CSS string. 66 * @param args 67 * An optional argument object. ordered: Whether order of 68 * comments and other nodes should be kept in the output. This 69 * will return an object where all the keys are numbers and the 70 * values are objects containing "name" and "value" keys for each 71 * node. comments: Whether to capture comments. split: Whether to 72 * split each comma separated list of selectors. 73 */ 74 base.toJSON = function (cssString, args) { 75 var node = { 76 children: {}, 77 attributes: {} 78 }; 79 var match = null; 80 var count = 0; 81 82 if (typeof args == 'undefined') { 83 var args = { 84 ordered: false, 85 comments: false, 86 stripComments: false, 87 split: false 88 }; 89 } 90 if (args.stripComments) { 91 args.comments = false; 92 cssString = cssString.replace(commentX, ''); 93 } 94 95 while ((match = altX.exec(cssString)) != null) { 96 if (!isEmpty(match[capComment]) && args.comments) { 97 // Comment 98 var add = match[capComment].trim(); 99 node[count++] = add; 100 } else if (!isEmpty(match[capSelector])) { 101 // New node, we recurse 102 var name = match[capSelector].trim(); 103 // This will return when we encounter a closing brace 104 var newNode = base.toJSON(cssString, args); 105 if (args.ordered) { 106 var obj = {}; 107 obj['name'] = name; 108 obj['value'] = newNode; 109 // Since we must use key as index to keep order and not 110 // name, this will differentiate between a Rule Node and an 111 // Attribute, since both contain a name and value pair. 112 obj['type'] = 'rule'; 113 node[count++] = obj; 114 } else { 115 if (args.split) { 116 var bits = name.split(','); 117 } else { 118 var bits = [name]; 119 } 120 for (i in bits) { 121 var sel = bits[i].trim(); 122 if (sel in node.children) { 123 for (var att in newNode.attributes) { 124 node.children[sel].attributes[att] = newNode.attributes[att]; 125 } 126 } else { 127 node.children[sel] = newNode; 128 } 129 } 130 } 131 } else if (!isEmpty(match[capEnd])) { 132 // Node has finished 133 return node; 134 } else if (!isEmpty(match[capAttr])) { 135 var line = match[capAttr].trim(); 136 var attr = lineAttrX.exec(line); 137 if (attr) { 138 // Attribute 139 var name = attr[1].trim(); 140 var value = attr[2].trim(); 141 if (args.ordered) { 142 var obj = {}; 143 obj['name'] = name; 144 obj['value'] = value; 145 obj['type'] = 'attr'; 146 node[count++] = obj; 147 } else { 148 if (name in node.attributes) { 149 var currVal = node.attributes[name]; 150 if (!(currVal instanceof Array)) { 151 node.attributes[name] = [currVal]; 152 } 153 node.attributes[name].push(value); 154 } else { 155 node.attributes[name] = value; 156 } 157 } 158 } else { 159 // Semicolon terminated line 160 node[count++] = line; 161 } 162 } 163 } 164 165 return node; 166 }; 167 168 /** 169 * @param node 170 * A JSON node. 171 * @param depth 172 * The depth of the current node; used for indentation and 173 * optional. 174 * @param breaks 175 * Whether to add line breaks in the output. 176 */ 177 base.toCSS = function (node, depth, breaks) { 178 var cssString = ''; 179 if (typeof depth == 'undefined') { 180 depth = 0; 181 } 182 if (typeof breaks == 'undefined') { 183 breaks = false;
184 } 185 if (typeof node.attributes !== 'undefined' && node.attributes) { 186 for (i in node.attributes) { 187 var att = node.attributes[i]; 188 if (att instanceof Array) { 189 for (var j = 0; j < att.length; j++) { 190 cssString += strAttr(i, att[j], depth); 191 } 192 } else { 193 cssString += strAttr(i, att, depth); 194 } 195 } 196 } 197 if (node.children) { 198 var first = true; 199 for (i in node.children) { 200 if (breaks && !first) { 201 cssString += '\n'; 202 } else { 203 first = false; 204 } 205 cssString += strNode(i, node.children[i], depth); 206 } 207 } 208 return cssString; 209 }; 210 211 // Helpers 212 213 var strAttr = function (name, value, depth) { 214 return '\t'.repeat(depth) + name + ': ' + value + ';\n'; 215 }; 216 217 var strNode = function (name, value, depth) { 218 var cssString = '\t'.repeat(depth) + name + ' {\n'; 219 cssString += base.toCSS(value, depth + 1); 220 cssString += '\t'.repeat(depth) + '}\n'; 221 return cssString; 222 }; 223 224};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.