├── .gitignore ├── Clean SL.jsx ├── Clean SL.png ├── README.md └── lib └── json2.js /.gitignore: -------------------------------------------------------------------------------- 1 | Clean SL Settings.txt 2 | Clean SL Settings* 3 | -------------------------------------------------------------------------------- /Clean SL.jsx: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | Clean SL.jsx 3 | Copyright (c) 2017 Tomas Šinkūnas. All rights reserved. 4 | www.rendertom.com 5 | 6 | Description: 7 | Clean SL (Clean ScriptingListenerJS.log) is a utility tool for Adobe Photoshop 8 | to clean up ScriptingListenerJS.log file. Script performs multiple actions such 9 | as cleaning-up variable names and hoisting them to the top, wraps code block 10 | into function, converts charID to string ID for better readability and such. 11 | Resulting code is clean and maintains better code readability. 12 | 13 | Features: 14 | - Load entire ScriptingListenerJS.log content 15 | - Load only last entry in ScriptingListenerJS.log 16 | - Enter ScriptingListenerJS code manually 17 | 18 | Options: 19 | - Hoist variable declaration to the top 20 | - Consolidate variables 21 | - Rename constructors 22 | - Convert charID to stringID for better readability 23 | - Shorten method names 24 | - Wrap to function block 25 | - Remove Code Junk from Action Manager code 26 | - Close Clean SL window before evaluating code 27 | - Save UI data on script quit. 28 | 29 | Released as open-source under the MIT license: 30 | The MIT License (MIT) 31 | Copyright (c) 2017 Tomas Šinkūnas www.renderTom.com 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 33 | software and associated documentation files (the "Software"), to deal in the Software 34 | without restriction, including without limitation the rights to use, copy, modify, merge, 35 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 36 | to whom the Software is furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all copies or 39 | substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 42 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 43 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 44 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 45 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 46 | DEALINGS IN THE SOFTWARE. 47 | 48 | **********************************************************************************************/ 49 | 50 | (function () { 51 | 52 | //@include "lib/json2.js" 53 | 54 | var predefined = { 55 | junkArray: [ 56 | "stringIDToTypeID( \"convertJSONdescriptor\" );", 57 | "stringIDToTypeID( \"invokeCommand\" );", 58 | "stringIDToTypeID( \"modalHTMLPending\" );", 59 | "stringIDToTypeID( \"modalStateChanged\" );", 60 | "stringIDToTypeID( \"toggleSearch\" );", 61 | "stringIDToTypeID( \"toolModalStateChanged\" );" 62 | ], 63 | constructorNames: { 64 | "ActionDescriptor": "descriptor", 65 | "ActionList": "list", 66 | "ActionReference": "reference" 67 | }, 68 | shortMethodNames: { 69 | "stringIDToTypeID": "s2t", 70 | "charIDToTypeID": "c2t" 71 | }, 72 | printToESTK: false, 73 | removeJunkOnFullLogRead: false, 74 | closeAfterSaving: false, 75 | descriptorMethods: [ 76 | // "clear", // - nothing - Clears the descriptor. 77 | // "erase", // - key - Erases a key from the descriptor. 78 | // "fromStream", // - value - Creates a descriptor from a stream of bytes; for reading from disk. 79 | // "getBoolean", // - key - Gets the value of a key of type boolean. 80 | // "getClass", // - key - Gets the value of a key of type class. 81 | // "getData", // - key - Gets raw byte data as a string value. 82 | // "getDouble", // - key - Gets the value of a key of type double. 83 | // "getEnumerationType", // - key - Gets the enumeration type of a key. 84 | // "getEnumerationValue", // - key - Gets the enumeration value of a key. 85 | // "getInteger", // - key - Gets the value of a key of type integer. 86 | // "getKey", // - index - Gets the ID of the Nth key, provided by index. 87 | // "getLargeInteger", // - key - Gets the value of a key of type large integer. 88 | // "getList", // - key - Gets the value of a key of type list. 89 | // "getObjectType", // - key - Gets the class ID of an object in a key of type object. 90 | // "getObjectValue", // - key - Gets the value of a key of type object. 91 | // "getPath", // - key - Gets the value of a key of type File. 92 | // "getReference", // - key - Gets the value of a key of type ActionReference. 93 | // "getString", // - key - Gets the value of a key of type string. 94 | // "getType", // - key - Gets the type of a key. 95 | // "getUnitDoubleType", // - key - Gets the unit type of a key of type UnitDouble. 96 | // "getUnitDoubleValue", // - Gets the value of a key of type UnitDouble. 97 | // "hasKey", // - key - Checks whether the descriptor contains the provided key. 98 | // "isEqual", // - Determines whether the descriptor is the same as another descriptor. 99 | "putBoolean", // - key, value - Sets the value for a key whose type is boolean. 100 | // "putClass", // - key, value - Sets the value for a key whose type is class. 101 | // "putData", // - key, value - Puts raw byte data as a string value. 102 | "putDouble", // - key, value - Sets the value for a key whose type is double. 103 | // "putEnumerated", // - key, enumType, value - Sets the enumeration type and value for a key. 104 | "putInteger", // - key, value - Sets the value for a key whose type is integer. 105 | "putLargeInteger", // - key, value - Sets the value for a key whose type is large integer. 106 | // "putList", // - key, value - Sets the value for a key whose type is an ActionList object. 107 | // "putObject", // - key, classID, value - Sets the value for a key whose type is an object, represented by an Action Descriptor. 108 | "putPath", // - key, value - Sets the value for a key whose type is path. 109 | // "putReference", // - key, value - Sets the value for a key whose type is an object reference. 110 | "putString", // - key, value - Sets the value for a key whose type is string. 111 | "putUnitDouble", // - key, unitID, value - Sets the value for a key whose type is a unit value formatted as a double. 112 | // "toStream", // - nothing - Gets the entire descriptor as a stream of bytes, for writing to disk. 113 | 114 | ], 115 | ignoreKeyList: [ 116 | "DocI", "documentID", 117 | "kcanDispatchWhileModal", 118 | "level", 119 | "profile", 120 | ] 121 | }; 122 | 123 | var script = { 124 | name: "Clean ScriptingListenerJS.log", 125 | nameShort: "Clean SL", 126 | version: "1.4", 127 | developer: { 128 | name: File.decode("Tomas%20%C5%A0ink%C5%ABnas"), // Tomas Šinkūnas 129 | url: "http://www.rendertom.com" 130 | }, 131 | getInfo: function () { 132 | return this.nameShort + " v" + this.version + "\n" + "Photoshop utility tool to clean " + 133 | "up ScriptingListenerJS log file. Script performs multiple actions such as cleaning-up " + 134 | "variable names and hoisting them to the top, wraps code block into function, " + 135 | "converts charID to string ID, extracts parameter values and such.\n\n" + 136 | "Resulting code is clean and maintains better readability.\n\n" + 137 | "Developed by " + this.developer.name + "\n" + this.developer.url; 138 | } 139 | }; 140 | 141 | var logSeparator = "// =======================================================\n"; 142 | 143 | var Incrementor = (function () { 144 | var storedFunctions = [], 145 | storedVariables = [], 146 | storedParameters = [], 147 | reservedWords = ["abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", 148 | "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", 149 | "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", 150 | "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield", 151 | "Array", "Date", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "name", "Number", "Object", "prototype", 152 | "String", "toString", "undefined", "valueOf", "getClass", "java", "JavaArray", "javaClass", "JavaObject", "JavaPackage" 153 | ]; 154 | 155 | 156 | function contains(arr, value) { 157 | var i, il; 158 | for (i = 0, il = arr.length; i < il; i++) { 159 | if (arr[i] === value) { 160 | return arr[i]; 161 | } 162 | } 163 | return false; 164 | } 165 | 166 | function resetVariables() { 167 | storedVariables = reservedWords.slice(0); 168 | } 169 | 170 | function resetFunctions() { 171 | storedFunctions = reservedWords.slice(0); 172 | } 173 | 174 | function resetParameters() { 175 | storedParameters = reservedWords.slice(0); 176 | } 177 | 178 | function incrementVariables(string) { 179 | var variableName = string; 180 | variableName = validateName(variableName); 181 | return increment(variableName, storedVariables); 182 | } 183 | 184 | function incrementFunctions(string) { 185 | var functionName = string; 186 | functionName = validateName(functionName); 187 | return increment(functionName, storedFunctions); 188 | } 189 | 190 | function incrementParameters(string) { 191 | var parameterName = string; 192 | parameterName = validateName(parameterName); 193 | return increment(parameterName, storedParameters); 194 | } 195 | 196 | function increment(string, storedArray) { 197 | var coreName, newVariableVersion, versionNumber; 198 | coreName = string.replace(/\d+$/, ""); 199 | newVariableVersion = coreName; 200 | versionNumber = 2; 201 | while (contains(storedArray, newVariableVersion)) { 202 | newVariableVersion = coreName + versionNumber; 203 | versionNumber++; 204 | } 205 | 206 | storedArray.push(newVariableVersion); 207 | return newVariableVersion; 208 | } 209 | 210 | function validateName(string) { 211 | var functionName = string; 212 | functionName = functionName.replace(/[^\w_\$]/gi, ""); // Remove forbidden characters from function names 213 | if (functionName === "") 214 | functionName = "xxx"; 215 | 216 | return functionName; 217 | } 218 | 219 | return { 220 | resetVariables: resetVariables, 221 | resetFunctions: resetFunctions, 222 | resetParameters : resetParameters, 223 | incrementVariables: incrementVariables, 224 | incrementFunctions: incrementFunctions, 225 | incrementParameters: incrementParameters 226 | }; 227 | })(); 228 | 229 | var Settings = (function () { 230 | var settings, defaultSettings, startupSettings, 231 | pathToSettingsFile; 232 | 233 | pathToSettingsFile = File($.fileName).parent.fsName + "/" + script.nameShort + " Settings " + script.version + ".txt"; 234 | defaultSettings = { 235 | hoistVariables: { 236 | value: true 237 | }, 238 | consolidateVariables: { 239 | value: true 240 | }, 241 | renameConstructors: { 242 | value: true 243 | }, 244 | charIDToStringID: { 245 | value: true 246 | }, 247 | shortMethodNames: { 248 | value: true 249 | }, 250 | wrapToFunction: { 251 | value: true 252 | }, 253 | extractParameters: { 254 | value: true 255 | }, 256 | closeBeforeEval: { 257 | value: true 258 | }, 259 | saveOnQuit: { 260 | value: true 261 | }, 262 | etInputText: { 263 | text: "" 264 | }, 265 | etOutputText: { 266 | text: "" 267 | } 268 | }; 269 | 270 | function copyObjectValues(sourceObject, targetObject) { 271 | for (var propertyName in sourceObject) { 272 | if (!sourceObject.hasOwnProperty(propertyName) || 273 | !targetObject.hasOwnProperty(propertyName)) { 274 | continue; 275 | } 276 | 277 | for (var deepPropertyName in sourceObject[propertyName]) { 278 | if (!sourceObject[propertyName].hasOwnProperty(deepPropertyName) || 279 | !targetObject[propertyName].hasOwnProperty(deepPropertyName)) { 280 | continue; 281 | } 282 | targetObject[propertyName][deepPropertyName] = sourceObject[propertyName][deepPropertyName]; 283 | } 284 | } 285 | } 286 | 287 | function getSettingsFromFile() { 288 | var settingsFile, fileContent, settingsJson; 289 | 290 | settingsFile = new File(pathToSettingsFile); 291 | if (!settingsFile.exists) { 292 | return null; 293 | } 294 | 295 | fileContent = readFileContent(settingsFile); 296 | 297 | try { 298 | settingsJson = JSON.parse(fileContent); 299 | } catch (e) { 300 | alert("Unable to parse settings file. Will use default values instead"); 301 | } 302 | 303 | return settingsJson; 304 | } 305 | 306 | function save(data) { 307 | var settingsFile, settingsAsString; 308 | try { 309 | settingsFile = new File(pathToSettingsFile); 310 | settingsAsString = JSON.stringify(data, false, 4); 311 | 312 | writeFile(settingsFile, settingsAsString); 313 | 314 | } catch (e) { 315 | alert(e.toString() + "\nLine: " + e.line.toString()); 316 | } 317 | } 318 | 319 | function saveSettings() { 320 | save(settings); 321 | } 322 | 323 | function saveStartupSettings() { 324 | startupSettings.saveOnQuit.value = false; 325 | save(startupSettings); 326 | } 327 | 328 | function init() { 329 | settings = getSettingsFromFile(); 330 | if (!settings) { 331 | settings = defaultSettings; 332 | } 333 | 334 | startupSettings = JSON.parse(JSON.stringify(settings)); 335 | return settings; 336 | } 337 | 338 | return { 339 | saveSettings: saveSettings, 340 | saveStartupSettings: saveStartupSettings, 341 | init: init, 342 | copyObjectValues: copyObjectValues, 343 | }; 344 | })(); 345 | 346 | var settings = Settings.init(); 347 | 348 | buidUI(); 349 | 350 | /* MAIN */ 351 | 352 | function preprocess(dirtyCode) { 353 | var cleanCode, cleanCodeBlock, cleanCodeArray = [], 354 | dirtyCodeBlock, dirtyCodeArray = [], 355 | i, il; 356 | 357 | Incrementor.resetFunctions(); 358 | 359 | dirtyCodeArray = dirtyCode.split(logSeparator); 360 | for (i = 0, il = dirtyCodeArray.length; i < il; i++) { 361 | Incrementor.resetVariables(); 362 | Incrementor.resetParameters(); 363 | 364 | dirtyCodeBlock = trimSpaces(dirtyCodeArray[i]); 365 | if (dirtyCodeBlock === "") continue; 366 | cleanCodeBlock = main(dirtyCodeBlock); 367 | cleanCodeArray.push(cleanCodeBlock); 368 | } 369 | 370 | cleanCode = (il === 1) ? "" : logSeparator; 371 | cleanCode = cleanCode + cleanCodeArray.join("\n\n" + logSeparator); 372 | 373 | return cleanCode; 374 | } 375 | 376 | function main(inString) { 377 | try { 378 | var string; 379 | 380 | string = inString; 381 | 382 | string = splitToNewLines(string); 383 | string = fixIndentation(string); 384 | string = fixTripleQuotes(string); 385 | string = fixConflictingVariableDeclarations(string); 386 | 387 | if (settings.hoistVariables.value) string = hoistVariables(string); 388 | if (settings.consolidateVariables.value) string = consolidateVariables(string); 389 | if (settings.renameConstructors.value) string = renameConstructors(string); 390 | if (settings.charIDToStringID.value) string = convert_CharID_to_StringID(string); 391 | if (settings.shortMethodNames.value) string = shortMethodNames(string); 392 | if (settings.wrapToFunction.value) string = wrapToFunction(string, settings.extractParameters.value); 393 | return string; 394 | 395 | } catch (e) { 396 | alert(e.toString() + "\nLine: " + e.line.toString()); 397 | } 398 | } 399 | 400 | /********************************************************************************/ 401 | 402 | 403 | 404 | /* WORKFLOW */ 405 | 406 | function hoistVariables(inString) { 407 | var outString, variableDeclarationLines = [], 408 | i, il; 409 | 410 | outString = inString; 411 | variableDeclarationLines = getVariableDeclarationLines(outString); 412 | 413 | if (variableDeclarationLines) { 414 | for (i = 0, il = variableDeclarationLines.length; i < il; i++) { 415 | outString = outString.replace(variableDeclarationLines[i] + "\n", ""); // remove from original position 416 | } 417 | 418 | // We have to separate "removing" and "adding" lines, 419 | // because if it adds variableDeclaration line, it might get removed 420 | 421 | variableDeclarationLines = removeDuplicatesFromArray(variableDeclarationLines); 422 | variableDeclarationLines.sort(function (a, b) { 423 | a = a.toUpperCase(); 424 | b = b.toUpperCase(); 425 | if (a > b) return 1; 426 | if (a < b) return -1; 427 | return 0; 428 | }); 429 | outString = variableDeclarationLines.join("\n") + "\n\n" + outString; 430 | } 431 | 432 | return trimSpaces(outString); 433 | } 434 | 435 | function consolidateVariables(inString) { 436 | var outString, 437 | variableName, 438 | variableValue, 439 | variableDeclarationLine, 440 | variableDeclarationLines, 441 | regexPattern, 442 | regexExpression, 443 | variablesInCodeBlock, 444 | lastSign, 445 | i, il, j, jl; 446 | 447 | outString = inString; 448 | variableDeclarationLines = getVariableDeclarationLines(outString); 449 | 450 | if (variableDeclarationLines) { 451 | for (i = 0, il = variableDeclarationLines.length; i < il; i++) { 452 | variableDeclarationLine = variableDeclarationLines[i]; 453 | 454 | // ignore lines with "new ActionDescriptor()", "new ActionList()" and such, 455 | // because we don't want to consolidate them. 456 | if (variableDeclarationLine.match("\\s*new.*\\(\\)")) continue; 457 | 458 | variableName = getVariableName(variableDeclarationLine); 459 | variableValue = getVariableValue(variableDeclarationLine); 460 | 461 | // Adds "," or ")" to variable name, so I could capture variables 462 | // used in code (and not in variable declaration block); 463 | regexPattern = variableName + "\\s*[,|\\)]"; 464 | 465 | variablesInCodeBlock = outString.match(new RegExp(regexPattern, "g")); 466 | if (variablesInCodeBlock) { 467 | for (j = 0, jl = variablesInCodeBlock.length; j < jl; j++) { 468 | // Determines what last character was : "," or ")"; 469 | lastSign = variablesInCodeBlock[j].slice(-1); 470 | outString = outString.replace(variablesInCodeBlock[j], variableValue + lastSign); 471 | } 472 | 473 | // Remove variable declaration lines. We have to use Global flag 474 | // because in some case there are duplicate declarations of same variable 475 | regexExpression = new RegExp(escapeRegexExpression(variableDeclarationLine + "\n"), "g"); 476 | outString = outString.replace(regexExpression, ""); 477 | } 478 | } 479 | } 480 | 481 | return outString; 482 | } 483 | 484 | function renameConstructors(inString) { 485 | var outString, 486 | constructorName, 487 | regexExpression, 488 | variableName, 489 | variableNameNew, 490 | variableValue, 491 | variableDeclarationLine, 492 | variableDeclarationLines = [], 493 | i, il; 494 | 495 | outString = inString; 496 | variableDeclarationLines = getVariableDeclarationLines(outString); 497 | 498 | if (variableDeclarationLines) { 499 | for (i = 0, il = variableDeclarationLines.length; i < il; i++) { 500 | variableDeclarationLine = variableDeclarationLines[i]; 501 | 502 | variableName = getVariableName(variableDeclarationLine); 503 | variableValue = getVariableValue(variableDeclarationLine); 504 | 505 | for (constructorName in predefined.constructorNames) { 506 | if (!predefined.constructorNames.hasOwnProperty(constructorName)) continue; 507 | if (variableValue.match(constructorName)) { 508 | variableNameNew = predefined.constructorNames[constructorName]; 509 | variableNameNew = Incrementor.incrementVariables(variableNameNew); 510 | 511 | regexExpression = new RegExp("\\b" + variableName + "\\b", "g"); // Matches word boundry 512 | outString = outString.replace(regexExpression, variableNameNew); 513 | } 514 | } 515 | } 516 | } 517 | 518 | return outString; 519 | } 520 | 521 | function convert_CharID_to_StringID(inString) { 522 | var outString, 523 | regexExpression, 524 | charIDfunctions, charIDfunction, newCharIDfunction, 525 | functionParts, functionStart, quote, 526 | charID, stringID, 527 | i, il; 528 | 529 | // From "Get Equivalent ID Code.js" v1.7 by Michel MARIANI 530 | // http://www.tonton-pixel.com/scripts/utility-scripts/get-equivalent-id-code/index.html 531 | var conflictingStringIDs = { 532 | "Algn": ["align", "alignment"], 533 | "AntA": ["antiAlias", "antiAliasedPICTAcquire"], 534 | "BckL": ["backgroundLayer", "backgroundLevel"], 535 | "BlcG": ["blackGenerationType", "blackGenerationCurve"], 536 | "BlcL": ["blackLevel", "blackLimit"], 537 | "Blks": ["blacks", "blocks"], 538 | "BlrM": ["blurMethod", "blurMore"], 539 | "BrgC": ["brightnessEvent", "brightnessContrast"], 540 | "BrsD": ["brushDetail", "brushesDefine"], 541 | "Brsh": ["brush", "brushes"], 542 | "Clcl": ["calculation", "calculations"], 543 | "ClrP": ["colorPalette", "coloredPencil"], 544 | "Cnst": ["constant", "constrain"], 545 | "CntC": ["centerCropMarks", "conteCrayon"], 546 | "Cntr": ["center", "contrast"], 547 | "CrtD": ["createDroplet", "createDuplicate"], 548 | "CstP": ["customPalette", "customPhosphors"], 549 | "Cstm": ["custom", "customPattern"], 550 | "Drkn": ["darken", "darkness"], 551 | "Dstr": ["distort", "distortion", "distribute", "distribution"], 552 | "Dstt": ["desaturate", "destWhiteMax"], 553 | "FlIn": ["fileInfo", "fillInverse"], 554 | "Gd ": ["good", "guide"], 555 | "GnrP": ["generalPreferences", "generalPrefs", "preferencesClass"], 556 | "GrSt": ["grainStippled", "graySetup"], 557 | "Grdn": ["gradientClassEvent", "gridMinor"], 558 | "Grn ": ["grain", "green"], 559 | "Grns": ["graininess", "greens"], 560 | "HstP": ["historyPreferences", "historyPrefs"], 561 | "HstS": ["historyState", "historyStateSourceType"], 562 | "ImgP": ["imageCachePreferences", "imagePoint"], 563 | "In ": ["in", "stampIn"], 564 | "IntW": ["interfaceWhite", "intersectWith"], 565 | "Intr": ["interfaceIconFrameDimmed", "interlace", "interpolation", "intersect"], 566 | "JPEG": ["JPEG", "JPEGFormat"], 567 | "LghD": ["lightDirection", "lightDirectional"], 568 | "LghO": ["lightOmni", "lightenOnly"], 569 | "LghS": ["lightSource", "lightSpot"], 570 | "Lns ": ["lens", "lines"], 571 | "Mgnt": ["magenta", "magentas"], 572 | "MrgL": ["mergeLayers", "mergedLayers"], 573 | "Mxm ": ["maximum", "maximumQuality"], 574 | "NTSC": ["NTSC", "NTSCColors"], 575 | "NmbL": ["numberOfLayers", "numberOfLevels"], 576 | "PlgP": ["pluginPicker", "pluginPrefs"], 577 | "Pncl": ["pencilEraser", "pencilWidth"], 578 | "Pnt ": ["paint", "point"], 579 | "Prsp": ["perspective", "perspectiveIndex"], 580 | "PrvM": ["previewMacThumbnail", "previewMagenta"], 581 | "Pstr": ["posterization", "posterize"], 582 | "RGBS": ["RGBSetup", "RGBSetupSource"], 583 | "Rds ": ["radius", "reds"], 584 | "ScrD": ["scratchDisks", "screenDot"], 585 | "ShdI": ["shadingIntensity", "shadowIntensity"], 586 | "ShpC": ["shapeCurveType", "shapingCurve"], 587 | "ShrE": ["sharpenEdges", "shearEd"], 588 | "Shrp": ["sharpen", "sharpness"], 589 | "SplC": ["splitChannels", "supplementalCategories"], 590 | "Spot": ["spot", "spotColor"], 591 | "SprS": ["separationSetup", "sprayedStrokes"], 592 | "StrL": ["strokeLength", "strokeLocation"], 593 | "Strt": ["saturation", "start"], 594 | "TEXT": ["char", "textType"], 595 | "TIFF": ["TIFF", "TIFFFormat"], 596 | "TglO": ["toggleOptionsPalette", "toggleOthers"], 597 | "TrnG": ["transparencyGamutPreferences", "transparencyGrid", "transparencyGridSize"], 598 | "TrnS": ["transferSpec", "transparencyShape", "transparencyStop"], 599 | "Trns": ["transparency", "transparent"], 600 | "TxtC": ["textClickPoint", "textureCoverage"], 601 | "TxtF": ["textureFile", "textureFill"], 602 | "UsrM": ["userMaskEnabled", "userMaskOptions"], 603 | "c@#^": ["inherits", "pInherits"], 604 | "comp": ["comp", "sInt64"], 605 | "doub": ["floatType", "IEEE64BitFloatingPoint", "longFloat"], 606 | "long": ["integer", "longInteger", "sInt32"], 607 | "magn": ["magnitude", "uInt32"], 608 | "null": ["null", "target"], 609 | "shor": ["sInt16", "sMInt", "shortInteger"], 610 | "sing": ["IEEE32BitFloatingPoint", "sMFloat", "shortFloat"], 611 | }; 612 | 613 | outString = inString; 614 | 615 | // Collect all charIDToTypeID() functions in the string. 616 | // We will catch `charIDToTypeID ( "xxxx` function without last quote 617 | regexExpression = /charIDToTypeID\s*\(\s*?["'].{4}(?="|')/g; 618 | charIDfunctions = outString.match(regexExpression); 619 | 620 | if (charIDfunctions) { 621 | for (i = 0, il = charIDfunctions.length; i < il; i++) { 622 | charIDfunction = charIDfunctions[i]; // charIDToTypeID ( "xxxx 623 | quote = charIDfunction.match(/["']/)[0]; 624 | functionParts = charIDfunction.split(quote); 625 | functionStart = functionParts[0]; 626 | charID = functionParts[1]; 627 | 628 | // Skip if "charID" has conflicting StringID values. 629 | // CharID and StringID are not a one-to-one mapping: one CharID 630 | // can map to two different StringIDs. For instance: 631 | // charIDToTypeID( "Grn " ) === stringIDToTypeID( "grain" ) === stringIDToTypeID( "green" ) 632 | if (conflictingStringIDs.hasOwnProperty(charID)) 633 | continue; 634 | 635 | // Skip if CharID does not have corresponding StringID 636 | stringID = charIDtoStringID(charID); 637 | if (!stringID) 638 | continue; 639 | 640 | functionStart = functionStart.replace("charIDToTypeID", "stringIDToTypeID"); 641 | newCharIDfunction = functionStart + quote + stringID; 642 | outString = outString.replace(charIDfunction, newCharIDfunction); 643 | } 644 | } 645 | 646 | return outString; 647 | } 648 | 649 | function shortMethodNames(inString) { 650 | var outString, functionName, 651 | updateString = function (string, fullString, shortString) { 652 | var functionDeclarationString, regexExpression; 653 | 654 | regexExpression = new RegExp(fullString, "g"); 655 | if (regexExpression.test(string)) { 656 | functionDeclarationString = "var " + shortString + " = function (s) {\n\treturn app." + fullString + "(s);\n};"; 657 | string = string.replace(regexExpression, shortString); 658 | string = functionDeclarationString + "\n\n" + string; 659 | } 660 | 661 | return string; 662 | }; 663 | 664 | outString = inString; 665 | 666 | for (functionName in predefined.shortMethodNames) { 667 | if (!predefined.shortMethodNames.hasOwnProperty(functionName)) continue; 668 | outString = updateString(outString, functionName, predefined.shortMethodNames[functionName]); 669 | } 670 | 671 | return outString; 672 | } 673 | 674 | function getFields(array, property) { 675 | var value, tempValue, values = [], 676 | i, il; 677 | for (i = 0, il = array.length; i < il; i++) { 678 | value = array[i][property]; 679 | tempValue = parseFloat(value); 680 | if (!isNaN(tempValue)) { 681 | value = tempValue; 682 | } 683 | values.push(value); 684 | } 685 | return values; 686 | } 687 | 688 | function wrapToFunction(inString, toExtractParameters) { 689 | var outString, 690 | functionName, 691 | functionBlock, 692 | functionNameFromExecuteAction, 693 | executeActionLine, 694 | methodParameters, 695 | parameterNames = "", 696 | parameterValues = ""; 697 | 698 | 699 | outString = inString; 700 | 701 | if (toExtractParameters === true) { 702 | methodParameters = getMethodParameters(outString); 703 | outString = methodParameters.outString; 704 | parameterNames = getFields(methodParameters.parameters, "methodKey").join(", "); 705 | parameterValues = getFields(methodParameters.parameters, "methodValue").join(", "); 706 | } 707 | 708 | functionName = "xxx"; 709 | executeActionLine = outString.match(/executeAction.*/); 710 | if (executeActionLine) { 711 | functionNameFromExecuteAction = executeActionLine[0].split("\"")[1]; 712 | if (functionNameFromExecuteAction) { 713 | functionName = functionNameFromExecuteAction; 714 | } 715 | } 716 | 717 | functionName = Incrementor.incrementFunctions(functionName); 718 | functionBlock = functionName + "(" + parameterValues + ");\n" + "function " + functionName + "(" + parameterNames + ") {\n"; 719 | outString = functionBlock + fixIndentation(outString, "\t", false) + "\n}"; 720 | 721 | return outString; 722 | } 723 | 724 | function getMethodParameters(inString) { 725 | var outString, 726 | codeArray, codeLine, splitCodeLine, partMethod, partValue, regBetweenQuotes, 727 | constructorMethodRegex, 728 | parameters = [], 729 | methodKey, 730 | methodValue, 731 | i, il, j, jl; 732 | 733 | outString = inString; 734 | codeArray = outString.split("\n"); 735 | 736 | regBetweenQuotes = new RegExp("\[\"'](.*?)[\"']"); 737 | 738 | for (i = 0, il = codeArray.length; i < il; i++) { 739 | codeLine = codeArray[i]; 740 | for (j = 0, jl = predefined.descriptorMethods.length; j < jl; j++) { 741 | constructorMethodRegex = new RegExp("\\.\\b" + predefined.descriptorMethods[j] + "\\b\\W"); 742 | if (codeLine.match(constructorMethodRegex) && keyShouldBeIgnored(codeLine) === false) { 743 | splitCodeLine = codeLine.split(","); 744 | partMethod = splitCodeLine[0]; 745 | partValue = splitCodeLine[splitCodeLine.length - 1]; 746 | 747 | if (!regBetweenQuotes.test(partMethod)) continue; 748 | methodKey = partMethod.match(regBetweenQuotes)[1]; // Matches text between quotes 749 | if (methodKey === "") continue; 750 | methodKey = Incrementor.incrementParameters(methodKey); 751 | 752 | methodValue = partValue.substring(partValue.lastIndexOf(",") + 1, partValue.lastIndexOf(")")); 753 | methodValue = trimSpaces(methodValue); 754 | 755 | partValue = partValue.replace(methodValue, methodKey); 756 | splitCodeLine[splitCodeLine.length - 1] = partValue; 757 | 758 | codeArray[i] = splitCodeLine.join(","); 759 | 760 | parameters.push({ 761 | methodKey: methodKey, 762 | methodValue: methodValue 763 | }); 764 | break; 765 | } 766 | } 767 | } 768 | 769 | outString = codeArray.join("\n"); 770 | 771 | return { 772 | outString: outString, 773 | parameters: parameters 774 | }; 775 | } 776 | 777 | function keyShouldBeIgnored(codeLine) { 778 | for (var p = 0, pl = predefined.ignoreKeyList.length; p < pl; p++) { 779 | var regex2 = new RegExp("\.\\b" + predefined.ignoreKeyList[p] + "\\b\\W"); 780 | if (codeLine.match(regex2)) { 781 | return true; 782 | } 783 | } 784 | return false; 785 | } 786 | 787 | function evaluateScript(codeAsString) { 788 | try { 789 | eval(codeAsString); 790 | } catch (e) { 791 | alert("Unable to evalue script.\n" + e.toString() + "\nLine: " + e.line.toString()); 792 | } 793 | } 794 | 795 | function removeJunkCode(inString, showAlert) { 796 | try { 797 | var cleanCode, cleanCodeArray = [], 798 | dirtyCode, dirtyCodeArray = [], 799 | isJunkBlock, numberJunksRemoved = 0, 800 | alertMessage, i, il; 801 | 802 | if (typeof showAlert === "undefined") { 803 | showAlert = true; 804 | } 805 | 806 | dirtyCodeArray = trimSpaces(inString).split(logSeparator); 807 | 808 | for (i = 0, il = dirtyCodeArray.length; i < il; i++) { 809 | dirtyCode = dirtyCodeArray[i]; 810 | if (trimSpaces(dirtyCode) === "") continue; 811 | isJunkBlock = stringContainsArrayItems(dirtyCode, predefined.junkArray); 812 | if (isJunkBlock) { 813 | numberJunksRemoved++; 814 | } else { 815 | cleanCodeArray.push(dirtyCode); 816 | } 817 | } 818 | 819 | if (numberJunksRemoved === 0) { 820 | alertMessage = "All good, no junk found."; 821 | cleanCode = false; 822 | } else { 823 | alertMessage = "Removed " + numberJunksRemoved + " junk " + ((numberJunksRemoved > 1) ? "blocks" : "block") + ".\n"; 824 | alertMessage += "\"Junk block\" is considered a log block that contains any of these:\n\n" + predefined.junkArray.join("\n"); 825 | 826 | if (cleanCodeArray.length === 0) { 827 | cleanCode = " "; 828 | } else { 829 | cleanCode = (cleanCodeArray.length === 1) ? "" : logSeparator; 830 | cleanCode = logSeparator + cleanCodeArray.join(logSeparator); 831 | } 832 | } 833 | 834 | if (showAlert === true) 835 | alert(alertMessage); 836 | 837 | return cleanCode; 838 | 839 | } catch (e) { 840 | alert(e.toString() + "\nLine: " + e.line.toString()); 841 | } 842 | } 843 | 844 | /********************************************************************************/ 845 | 846 | 847 | 848 | /* USER INTERFACE */ 849 | 850 | function buidUI() { 851 | var uiControlls = {}; 852 | var win = new Window("dialog", script.name + " v" + script.version, undefined, { 853 | resizeable: true 854 | }); 855 | win.preferredSize = [1100, 500]; 856 | win.alignChildren = ["fill", "fill"]; 857 | win.orientation = "row"; 858 | 859 | 860 | uiControlls.etInputText = win.add("edittext", undefined, "", { 861 | multiline: true 862 | }); 863 | 864 | uiControlls.etInputText.onChange = uiControlls.etInputText.onChanging = function () { 865 | btnExecSource.enabled = btnCleanCode.enabled = btnRemoveJunkCode.enabled = this.text !== ""; 866 | }; 867 | 868 | uiControlls.etOutputText = win.add("edittext", undefined, "", { 869 | multiline: true 870 | }); 871 | 872 | uiControlls.etOutputText.onChange = uiControlls.etOutputText.onChanging = function () { 873 | btnSave.enabled = btnExecOutput.enabled = this.text !== ""; 874 | }; 875 | 876 | 877 | var grpRightColumn = win.add("group"); 878 | grpRightColumn.orientation = "column"; 879 | grpRightColumn.alignment = ["right", "fill"]; 880 | grpRightColumn.alignChildren = ["fill", "top"]; 881 | grpRightColumn.spacing = 2; 882 | 883 | var btnReadFullLog = grpRightColumn.add("button", undefined, "Load full log"); 884 | btnReadFullLog.helpTip = "Loads entire content of \nScriptingListenerJS.log file"; 885 | btnReadFullLog.onClick = function () { 886 | var fullLog = getFullLog(); 887 | if (fullLog) { 888 | var inputText = fullLog; 889 | if (predefined.removeJunkOnFullLogRead === true) { 890 | inputText = removeJunkCode(inputText, false); 891 | } 892 | 893 | uiControlls.etInputText.text = trimSpaces(inputText); 894 | uiControlls.etInputText.onChanging(); 895 | } 896 | }; 897 | 898 | var btnReadLastLog = grpRightColumn.add("button", undefined, "Load last log entry"); 899 | btnReadLastLog.helpTip = "Loads last code entry from \nScriptingListenerJS.log file"; 900 | btnReadLastLog.onClick = function () { 901 | var lastLogEntry = getLastLogEntry(); 902 | if (lastLogEntry) { 903 | uiControlls.etInputText.text = lastLogEntry; 904 | uiControlls.etInputText.onChanging(); 905 | } 906 | }; 907 | 908 | var btnRemoveJunkCode = grpRightColumn.add("button", undefined, "Remove junk code"); 909 | btnRemoveJunkCode.helpTip = "\"Junk block\" is considered a log block that contains any of these:\n\n" + predefined.junkArray.join("\n"); 910 | btnRemoveJunkCode.onClick = function () { 911 | var cleanCode = removeJunkCode(uiControlls.etInputText.text); 912 | if (cleanCode) { 913 | uiControlls.etInputText.text = trimSpaces(cleanCode); 914 | uiControlls.etInputText.onChanging(); 915 | } 916 | }; 917 | 918 | var btnExecSource = grpRightColumn.add("button", undefined, "Evaluate source"); 919 | btnExecSource.helpTip = "Evaluates source (Action Manager) code"; 920 | btnExecSource.onClick = function () { 921 | if (uiControlls.closeBeforeEval.value === true) win.close(); 922 | evaluateScript(uiControlls.etInputText.text); 923 | }; 924 | 925 | addSpace(grpRightColumn, 20); 926 | 927 | uiControlls.hoistVariables = grpRightColumn.add("checkbox", undefined, "Hoist variables to the top"); 928 | uiControlls.hoistVariables.helpTip = "Collects all variable declarations\nand moves them to the top of the block"; 929 | uiControlls.consolidateVariables = grpRightColumn.add("checkbox", undefined, "Consolidate variables"); 930 | uiControlls.consolidateVariables.helpTip = "Replaces each variable in the code\nwith its value"; 931 | uiControlls.renameConstructors = grpRightColumn.add("checkbox", undefined, "Rename constructors"); 932 | uiControlls.renameConstructors.helpTip = "Renames constructor variables:\n" + objectToString(predefined.constructorNames, "() as \"", "- new ", "\";"); 933 | uiControlls.charIDToStringID = grpRightColumn.add("checkbox", undefined, "Convert charID to stringID"); 934 | uiControlls.charIDToStringID.helpTip = "Converts charID value to stringID value.\nSkips converting particular case if charID has conflicting stringID values or does not have corresponding StringID at all"; 935 | uiControlls.shortMethodNames = grpRightColumn.add("checkbox", undefined, "Shorten method names"); 936 | uiControlls.shortMethodNames.helpTip = "Renames methods globally:\n" + objectToString(predefined.shortMethodNames, "() to ", "- ", "();"); 937 | uiControlls.wrapToFunction = grpRightColumn.add("checkbox", undefined, "Wrap to function block"); 938 | uiControlls.wrapToFunction.helpTip = "Wraps entire code block to function block"; 939 | uiControlls.wrapToFunction.onClick = function () { 940 | uiControlls.extractParameters.enabled = this.value; 941 | }; 942 | uiControlls.extractParameters = grpRightColumn.add("checkbox", undefined, "Extract parameters"); 943 | uiControlls.extractParameters.helpTip = "Extracts parameters and puts them to function call"; 944 | 945 | addSpace(grpRightColumn, 10); 946 | 947 | uiControlls.closeBeforeEval = grpRightColumn.add("checkbox", undefined, "Close before evaluating"); 948 | uiControlls.closeBeforeEval.helpTip = "Closes " + script.nameShort + " window before evaluating code"; 949 | uiControlls.saveOnQuit = grpRightColumn.add("checkbox", undefined, "Save UI data on quit"); 950 | uiControlls.saveOnQuit.helpTip = "Saves UI values when " + script.nameShort + " window closes"; 951 | 952 | addSpace(grpRightColumn, 20); 953 | 954 | var btnCleanCode = grpRightColumn.add("button", undefined, "Clean Code"); 955 | btnCleanCode.helpTip = "Starts cleaning-up source code"; 956 | btnCleanCode.onClick = function () { 957 | Settings.copyObjectValues(uiControlls, settings); 958 | 959 | var finalCode = preprocess(uiControlls.etInputText.text); 960 | if (finalCode) { 961 | uiControlls.etOutputText.text = finalCode; 962 | uiControlls.etOutputText.onChanging(); 963 | 964 | if (predefined.printToESTK === true) { 965 | cleanESTKconsole(); 966 | $.writeln(finalCode); 967 | } 968 | } 969 | }; 970 | 971 | var btnExecOutput = grpRightColumn.add("button", undefined, "Evaluate output"); 972 | btnExecOutput.helpTip = "Evaluates clean code"; 973 | btnExecOutput.onClick = function () { 974 | if (uiControlls.closeBeforeEval.value === true) win.close(); 975 | evaluateScript(uiControlls.etOutputText.text); 976 | }; 977 | 978 | var btnSave = grpRightColumn.add("button", undefined, "Save output code"); 979 | btnSave.helpTip = "Save clean code to file"; 980 | btnSave.onClick = function () { 981 | var pathToFile = File.saveDialog("Save output code."); 982 | if (pathToFile) { 983 | saveFile(pathToFile, "jsx", uiControlls.etOutputText.text); 984 | if (predefined.closeAfterSaving === true) { 985 | win.close(); 986 | } 987 | } 988 | }; 989 | 990 | var btnInfo = grpRightColumn.add("button", undefined, "About"); 991 | btnInfo.alignment = ["fill", "bottom"]; 992 | btnInfo.onClick = function () { 993 | alert(script.getInfo()); 994 | }; 995 | 996 | var btnClose = grpRightColumn.add("button", undefined, "Close"); 997 | btnClose.alignment = ["fill", "bottom"]; 998 | btnClose.onClick = function () { 999 | win.close(); 1000 | }; 1001 | 1002 | win.onResizing = win.onResize = function () { 1003 | this.layout.resize(); 1004 | }; 1005 | 1006 | win.onShow = function () { 1007 | 1008 | Settings.copyObjectValues(settings, uiControlls); 1009 | 1010 | btnCleanCode.size.height = btnCleanCode.size.height * 1.5; 1011 | uiControlls.etOutputText.onChanging(); 1012 | uiControlls.etInputText.onChanging(); 1013 | 1014 | uiControlls.extractParameters.enabled = uiControlls.wrapToFunction.value; 1015 | 1016 | win.layout.layout(true); 1017 | }; 1018 | 1019 | win.onClose = function () { 1020 | try { 1021 | if (uiControlls.saveOnQuit.value === true) { 1022 | Settings.copyObjectValues(uiControlls, settings); 1023 | Settings.saveSettings(); 1024 | } else { 1025 | Settings.saveStartupSettings(); 1026 | } 1027 | } catch (e) { 1028 | alert(e.toString() + "\nLine: " + e.line.toString()); 1029 | } 1030 | }; 1031 | 1032 | win.center(); 1033 | win.show(); 1034 | 1035 | function addSpace(groupContainer, spaceSize) { 1036 | var grpSpacer = groupContainer.add("group"); 1037 | grpSpacer.preferredSize.height = spaceSize; 1038 | } 1039 | } 1040 | 1041 | /********************************************************************************/ 1042 | 1043 | 1044 | 1045 | /* HELPER FUNCTIONS */ 1046 | 1047 | function getVariableName(string) { 1048 | var variableName; 1049 | 1050 | // Split line by "=" and capture first part. 1051 | // Remove "var" keyword and cleanup white spaces. 1052 | variableName = string.split("=")[0]; 1053 | variableName = variableName.replace(/^\s*var/, ""); 1054 | variableName = trimSpaces(variableName); 1055 | 1056 | return variableName; 1057 | } 1058 | 1059 | function getVariableValue(string) { 1060 | var variableValue; 1061 | 1062 | // Split line by "=" and capture second part. 1063 | // Remove ";" at the end and cleanup white spaces. 1064 | variableValue = string.split("=")[1]; 1065 | variableValue = variableValue.replace(/;$/, ""); 1066 | variableValue = trimSpaces(variableValue); 1067 | 1068 | return variableValue; 1069 | } 1070 | 1071 | function getLastLogEntry() { 1072 | var fullLog, lastLog, logArray = []; 1073 | 1074 | fullLog = getFullLog(); 1075 | logArray = fullLog.split(logSeparator); 1076 | lastLog = logArray.pop(); 1077 | lastLog = trimSpaces(lastLog); 1078 | 1079 | return lastLog; 1080 | } 1081 | 1082 | function escapeRegexExpression(text) { 1083 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 1084 | } 1085 | 1086 | function getVariableDeclarationLines(multilineString) { 1087 | var regexPattern, regexFlag, regexExpression; 1088 | 1089 | regexPattern = "^\\s*var.+;$"; 1090 | regexFlag = "gm"; // Global, Multiline 1091 | regexExpression = new RegExp(regexPattern, regexFlag); 1092 | 1093 | return multilineString.match(regexExpression); 1094 | } 1095 | 1096 | function getFullLog() { 1097 | var logFile; 1098 | 1099 | logFile = File(Folder.desktop.fsName + "/" + "ScriptingListenerJS.log"); 1100 | if (!logFile.exists) throw new Error("Unable to find Log file.\nFile does not exist at path " + logFile.fsName); 1101 | 1102 | return readFileContent(logFile); 1103 | } 1104 | 1105 | function stringContainsArrayItems(string, array) { 1106 | for (var i = 0, il = array.length; i < il; i++) { 1107 | if (string.indexOf(array[i]) > -1) 1108 | return true; 1109 | } 1110 | 1111 | return false; 1112 | } 1113 | 1114 | function getConflictingVariables(dataArray) { 1115 | /* 1116 | var example = [{ 1117 | "idPnt": [{ 1118 | "variableName": "idPnt", 1119 | "variableValue": "charIDToTypeID( \"Pnt \" )", 1120 | "variableLine": "var idPnt = charIDToTypeID( \"Pnt \" );" 1121 | }, { 1122 | "variableName": "idPnt", 1123 | "variableValue": "charIDToTypeID( \"#Pnt\" )", 1124 | "variableLine": "var idPnt = charIDToTypeID( \"#Pnt\" );" 1125 | }] 1126 | }, { 1127 | "idPntASD": [{ 1128 | "variableName": "idPntASD", 1129 | "variableValue": "charIDToTypeID( \"Pnt \" )", 1130 | "variableLine": "\nvar idPntASD= charIDToTypeID( \"Pnt \" );" 1131 | }, { 1132 | "variableName": "idPntASD", 1133 | "variableValue": "charIDToTypeID( \"#Pnt\" )", 1134 | "variableLine": "var idPntASD = charIDToTypeID( \"#Pnt\" );" 1135 | }] 1136 | }]; 1137 | */ 1138 | 1139 | var remembered = {}, 1140 | conflicts = {}, 1141 | variableName, 1142 | variableValue, 1143 | variableLine, 1144 | hasConflicts = false, 1145 | isConflictingLineKnown, 1146 | i, il, j, jl; 1147 | 1148 | for (i = 0, il = dataArray.length; i < il; i++) { 1149 | variableName = dataArray[i].variableName; 1150 | variableValue = dataArray[i].variableValue; 1151 | variableLine = dataArray[i].variableLine; 1152 | 1153 | if (!remembered.hasOwnProperty(variableName)) { 1154 | remembered[variableName] = { 1155 | variableValue: variableValue, 1156 | variableLine: variableLine 1157 | }; 1158 | } else { 1159 | if (remembered[variableName].variableLine !== variableLine) { 1160 | if (!conflicts.hasOwnProperty(variableName)) { 1161 | conflicts[variableName] = []; 1162 | conflicts[variableName].push({ 1163 | variableName: variableName, 1164 | variableValue: remembered[variableName].variableValue, 1165 | variableLine: remembered[variableName].variableLine 1166 | }); 1167 | } 1168 | 1169 | isConflictingLineKnown = false; 1170 | for (j = 0, jl = conflicts[variableName].length; j < jl; j++) { 1171 | if (conflicts[variableName][j].variableLine === variableLine) { 1172 | isConflictingLineKnown = true; 1173 | break; 1174 | } 1175 | } 1176 | 1177 | if (isConflictingLineKnown === false) { 1178 | conflicts[variableName].push({ 1179 | variableName: variableName, 1180 | variableValue: variableValue, 1181 | variableLine: variableLine 1182 | }); 1183 | } 1184 | 1185 | hasConflicts = true; 1186 | } 1187 | } 1188 | } 1189 | 1190 | if (hasConflicts === true) { 1191 | return conflicts; 1192 | } else { 1193 | return null; 1194 | } 1195 | } 1196 | 1197 | function removeDuplicatesFromArray(array) { 1198 | var seen = {}, 1199 | out = [], 1200 | i = 0, 1201 | j = 0, 1202 | item; 1203 | 1204 | for (i = 0, il = array.length; i < il; i++) { 1205 | item = array[i]; 1206 | if (seen[item] !== 1) { 1207 | seen[item] = 1; 1208 | out[j++] = item; 1209 | } 1210 | } 1211 | return out; 1212 | } 1213 | 1214 | function objectToString(object, separator, preString, postString) { 1215 | var array = [], 1216 | propertyName; 1217 | 1218 | separator = separator || " - "; 1219 | preString = preString || ""; 1220 | postString = postString || ""; 1221 | for (propertyName in object) { 1222 | if (!object.hasOwnProperty(propertyName)) continue; 1223 | array.push(preString + propertyName + separator + object[propertyName] + postString); 1224 | } 1225 | return array.join("\n"); 1226 | } 1227 | 1228 | function cleanESTKconsole() { 1229 | // https://forums.adobe.com/thread/1396184 1230 | try { 1231 | var bridge = new BridgeTalk(); 1232 | bridge.target = "estoolkit"; 1233 | bridge.body = function () { 1234 | app.clc(); 1235 | }.toSource() + "()"; 1236 | bridge.send(5); 1237 | } catch (e) {} 1238 | } 1239 | 1240 | /********************************************************************************/ 1241 | 1242 | 1243 | 1244 | /* STRING MANIPULATION */ 1245 | 1246 | function trimSpaces(string) { 1247 | return string.replace(/^\s+|\s+$/g, ""); 1248 | } 1249 | 1250 | function fixIndentation(inString, indentation, toTrim) { 1251 | var outString, stringLine, stringsArray, i, il; 1252 | 1253 | if (typeof indentation === "undefined") indentation = ""; 1254 | if (typeof toTrim === "undefined") toTrim = true; 1255 | 1256 | outString = inString; 1257 | stringsArray = outString.split("\n"); 1258 | 1259 | for (i = 0, il = stringsArray.length; i < il; i++) { 1260 | if (trimSpaces(stringsArray[i]) === "") continue; 1261 | 1262 | stringLine = (toTrim === true) ? trimSpaces(stringsArray[i]) : stringsArray[i]; 1263 | stringsArray[i] = indentation + stringLine; 1264 | } 1265 | 1266 | outString = stringsArray.join("\n"); 1267 | 1268 | return outString; 1269 | } 1270 | 1271 | function fixTripleQuotes(string) { 1272 | return string.replace(/"""/g, "\""); 1273 | } 1274 | 1275 | function splitToNewLines(inString, separator) { 1276 | var outString; 1277 | 1278 | separator = separator || ";"; 1279 | 1280 | outString = inString; 1281 | outString = outString.replace(new RegExp(separator, "g"), separator + "\n"); 1282 | outString = outString.replace(/\n\s*\n/g, "\n"); // remove double-returns 1283 | 1284 | return outString; 1285 | } 1286 | 1287 | function charIDtoStringID(charID) { 1288 | var stringID; 1289 | try { 1290 | stringID = typeIDToStringID(charIDToTypeID(charID)); 1291 | if (stringID === "") { 1292 | stringID = null; 1293 | } 1294 | } catch (e) {} 1295 | 1296 | return stringID; 1297 | } 1298 | 1299 | function fixConflictingVariableDeclarations(inString) { 1300 | var outString, 1301 | codeArray, 1302 | conflicts, 1303 | newVariableName, 1304 | nextCodeLine, 1305 | variable, 1306 | variablesArray = [], 1307 | variableDeclarationLine, 1308 | variableDeclarationLines, 1309 | variableName, 1310 | variableValue, 1311 | i, il, j, jl, p, pl, n; 1312 | 1313 | outString = inString; 1314 | variableDeclarationLines = getVariableDeclarationLines(outString); 1315 | 1316 | if (variableDeclarationLines) { 1317 | for (i = 0, il = variableDeclarationLines.length; i < il; i++) { 1318 | variableDeclarationLine = variableDeclarationLines[i]; 1319 | 1320 | variableName = getVariableName(variableDeclarationLine); 1321 | variableValue = getVariableValue(variableDeclarationLine); 1322 | 1323 | variablesArray.push({ 1324 | variableName: variableName, 1325 | variableValue: variableValue, 1326 | variableLine: variableDeclarationLine 1327 | }); 1328 | } 1329 | } 1330 | 1331 | conflicts = getConflictingVariables(variablesArray); 1332 | if (conflicts) { 1333 | codeArray = outString.split("\n"); 1334 | for (j = 0, jl = codeArray.length; j < jl; j++) { 1335 | for (variable in conflicts) { 1336 | if (!conflicts.hasOwnProperty(variable)) continue; 1337 | for (p = 0, pl = conflicts[variable].length; p < pl; p++) { 1338 | if (codeArray[j] === conflicts[variable][p].variableLine) { 1339 | variableName = conflicts[variable][p].variableName; 1340 | variableLine = conflicts[variable][p].variableLine; 1341 | newVariableName = conflicts[variable][p].newVariableName; 1342 | 1343 | if (!conflicts[variable][p].hasOwnProperty("newVariableLine")) { 1344 | newVariableName = Incrementor.incrementVariables(variableName + "_unique_"); 1345 | conflicts[variable][p].newVariableName = newVariableName; 1346 | conflicts[variable][p].newVariableLine = variableLine.replace(variableName, newVariableName); 1347 | } 1348 | 1349 | codeArray[j] = conflicts[variable][p].newVariableLine; 1350 | 1351 | for (n = 0; n < 20; n++) { 1352 | if (codeArray[j + n + 1]) { 1353 | nextCodeLine = codeArray[j + n + 1]; 1354 | if (!/\\s?var /.test(nextCodeLine) && nextCodeLine.match(variableName)) { 1355 | nextCodeLine = nextCodeLine.replace(variableName, newVariableName); 1356 | codeArray[j + n + 1] = nextCodeLine; 1357 | break; 1358 | } 1359 | } 1360 | } 1361 | } 1362 | } 1363 | } 1364 | } 1365 | outString = codeArray.join("\n"); 1366 | } 1367 | return outString; 1368 | } 1369 | 1370 | /********************************************************************************/ 1371 | 1372 | 1373 | 1374 | /* FILE */ 1375 | 1376 | function readFileContent(fileObj, encoding) { 1377 | var fileContent; 1378 | 1379 | fileObj.open("r"); 1380 | fileObj.encoding = encoding || "utf-8"; 1381 | fileContent = fileObj.read(); 1382 | fileObj.close(); 1383 | 1384 | return fileContent; 1385 | } 1386 | 1387 | function writeFile(fileObj, fileContent, encoding) { 1388 | encoding = encoding || "utf-8"; 1389 | fileObj = (fileObj instanceof File) ? fileObj : new File(fileObj); 1390 | 1391 | var parentFolder = fileObj.parent; 1392 | if (!parentFolder.exists && !parentFolder.create()) 1393 | throw new Error("Cannot create file in path " + fileObj.fsName); 1394 | 1395 | fileObj.encoding = encoding; 1396 | fileObj.open("w"); 1397 | fileObj.write(fileContent); 1398 | fileObj.close(); 1399 | 1400 | return fileObj; 1401 | } 1402 | 1403 | function saveFile(fileObject, fileExtension, fileContent) { 1404 | var filePath, newPath; 1405 | 1406 | filePath = fileObject.toString(); 1407 | if (filePath.lastIndexOf(".") < 0) { 1408 | newPath = filePath + "." + fileExtension; 1409 | } else { 1410 | newPath = filePath.substr(0, filePath.lastIndexOf(".")) + "." + fileExtension; 1411 | } 1412 | 1413 | writeFile(newPath, fileContent); 1414 | } 1415 | })(); -------------------------------------------------------------------------------- /Clean SL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendertom/Clean-SL/2371491abce3f9a9acec98ffc6bc26739306c8e5/Clean SL.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Clean SL](Clean%20SL.png) 2 | 3 | # Clean SL # 4 | 5 | Clean SL (Clean ScriptingListenerJS.log) is a utility tool for Adobe Photoshop to clean up ScriptingListenerJS.log file. 6 | 7 | Script performs multiple actions such as cleaning-up variable names and hoisting them to the top, wraps code block into function, converts charID to string ID and such. Resulting code is clean and maintains better readability. 8 | 9 | ### Features ### 10 | 11 | * Load entire ScriptingListenerJS.log content 12 | * Load only last entry in ScriptingListenerJS.log 13 | * Enter ScriptingListenerJS code manually 14 | 15 | ### Options ### 16 | 17 | * Hoist variable declaration to the top 18 | * Consolidate variables 19 | * Give descriptive variable names 20 | * Convert charID to stringID for better readability 21 | * Replace stringIDToTypeID() to s2t() function 22 | * Wrap to function block 23 | * Extract parameter values 24 | * Close Clean SL window before evaluating code 25 | * Save UI data on script quit. 26 | 27 | ### Example ### 28 | 29 | From this: 30 | 31 | ```javascript 32 | var idMk = charIDToTypeID( "Mk " ); 33 | var desc4 = new ActionDescriptor(); 34 | var idNw = charIDToTypeID( "Nw " ); 35 | var desc5 = new ActionDescriptor(); 36 | var idartboard = stringIDToTypeID( "artboard" ); 37 | desc5.putBoolean( idartboard, false ); 38 | var idMd = charIDToTypeID( "Md " ); 39 | var idRGBM = charIDToTypeID( "RGBM" ); 40 | desc5.putClass( idMd, idRGBM ); 41 | var idWdth = charIDToTypeID( "Wdth" ); 42 | var idRlt = charIDToTypeID( "#Rlt" ); 43 | desc5.putUnitDouble( idWdth, idRlt, 1000.000000 ); 44 | var idHght = charIDToTypeID( "Hght" ); 45 | var idRlt = charIDToTypeID( "#Rlt" ); 46 | desc5.putUnitDouble( idHght, idRlt, 1000.000000 ); 47 | var idRslt = charIDToTypeID( "Rslt" ); 48 | var idRsl = charIDToTypeID( "#Rsl" ); 49 | desc5.putUnitDouble( idRslt, idRsl, 72.000000 ); 50 | var idpixelScaleFactor = stringIDToTypeID( "pixelScaleFactor" ); 51 | desc5.putDouble( idpixelScaleFactor, 1.000000 ); 52 | var idFl = charIDToTypeID( "Fl " ); 53 | var idFl = charIDToTypeID( "Fl " ); 54 | var idWht = charIDToTypeID( "Wht " ); 55 | desc5.putEnumerated( idFl, idFl, idWht ); 56 | var idDpth = charIDToTypeID( "Dpth" ); 57 | desc5.putInteger( idDpth, 8 ); 58 | var idprofile = stringIDToTypeID( "profile" ); 59 | desc5.putString( idprofile, """sRGB IEC61966-2.1""" ); 60 | var idGdes = charIDToTypeID( "Gdes" ); 61 | var list1 = new ActionList(); 62 | desc5.putList( idGdes, list1 ); 63 | var idDcmn = charIDToTypeID( "Dcmn" ); 64 | desc4.putObject( idNw, idDcmn, desc5 ); 65 | var idDocI = charIDToTypeID( "DocI" ); 66 | desc4.putInteger( idDocI, 195 ); 67 | executeAction( idMk, desc4, DialogModes.NO ); 68 | ``` 69 | 70 | To this: 71 | 72 | ```javascript 73 | make(false, 1000, 1000, 72, 1, 8); 74 | function make(artboard, width, height, resolution, pixelScaleFactor, depth) { 75 | var s2t = function (s) { 76 | return app.stringIDToTypeID(s); 77 | }; 78 | 79 | var descriptor = new ActionDescriptor(); 80 | var descriptor2 = new ActionDescriptor(); 81 | var list = new ActionList(); 82 | 83 | descriptor2.putBoolean( s2t( "artboard" ), artboard ); 84 | descriptor2.putClass( s2t( "mode" ), s2t( "RGBColorMode" )); 85 | descriptor2.putUnitDouble( s2t( "width" ), s2t( "distanceUnit" ), width ); 86 | descriptor2.putUnitDouble( s2t( "height" ), s2t( "distanceUnit" ), height ); 87 | descriptor2.putUnitDouble( s2t( "resolution" ), s2t( "densityUnit" ), resolution ); 88 | descriptor2.putDouble( s2t( "pixelScaleFactor" ), pixelScaleFactor ); 89 | descriptor2.putEnumerated( s2t( "fill" ), s2t( "fill" ), s2t( "white" )); 90 | descriptor2.putInteger( s2t( "depth" ), depth ); 91 | descriptor2.putString( s2t( "profile" ), "sRGB IEC61966-2.1" ); 92 | descriptor2.putList( s2t( "guides" ), list ); 93 | descriptor.putObject( s2t( "new" ), s2t( "document" ), descriptor2 ); 94 | descriptor.putInteger( s2t( "documentID" ), 195 ); 95 | executeAction( s2t( "make" ), descriptor, DialogModes.NO ); 96 | } 97 | ``` 98 | 99 | ### Change log ### 100 | 101 | * v1.4 - 2018 03 29 : 102 | * Adds option to extract parameter values; 103 | * v1.3.1 - 2017 09 13 : 104 | * Skips converting CharID to StringID if CharID does not have corresponding StringID; 105 | * Validates function and variable names against reserved JS words. 106 | * v1.3 - 2017 09 12 : _Please remove old Clean SL Settings.txt file before using this update_ 107 | * Renames "Descriptive variable name" to "Rename constructors" 108 | * Renames "Shorten stringIDToTypeID" to "Shorten method names" 109 | * Exposes "predefined" object with variables for user to change in code 110 | * Removes duplicate variable declarations and sorts them alphabetically when hoisting 111 | * "Shorten method names" shortens both charIDToTypeID() and stringIDToTypeID() 112 | * Skips converting charIDtoStringID() if CharID has conflicting StringID values 113 | * Parses variable declarations to check if same variable has different values 114 | * v1.2 - 2017 09 10 : 115 | * Adds option to remove Action Managers junk code 116 | * Adds option to close Clean SL script before evaluating code 117 | * Adds option to save UI data on script quit 118 | * Fixes invalid function names 119 | * Replaces three quotes with one quote 120 | * Uses external JSON object to read/write UI data 121 | * Saves output code as JSX instead of TXT 122 | * Adds tooltips everywhere 123 | * Removes start-up demo code 124 | * Removes duplicate functions 125 | * v1.1 - 2017 09 08 - Increments function names when processing multiple logs 126 | * v1 - 2017 09 07 - Initial release 127 | 128 | ### Installation ### 129 | 130 | Clone or download this repository and place **Clean SJ.jsx** script to Photoshop’s Scripts folder: 131 | 132 | ```Adobe Photoshop CC 20XX -> Presets -> Scripts -> **Clean JS.jsx**``` 133 | 134 | Restart Photoshop to access **Clean JS** script from File -> Scripts 135 | 136 | --------- 137 | Developed by Tomas Šinkūnas 138 | 139 | www.rendertom.com 140 | 141 | --------- 142 | 143 | Released as open-source under the MIT license: 144 | 145 | The MIT License (MIT) 146 | 147 | Copyright (c) 2018 Tomas Šinkūnas www.renderTom.com 148 | 149 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 150 | 151 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/json2.js: -------------------------------------------------------------------------------- 1 | // json2.js 2 | // 2016-10-28 3 | // Public Domain. 4 | // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 5 | // See http://www.JSON.org/js.html 6 | // This code should be minified before deployment. 7 | // See http://javascript.crockford.com/jsmin.html 8 | 9 | // USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 10 | // NOT CONTROL. 11 | 12 | // This file creates a global JSON object containing two methods: stringify 13 | // and parse. This file provides the ES5 JSON capability to ES3 systems. 14 | // If a project might run on IE8 or earlier, then this file should be included. 15 | // This file does nothing on ES5 systems. 16 | 17 | // JSON.stringify(value, replacer, space) 18 | // value any JavaScript value, usually an object or array. 19 | // replacer an optional parameter that determines how object 20 | // values are stringified for objects. It can be a 21 | // function or an array of strings. 22 | // space an optional parameter that specifies the indentation 23 | // of nested structures. If it is omitted, the text will 24 | // be packed without extra whitespace. If it is a number, 25 | // it will specify the number of spaces to indent at each 26 | // level. If it is a string (such as "\t" or " "), 27 | // it contains the characters used to indent at each level. 28 | // This method produces a JSON text from a JavaScript value. 29 | // When an object value is found, if the object contains a toJSON 30 | // method, its toJSON method will be called and the result will be 31 | // stringified. A toJSON method does not serialize: it returns the 32 | // value represented by the name/value pair that should be serialized, 33 | // or undefined if nothing should be serialized. The toJSON method 34 | // will be passed the key associated with the value, and this will be 35 | // bound to the value. 36 | 37 | // For example, this would serialize Dates as ISO strings. 38 | 39 | // Date.prototype.toJSON = function (key) { 40 | // function f(n) { 41 | // // Format integers to have at least two digits. 42 | // return (n < 10) 43 | // ? "0" + n 44 | // : n; 45 | // } 46 | // return this.getUTCFullYear() + "-" + 47 | // f(this.getUTCMonth() + 1) + "-" + 48 | // f(this.getUTCDate()) + "T" + 49 | // f(this.getUTCHours()) + ":" + 50 | // f(this.getUTCMinutes()) + ":" + 51 | // f(this.getUTCSeconds()) + "Z"; 52 | // }; 53 | 54 | // You can provide an optional replacer method. It will be passed the 55 | // key and value of each member, with this bound to the containing 56 | // object. The value that is returned from your method will be 57 | // serialized. If your method returns undefined, then the member will 58 | // be excluded from the serialization. 59 | 60 | // If the replacer parameter is an array of strings, then it will be 61 | // used to select the members to be serialized. It filters the results 62 | // such that only members with keys listed in the replacer array are 63 | // stringified. 64 | 65 | // Values that do not have JSON representations, such as undefined or 66 | // functions, will not be serialized. Such values in objects will be 67 | // dropped; in arrays they will be replaced with null. You can use 68 | // a replacer function to replace those with JSON values. 69 | 70 | // JSON.stringify(undefined) returns undefined. 71 | 72 | // The optional space parameter produces a stringification of the 73 | // value that is filled with line breaks and indentation to make it 74 | // easier to read. 75 | 76 | // If the space parameter is a non-empty string, then that string will 77 | // be used for indentation. If the space parameter is a number, then 78 | // the indentation will be that many spaces. 79 | 80 | // Example: 81 | 82 | // text = JSON.stringify(["e", {pluribus: "unum"}]); 83 | // // text is '["e",{"pluribus":"unum"}]' 84 | 85 | // text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); 86 | // // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 87 | 88 | // text = JSON.stringify([new Date()], function (key, value) { 89 | // return this[key] instanceof Date 90 | // ? "Date(" + this[key] + ")" 91 | // : value; 92 | // }); 93 | // // text is '["Date(---current time---)"]' 94 | 95 | // JSON.parse(text, reviver) 96 | // This method parses a JSON text to produce an object or array. 97 | // It can throw a SyntaxError exception. 98 | 99 | // The optional reviver parameter is a function that can filter and 100 | // transform the results. It receives each of the keys and values, 101 | // and its return value is used instead of the original value. 102 | // If it returns what it received, then the structure is not modified. 103 | // If it returns undefined then the member is deleted. 104 | 105 | // Example: 106 | 107 | // // Parse the text. Values that look like ISO date strings will 108 | // // be converted to Date objects. 109 | 110 | // myData = JSON.parse(text, function (key, value) { 111 | // var a; 112 | // if (typeof value === "string") { 113 | // a = 114 | // /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 115 | // if (a) { 116 | // return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 117 | // +a[5], +a[6])); 118 | // } 119 | // } 120 | // return value; 121 | // }); 122 | 123 | // myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 124 | // var d; 125 | // if (typeof value === "string" && 126 | // value.slice(0, 5) === "Date(" && 127 | // value.slice(-1) === ")") { 128 | // d = new Date(value.slice(5, -1)); 129 | // if (d) { 130 | // return d; 131 | // } 132 | // } 133 | // return value; 134 | // }); 135 | 136 | // This is a reference implementation. You are free to copy, modify, or 137 | // redistribute. 138 | 139 | /*jslint 140 | eval, for, this 141 | */ 142 | 143 | /*property 144 | JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 145 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 146 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 147 | test, toJSON, toString, valueOf 148 | */ 149 | 150 | 151 | // Create a JSON object only if one does not already exist. We create the 152 | // methods in a closure to avoid creating global variables. 153 | 154 | if (typeof JSON !== "object") { 155 | JSON = {}; 156 | } 157 | 158 | (function () { 159 | "use strict"; 160 | 161 | var rx_one = /^[\],:{}\s]*$/; 162 | var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; 163 | var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; 164 | var rx_four = /(?:^|:|,)(?:\s*\[)+/g; 165 | var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 166 | var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? "0" + n : n; 171 | } 172 | 173 | function this_value() { 174 | return this.valueOf(); 175 | } 176 | 177 | if (typeof Date.prototype.toJSON !== "function") { 178 | 179 | Date.prototype.toJSON = function () { 180 | 181 | return isFinite(this.valueOf()) ? this.getUTCFullYear() + "-" + 182 | f(this.getUTCMonth() + 1) + "-" + 183 | f(this.getUTCDate()) + "T" + 184 | f(this.getUTCHours()) + ":" + 185 | f(this.getUTCMinutes()) + ":" + 186 | f(this.getUTCSeconds()) + "Z" : null; 187 | }; 188 | 189 | Boolean.prototype.toJSON = this_value; 190 | Number.prototype.toJSON = this_value; 191 | String.prototype.toJSON = this_value; 192 | } 193 | 194 | var gap; 195 | var indent; 196 | var meta; 197 | var rep; 198 | 199 | 200 | function quote(string) { 201 | 202 | // If the string contains no control characters, no quote characters, and no 203 | // backslash characters, then we can safely slap some quotes around it. 204 | // Otherwise we must also replace the offending characters with safe escape 205 | // sequences. 206 | 207 | rx_escapable.lastIndex = 0; 208 | return rx_escapable.test(string) ? "\"" + string.replace(rx_escapable, function (a) { 209 | var c = meta[a]; 210 | return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); 211 | }) + "\"" : "\"" + string + "\""; 212 | } 213 | 214 | 215 | function str(key, holder) { 216 | 217 | // Produce a string from holder[key]. 218 | 219 | var i; // The loop counter. 220 | var k; // The member key. 221 | var v; // The member value. 222 | var length; 223 | var mind = gap; 224 | var partial; 225 | var value = holder[key]; 226 | 227 | // If the value has a toJSON method, call it to obtain a replacement value. 228 | 229 | if (value && typeof value === "object" && 230 | typeof value.toJSON === "function") { 231 | value = value.toJSON(key); 232 | } 233 | 234 | // If we were called with a replacer function, then call the replacer to 235 | // obtain a replacement value. 236 | 237 | if (typeof rep === "function") { 238 | value = rep.call(holder, key, value); 239 | } 240 | 241 | // What happens next depends on the value's type. 242 | 243 | switch (typeof value) { 244 | case "string": 245 | return quote(value); 246 | 247 | case "number": 248 | 249 | // JSON numbers must be finite. Encode non-finite numbers as null. 250 | 251 | return isFinite(value) ? String(value) : "null"; 252 | 253 | case "boolean": 254 | case "null": 255 | 256 | // If the value is a boolean or null, convert it to a string. Note: 257 | // typeof null does not produce "null". The case is included here in 258 | // the remote chance that this gets fixed someday. 259 | 260 | return String(value); 261 | 262 | // If the type is "object", we might be dealing with an object or an array or 263 | // null. 264 | 265 | case "object": 266 | 267 | // Due to a specification blunder in ECMAScript, typeof null is "object", 268 | // so watch out for that case. 269 | 270 | if (!value) { 271 | return "null"; 272 | } 273 | 274 | // Make an array to hold the partial results of stringifying this object value. 275 | 276 | gap += indent; 277 | partial = []; 278 | 279 | // Is the value an array? 280 | 281 | if (Object.prototype.toString.apply(value) === "[object Array]") { 282 | 283 | // The value is an array. Stringify every element. Use null as a placeholder 284 | // for non-JSON values. 285 | 286 | length = value.length; 287 | for (i = 0; i < length; i += 1) { 288 | partial[i] = str(i, value) || "null"; 289 | } 290 | 291 | // Join all of the elements together, separated with commas, and wrap them in 292 | // brackets. 293 | 294 | v = partial.length === 0 ? "[]" : gap ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" : "[" + partial.join(",") + "]"; 295 | gap = mind; 296 | return v; 297 | } 298 | 299 | // If the replacer is an array, use it to select the members to be stringified. 300 | 301 | if (rep && typeof rep === "object") { 302 | length = rep.length; 303 | for (i = 0; i < length; i += 1) { 304 | if (typeof rep[i] === "string") { 305 | k = rep[i]; 306 | v = str(k, value); 307 | if (v) { 308 | partial.push(quote(k) + ( 309 | gap ? ": " : ":" 310 | ) + v); 311 | } 312 | } 313 | } 314 | } else { 315 | 316 | // Otherwise, iterate through all of the keys in the object. 317 | 318 | for (k in value) { 319 | if (Object.prototype.hasOwnProperty.call(value, k)) { 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + ( 323 | gap ? ": " : ":" 324 | ) + v); 325 | } 326 | } 327 | } 328 | } 329 | 330 | // Join all of the member texts together, separated with commas, 331 | // and wrap them in braces. 332 | 333 | v = partial.length === 0 ? "{}" : gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" : "{" + partial.join(",") + "}"; 334 | gap = mind; 335 | return v; 336 | } 337 | } 338 | 339 | // If the JSON object does not yet have a stringify method, give it one. 340 | 341 | if (typeof JSON.stringify !== "function") { 342 | meta = { // table of character substitutions 343 | "\b": "\\b", 344 | "\t": "\\t", 345 | "\n": "\\n", 346 | "\f": "\\f", 347 | "\r": "\\r", 348 | "\"": "\\\"", 349 | "\\": "\\\\" 350 | }; 351 | JSON.stringify = function (value, replacer, space) { 352 | 353 | // The stringify method takes a value and an optional replacer, and an optional 354 | // space parameter, and returns a JSON text. The replacer can be a function 355 | // that can replace values, or an array of strings that will select the keys. 356 | // A default replacer method can be provided. Use of the space parameter can 357 | // produce text that is more easily readable. 358 | 359 | var i; 360 | gap = ""; 361 | indent = ""; 362 | 363 | // If the space parameter is a number, make an indent string containing that 364 | // many spaces. 365 | 366 | if (typeof space === "number") { 367 | for (i = 0; i < space; i += 1) { 368 | indent += " "; 369 | } 370 | 371 | // If the space parameter is a string, it will be used as the indent string. 372 | 373 | } else if (typeof space === "string") { 374 | indent = space; 375 | } 376 | 377 | // If there is a replacer, it must be a function or an array. 378 | // Otherwise, throw an error. 379 | 380 | rep = replacer; 381 | if (replacer && typeof replacer !== "function" && 382 | (typeof replacer !== "object" || 383 | typeof replacer.length !== "number")) { 384 | throw new Error("JSON.stringify"); 385 | } 386 | 387 | // Make a fake root object containing our value under the key of "". 388 | // Return the result of stringifying the value. 389 | 390 | return str("", { 391 | "": value 392 | }); 393 | }; 394 | } 395 | 396 | 397 | // If the JSON object does not yet have a parse method, give it one. 398 | 399 | if (typeof JSON.parse !== "function") { 400 | JSON.parse = function (text, reviver) { 401 | 402 | // The parse method takes a text and an optional reviver function, and returns 403 | // a JavaScript value if the text is a valid JSON text. 404 | 405 | var j; 406 | 407 | function walk(holder, key) { 408 | 409 | // The walk method is used to recursively walk the resulting structure so 410 | // that modifications can be made. 411 | 412 | var k; 413 | var v; 414 | var value = holder[key]; 415 | if (value && typeof value === "object") { 416 | for (k in value) { 417 | if (Object.prototype.hasOwnProperty.call(value, k)) { 418 | v = walk(value, k); 419 | if (v !== undefined) { 420 | value[k] = v; 421 | } else { 422 | delete value[k]; 423 | } 424 | } 425 | } 426 | } 427 | return reviver.call(holder, key, value); 428 | } 429 | 430 | 431 | // Parsing happens in four stages. In the first stage, we replace certain 432 | // Unicode characters with escape sequences. JavaScript handles many characters 433 | // incorrectly, either silently deleting them, or treating them as line endings. 434 | 435 | text = String(text); 436 | rx_dangerous.lastIndex = 0; 437 | if (rx_dangerous.test(text)) { 438 | text = text.replace(rx_dangerous, function (a) { 439 | return "\\u" + 440 | ("0000" + a.charCodeAt(0).toString(16)).slice(-4); 441 | }); 442 | } 443 | 444 | // In the second stage, we run the text against regular expressions that look 445 | // for non-JSON patterns. We are especially concerned with "()" and "new" 446 | // because they can cause invocation, and "=" because it can cause mutation. 447 | // But just to be safe, we want to reject all unexpected forms. 448 | 449 | // We split the second stage into 4 regexp operations in order to work around 450 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 451 | // replace the JSON backslash pairs with "@" (a non-JSON character). Second, we 452 | // replace all simple value tokens with "]" characters. Third, we delete all 453 | // open brackets that follow a colon or comma or that begin the text. Finally, 454 | // we look to see that the remaining characters are only whitespace or "]" or 455 | // "," or ":" or "{" or "}". If that is so, then the text is safe for eval. 456 | 457 | if ( 458 | rx_one.test( 459 | text 460 | .replace(rx_two, "@") 461 | .replace(rx_three, "]") 462 | .replace(rx_four, "") 463 | ) 464 | ) { 465 | 466 | // In the third stage we use the eval function to compile the text into a 467 | // JavaScript structure. The "{" operator is subject to a syntactic ambiguity 468 | // in JavaScript: it can begin a block or an object literal. We wrap the text 469 | // in parens to eliminate the ambiguity. 470 | 471 | j = eval("(" + text + ")"); 472 | 473 | // In the optional fourth stage, we recursively walk the new structure, passing 474 | // each name/value pair to a reviver function for possible transformation. 475 | 476 | return (typeof reviver === "function") ? walk({ 477 | "": j 478 | }, "") : j; 479 | } 480 | 481 | // If the text is not JSON parseable, then a SyntaxError is thrown. 482 | 483 | throw new SyntaxError("JSON.parse"); 484 | }; 485 | } 486 | }()); --------------------------------------------------------------------------------