1 //------------------------------------------------------------------------------
  2 // File: jamEngine.jsxinc
  3 // Version: 4.5
  4 // Release Date: 2016-09-29
  5 // Copyright: © 2011-2016 Michel MARIANI <http://www.tonton-pixel.com/blog/>
  6 // Licence: GPL <http://www.gnu.org/licenses/gpl.html>
  7 //------------------------------------------------------------------------------
  8 // This program is free software: you can redistribute it and/or modify
  9 // it under the terms of the GNU General Public License as published by
 10 // the Free Software Foundation, either version 3 of the License, or
 11 // (at your option) any later version.
 12 // 
 13 // This program is distributed in the hope that it will be useful,
 14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 // GNU General Public License for more details.
 17 // 
 18 // You should have received a copy of the GNU General Public License
 19 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 20 //------------------------------------------------------------------------------
 21 // Version History:
 22 //  4.5:
 23 //  - Updated the disambiguating rules table: brush in brushPreset, 
 24 //    currentToolOptions and displayPrefs classes.
 25 //  4.4.4:
 26 //  - Added property jamEngine.displayDialogs (DialogModes.ERROR by default) 
 27 //    used as default value for missing parameter displayDialogs when calling 
 28 //    function jamEngine.jsonPlay ().
 29 //  4.4.1:
 30 //  - Updated the disambiguating rules table: interpolation in dBrush and 
 31 //    sampledBrush classes.
 32 //  4.4:
 33 //  - Normalized error messages.
 34 //  4.0:
 35 //  - Removed reference to 'this' for main global object.
 36 //  3.6.4:
 37 //  - Updated the disambiguating rules table: align in gradientLayer and 
 38 //    patternLayer classes.
 39 //  3.6.3:
 40 //  - Fixed reentrancy problem in jamEngine.simplifyObject () and
 41 //    jamEngine.simplifyList () by propagating hook function as parameter.
 42 //  3.6:
 43 //  - Updated the disambiguating rules table: start in fileNamingRules class.
 44 //  - Added jamEngine.simplifyObject () and jamEngine.simplifyList ().
 45 //  3.5:
 46 //  - Updated the disambiguating rules table for CS6: interpolation in 
 47 //    transform event.
 48 //  3.4:
 49 //  - Incremented version number to keep in sync with other modules.
 50 //  3.3:
 51 //  - Updated the disambiguating rules table.
 52 //  3.2:
 53 //  - Added support for large integers (from CS6).
 54 //  3.1:
 55 //  - Removed hexadecimal string conversion for raw data format.
 56 //  - Moved jamEngine.dataToHexaString () and jamEngine.hexaToDataString ()
 57 //    to jamUtils.dataToHexaString () and jamUtils.hexaToDataString ().
 58 //  3.0:
 59 //  - Applied the redefined JSON AM Reference format.
 60 //  2.0:
 61 //  - Renamed jamEngine.js to jamEngine.jsxinc.
 62 //  - Added alternative, more parse-friendly JSON compact syntax, using ordered
 63 //    pairs made of arrays instead of objects.
 64 //  - Modified the interface of jamEngine.getConflictingStringIdStrs (); now
 65 //    takes a CharID string as input and returns an array of the corresponding
 66 //    conflicting StringID strings if available, null otherwise.
 67 //  - Moved dataToHexaString () and hexaToDataString () to public members of
 68 //    jamEngine.
 69 //  - Changed names of members returned
 70 //    by jamEngine.eventIdAndActionDescriptorToJson (): "<event>", "<descriptor>"
 71 //    and jamEngine.classIdAndActionDescriptorToJson (): "<class>", "<descriptor>".
 72 //  - Changed default names of members handled by jamEngine.compactToExplicit ()
 73 //    and jamEngine.explicitToCompact (): "<type>", "<value>".
 74 //  - Simplified result of jamEngine.idToUniIdStrs () to a plain two-element array.
 75 //  - Merged specific and general context rules into one table with new format.
 76 //  1.0:
 77 //  - Initial release.
 78 //------------------------------------------------------------------------------
 79 
 80 /**
 81  * @fileOverview
 82  * @name jamEngine.jsxinc
 83  * @author Michel MARIANI
 84  */
 85 
 86 if (typeof jamEngine !== 'object')
 87 {
 88     /**
 89      * Global object (used to simulate a namespace in JavaScript) containing all the methods and properties of the
 90      * <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-action-manager/">JSON Action Manager</a> engine.
 91      * @author Michel MARIANI
 92      * @version 4.5
 93      * @namespace
 94      */
 95     var jamEngine = { };
 96     //
 97     (function ()
 98     {
 99         var that;
100         //
101         /**
102          * @description Global option: generate as "meaningful" as possible ID strings (instead of "canonic" ID strings).
103          * @type Boolean
104          * @default false
105          * @example
106          * jamEngine.<strong>meaningfulIds</strong> = false;
107          * var resultDescriptorObj = jamEngine.jsonGet
108          * (
109          *     [
110          *         { "'Prpr'": { "<property>": "'HstN'" } },
111          *         { "'capp'": { "<enumerated>": { "'Ordn'": "'Trgt'" } } }
112          *     ]
113          * );
114          * alert (jamJSON.stringify (resultDescriptorObj)); // -> { "'HstN'": { "<string>": "Adobe Photoshop CS" } }
115          * @example
116          * jamEngine.<strong>meaningfulIds</strong> = true;
117          * var resultDescriptorObj = jamEngine.jsonGet
118          * (
119          *     [
120          *         { "property": { "<property>": "hostName" } },
121          *         { "application": { "<enumerated>": { "ordinal": "targetEnum" } } }
122          *     ]
123          * );
124          * alert (jamJSON.stringify (resultDescriptorObj)); // -> { "hostName": { "<string>": "Adobe Photoshop CS" } }
125          */
126         jamEngine.meaningfulIds = false;
127         //
128         /**
129          * @description Global option: generate alternative, more parse-friendly JSON compact format (using ordered pairs made of arrays instead of objects).
130          * @type Boolean
131          * @default false
132          * @example
133          * jamEngine.meaningfulIds = true;
134          * jamEngine.<strong>parseFriendly</strong> = false;
135          * var resultDescriptorObj = jamEngine.jsonGet
136          * (
137          *     [
138          *         { "property": { "<property>": "rulerUnits" } },
139          *         { "application": { "<enumerated>": { "ordinal": "targetEnum" } } }
140          *     ]
141          * );
142          * alert (jamJSON.stringify (resultDescriptorObj));
143          * // -> { "rulerUnits": { "<enumerated>": { "rulerUnits": "rulerPixels" } } }
144          * var rulerUnits = resultDescriptorObj["rulerUnits"]["<enumerated>"]["rulerUnits"];
145          * alert (jamJSON.stringify (rulerUnits)); // -> "rulerPixels"
146          * @example
147          * jamEngine.meaningfulIds = true;
148          * jamEngine.<strong>parseFriendly</strong> = true;
149          * var resultDescriptorObj = jamEngine.jsonGet
150          * (
151          *     [
152          *         [ "property", [ "<property>", "rulerUnits" ] ],
153          *         [ "application", [ "<enumerated>", [ "ordinal", "targetEnum" ] ] ]
154          *     ]
155          * );
156          * alert (jamJSON.stringify (resultDescriptorObj));
157          * // -> { "rulerUnits": [ "<enumerated>", [ "rulerUnits", "rulerPixels" ] ] }
158          * var rulerUnits = resultDescriptorObj["rulerUnits"][1][1];
159          * alert (jamJSON.stringify (rulerUnits)); // -> "rulerPixels"
160          */
161         jamEngine.parseFriendly = false;
162         //
163         /**
164          * @description Global option: default value for displayDialogs when calling jamEngine.jsonPlay ().
165          * @type Object
166          * @default DialogModes.ERROR
167          * @example
168          * jamEngine.<strong>displayDialogs</strong> = DialogModes.ALL;
169          */
170         jamEngine.displayDialogs = DialogModes.ERROR;
171         //
172         // Table of conflicting StringID strings.
173         var conflictingStringIdStrs =
174         {
175             "'Algn'": [ "align", "alignment" ],
176             "'AntA'": [ "antiAlias", "antiAliasedPICTAcquire" ],
177             "'BckL'": [ "backgroundLayer", "backgroundLevel" ],
178             "'BlcG'": [ "blackGenerationType", "blackGenerationCurve" ],
179             "'BlcL'": [ "blackLevel", "blackLimit" ],
180             "'Blks'": [ "blacks", "blocks" ],
181             "'BlrM'": [ "blurMethod", "blurMore" ],
182             "'BrgC'": [ "brightnessEvent", "brightnessContrast" ],
183             "'BrsD'": [ "brushDetail", "brushesDefine" ],
184             "'Brsh'": [ "brush", "brushes" ],
185             "'Clcl'": [ "calculation", "calculations" ],
186             "'ClrP'": [ "colorPalette", "coloredPencil" ],
187             "'Cnst'": [ "constant", "constrain" ],
188             "'CntC'": [ "centerCropMarks", "conteCrayon" ],
189             "'Cntr'": [ "center", "contrast" ],
190             "'CrtD'": [ "createDroplet", "createDuplicate" ],
191             "'CstP'": [ "customPalette", "customPhosphors" ],
192             "'Cstm'": [ "custom", "customPattern" ],
193             "'Drkn'": [ "darken", "darkness" ],
194             "'Dstr'": [ "distort", "distortion", "distribute", "distribution" ],
195             "'Dstt'": [ "desaturate", "destWhiteMax" ],
196             "'FlIn'": [ "fileInfo", "fillInverse" ],
197             "'Gd  '": [ "good", "guide" ],
198             "'GnrP'": [ "generalPreferences", "generalPrefs", "preferencesClass" ],
199             "'GrSt'": [ "grainStippled", "graySetup" ],
200             "'Grdn'": [ "gradientClassEvent", "gridMinor" ],
201             "'Grn '": [ "grain", "green" ],
202             "'Grns'": [ "graininess", "greens" ],
203             "'HstP'": [ "historyPreferences", "historyPrefs" ],
204             "'HstS'": [ "historyState", "historyStateSourceType" ],
205             "'ImgP'": [ "imageCachePreferences", "imagePoint" ],
206             "'In  '": [ "in", "stampIn" ],
207             "'IntW'": [ "interfaceWhite", "intersectWith" ],
208             "'Intr'": [ "interfaceIconFrameDimmed", "interlace", "interpolation", "intersect" ],
209             "'JPEG'": [ "JPEG", "JPEGFormat" ],
210             "'LghD'": [ "lightDirection", "lightDirectional" ],
211             "'LghO'": [ "lightOmni", "lightenOnly" ],
212             "'LghS'": [ "lightSource", "lightSpot" ],
213             "'Lns '": [ "lens", "lines" ],
214             "'Mgnt'": [ "magenta", "magentas" ],
215             "'MrgL'": [ "mergeLayers", "mergedLayers" ],
216             "'Mxm '": [ "maximum", "maximumQuality" ],
217             "'NTSC'": [ "NTSC", "NTSCColors" ],
218             "'NmbL'": [ "numberOfLayers", "numberOfLevels" ],
219             "'PlgP'": [ "pluginPicker", "pluginPrefs" ],
220             "'Pncl'": [ "pencilEraser", "pencilWidth" ],
221             "'Pnt '": [ "paint", "point" ],
222             "'Prsp'": [ "perspective", "perspectiveIndex" ],
223             "'PrvM'": [ "previewMacThumbnail", "previewMagenta" ],
224             "'Pstr'": [ "posterization", "posterize" ],
225             "'RGBS'": [ "RGBSetup", "RGBSetupSource" ],
226             "'Rds '": [ "radius", "reds" ],
227             "'ScrD'": [ "scratchDisks", "screenDot" ],
228             "'ShdI'": [ "shadingIntensity", "shadowIntensity" ],
229             "'ShpC'": [ "shapeCurveType", "shapingCurve" ],
230             "'ShrE'": [ "sharpenEdges", "shearEd" ],
231             "'Shrp'": [ "sharpen", "sharpness" ],
232             "'SplC'": [ "splitChannels", "supplementalCategories" ],
233             "'Spot'": [ "spot", "spotColor" ],
234             "'SprS'": [ "separationSetup", "sprayedStrokes" ],
235             "'StrL'": [ "strokeLength", "strokeLocation" ],
236             "'Strt'": [ "saturation", "start" ],
237             "'TEXT'": [ "char", "textType" ],
238             "'TIFF'": [ "TIFF", "TIFFFormat" ],
239             "'TglO'": [ "toggleOptionsPalette", "toggleOthers" ],
240             "'TrnG'": [ "transparencyGamutPreferences", "transparencyGrid", "transparencyGridSize" ],
241             "'TrnS'": [ "transferSpec", "transparencyShape", "transparencyStop" ],
242             "'Trns'": [ "transparency", "transparent" ],
243             "'TxtC'": [ "textClickPoint", "textureCoverage" ],
244             "'TxtF'": [ "textureFile", "textureFill" ],
245             "'UsrM'": [ "userMaskEnabled", "userMaskOptions" ],
246             "'c@#^'": [ "inherits", "pInherits" ],
247             "'comp'": [ "comp", "sInt64" ],
248             "'doub'": [ "floatType", "IEEE64BitFloatingPoint", "longFloat" ],
249             "'long'": [ "integer", "longInteger", "sInt32" ],
250             "'magn'": [ "magnitude", "uInt32" ],
251             "'null'": [ "null", "target" ],
252             "'shor'": [ "sInt16", "sMInt", "shortInteger" ],
253             "'sing'": [ "IEEE32BitFloatingPoint", "sMFloat", "shortFloat" ]
254         };
255         //
256         /**
257          * @description Get an array of conflicting StringID strings corresponding to a CharID string.
258          * @param {String} charIdStr CharID string (<code>"'xxxx'"</code>)
259          * @returns {Array|Null} Array of conflicting StringID strings corresponding to a CharID string if available, null otherwise
260          * @example
261          * alert (jamJSON.stringify (jamEngine.<strong>getConflictingStringIdStrs</strong> ("'Rd  '")));
262          * // -> null
263          * alert (jamJSON.stringify (jamEngine.<strong>getConflictingStringIdStrs</strong> ("'Grn '")));
264          * // -> [ "grain", "green" ]
265          * alert (jamJSON.stringify (jamEngine.<strong>getConflictingStringIdStrs</strong> ("'Bl  '")));
266          * // -> null
267          */
268         jamEngine.getConflictingStringIdStrs = function (charIdStr)
269         {
270             return conflictingStringIdStrs[charIdStr] || null;
271         };
272         //
273         /**
274          * @description Convert a unified ID string into its numerical ID.
275          * @param {String} uniIdStr unified ID string (either <code>"'xxxx'"</code> or <code>"xxxxxxxx"</code>)
276          * @returns {Number} Numerical ID
277          * @see jamEngine.idToUniIdStrs
278          * @example
279          * var smallestHashValue = jamEngine.<strong>uniIdStrToId</strong> ("'    '");  // -> 0x20202020
280          */
281         jamEngine.uniIdStrToId = function (uniIdStr)
282         {
283             var id = 0;
284             if (typeof uniIdStr === 'string')
285             {
286                 if ((uniIdStr.length === (1 + 4 + 1)) && (uniIdStr.charAt (0) === "'") && (uniIdStr.charAt (5) === "'"))
287                 {
288                     // Predefined ID
289                     id = app.charIDToTypeID (uniIdStr.substring (1, 5));
290                 }
291                 else
292                 {
293                     // Predefined or runtime ID
294                     id = app.stringIDToTypeID (uniIdStr);
295                 }
296             }
297             return id;
298         };
299         //
300         var smallestHashValue = app.charIDToTypeID ("    "); // 0x20202020
301         //
302         /**
303          * @description Convert a numerical ID into a CharID string and a StringID string (or an array of conflicting StringID strings).
304          * @param {Number} id Numerical ID
305          * @returns {Object} Two-element array: CharID string (<code>"'xxxx'"</code>) (empty string if not available) and StringID string (<code>"xxxxxxxx"</code>) (empty string if not available, or array of StringID strings if they are conflicting)
306          * @see jamEngine.uniIdStrToId
307          * @example
308          * var uniIdStr = "green";
309          * alert (jamJSON.stringify (jamEngine.<strong>idToUniIdStrs</strong> (jamEngine.uniIdStrToId (uniIdStr))));
310          * // -> [ "'Grn '", [ "grain", "green" ] ]
311          */
312         jamEngine.idToUniIdStrs = function (id)
313         {
314             var charIdStr = "";
315             var stringIdStr = app.typeIDToStringID (id);
316             if (id >= smallestHashValue)
317             {
318                 // Predefined ID
319                 charIdStr = "'" + app.typeIDToCharID (id) + "'";
320                 if (stringIdStr !== "")
321                 {
322                     if (charIdStr in conflictingStringIdStrs)
323                     {
324                         stringIdStr = conflictingStringIdStrs[charIdStr];
325                     }
326                 }
327             }
328             return [ charIdStr, stringIdStr ];
329         };
330         //
331         /**
332          * @description Test if two unified ID strings are equivalent (map to the same numerical ID).
333          * @param {String} uniIdStr1 unified ID string (either <code>"'xxxx'"</code> or <code>"xxxxxxxx"</code>)
334          * @param {String} uniIdStr2 unified ID string (either <code>"'xxxx'"</code> or <code>"xxxxxxxx"</code>)
335          * @returns {Boolean} true if the two unified ID strings are equivalent, false otherwise 
336          * @example
337          * jamEngine.<strong>equivalentUniIdStrs</strong> ("green", "'Grn '");    // returns true
338          * jamEngine.<strong>equivalentUniIdStrs</strong> ("grain", "'Grn '");    // returns true
339          * jamEngine.<strong>equivalentUniIdStrs</strong> ("green", "grain");     // returns true
340          * jamEngine.<strong>equivalentUniIdStrs</strong> ("null", "'null'");     // returns true
341          * jamEngine.<strong>equivalentUniIdStrs</strong> ("target", "'Trgt'");   // returns false
342          * jamEngine.<strong>equivalentUniIdStrs</strong> ("target", "null");     // returns true
343          * jamEngine.<strong>equivalentUniIdStrs</strong> ("target", "'null'");   // returns true
344          * jamEngine.<strong>equivalentUniIdStrs</strong> ("axis", "'Axis'");     // returns true
345          * jamEngine.<strong>equivalentUniIdStrs</strong> ("center", "contrast"); // returns true
346          * jamEngine.<strong>equivalentUniIdStrs</strong> ("'Grn '", "'Grns'");   // returns false
347          * jamEngine.<strong>equivalentUniIdStrs</strong> ("green", "greens");    // returns false
348          */
349         jamEngine.equivalentUniIdStrs = function (uniIdStr1, uniIdStr2)
350         {
351             return this.uniIdStrToId (uniIdStr1) === this.uniIdStrToId (uniIdStr2);
352         };
353         //
354         function putInReference (ref, containers)
355         {
356             if (containers.constructor === Array)
357             {
358                 var count = containers.length;
359                 for (var i = 0; i < count; i++)
360                 {
361                     var container = that.parseCompact (containers[i]);
362                     var desiredClassId = that.uniIdStrToId (container[0]);
363                     var typedValue = that.parseCompact (container[1]);
364                     var form = typedValue[0];
365                     var value = typedValue[1];
366                     switch (form)
367                     {
368                         case "<class>":
369                             ref.putClass (desiredClassId);
370                             break;
371                         case "<enumerated>":
372                             var enumerated = that.parseCompact (value);
373                             ref.putEnumerated (desiredClassId, that.uniIdStrToId (enumerated[0]), that.uniIdStrToId (enumerated[1]));
374                             break;
375                         case "<identifier>":
376                             ref.putIdentifier (desiredClassId, value);
377                             break;
378                         case "<index>":
379                             ref.putIndex (desiredClassId, value);
380                             break;
381                         case "<name>":
382                             ref.putName (desiredClassId, value);
383                             break;
384                         case "<offset>":
385                             ref.putOffset (desiredClassId, value);
386                             break;
387                         case "<property>":
388                             ref.putProperty (desiredClassId, that.uniIdStrToId (value));
389                             break;
390                         default:
391                             throw new Error ("[jamEngine putInReference] Unknown reference form: " + form);
392                             break;
393                     }
394                  }
395             }
396             else
397             {
398                 throw new Error ("[jamEngine putInReference] JavaScript array expected");
399             }
400         }
401         //
402         function putInList (list, items)
403         {
404             if (items.constructor === Array)
405             {
406                 var count = items.length;
407                 for (var i = 0; i < count; i++)
408                 {
409                     var item = that.parseCompact (items[i]);
410                     var type = item[0];
411                     var value = item[1];
412                     switch (type)
413                     {
414                         case "<boolean>":
415                             list.putBoolean (value);
416                             break;
417                         case "<class>":
418                             list.putClass (that.uniIdStrToId (value));
419                             break;
420                         case "<data>":
421                             list.putData (value);    // Only from CS2
422                             break;
423                         case "<double>":
424                             list.putDouble (value);
425                             break;
426                         case "<enumerated>":
427                             var enumerated = that.parseCompact (value);
428                             list.putEnumerated (that.uniIdStrToId (enumerated[0]), that.uniIdStrToId (enumerated[1]));
429                             break;
430                         case "<integer>":
431                             list.putInteger (value);
432                             break;
433                         case "<largeInteger>":
434                             list.putLargeInteger (value);    // Only from CS6
435                             break;
436                         case "<list>":
437                             var actionList = new ActionList ();
438                             putInList (actionList, value);
439                             list.putList (actionList);
440                             break;
441                         case "<object>":
442                             var object = that.parseCompact (value);
443                             if (object[1])
444                             {
445                                 var actionDescriptor = new ActionDescriptor ();
446                                 putInDescriptor (actionDescriptor, object[1]);
447                                 list.putObject (that.uniIdStrToId (object[0]), actionDescriptor);
448                             }
449                             else
450                             {
451                                 // A Photoshop object is not supposed to have a null descriptor, use a simple class instead
452                                 list.putClass (that.uniIdStrToId (object[0]));
453                             }
454                             break;
455                         case "<path>":
456                             var fileRef = new File (value);
457                             list.putPath (fileRef);
458                             break;
459                         case "<reference>":
460                             var actionReference = new ActionReference ();
461                             putInReference (actionReference, value);
462                             list.putReference (actionReference);
463                             break;
464                         case "<string>":
465                             list.putString (value);
466                             break;
467                         case "<unitDouble>":
468                             var unitDouble = that.parseCompact (value);
469                             list.putUnitDouble (that.uniIdStrToId (unitDouble[0]), unitDouble[1]);
470                             break;
471                         default:
472                             throw new Error ("[jamEngine putInList] Unknown list type: " + type);
473                             break;
474                     }
475                 }
476             }
477             else
478             {
479                 throw new Error ("[jamEngine putInList] JavaScript array expected");
480             }
481         }
482         //
483         function putInDescriptor (desc, members)
484         {
485             if (members.constructor === Object)
486             {
487                 for (var key in members)
488                 {
489                     if (members.hasOwnProperty (key))
490                     {
491                         var keyID = that.uniIdStrToId (key);
492                         var member = that.parseCompact (members[key]);
493                         var type = member[0];
494                         var value = member[1];
495                         switch (type)
496                         {
497                             case "<boolean>":
498                                 desc.putBoolean (keyID, value);
499                                 break;
500                             case "<class>":
501                                 desc.putClass (keyID, that.uniIdStrToId (value));
502                                 break;
503                             case "<data>":
504                                 desc.putData (keyID, value); // Only from CS2
505                                 break;
506                             case "<double>":
507                                 desc.putDouble (keyID, value);
508                                 break;
509                             case "<enumerated>":
510                                 var enumerated = that.parseCompact (value);
511                                 desc.putEnumerated (keyID, that.uniIdStrToId (enumerated[0]), that.uniIdStrToId (enumerated[1]));
512                                 break;
513                             case "<integer>":
514                                 desc.putInteger (keyID, value);
515                                 break;
516                             case "<largeInteger>":
517                                 desc.putLargeInteger (keyID, value);    // Only from CS6
518                                 break;
519                             case "<list>":
520                                 var actionList = new ActionList ();
521                                 putInList (actionList, value);
522                                 desc.putList (keyID, actionList);
523                                 break;
524                             case "<object>":
525                                 var object = that.parseCompact (value);
526                                 if (object[1])
527                                 {
528                                     var actionDescriptor = new ActionDescriptor ();
529                                     putInDescriptor (actionDescriptor, object[1]);
530                                     desc.putObject (keyID, that.uniIdStrToId (object[0]), actionDescriptor);
531                                 }
532                                 else
533                                 {
534                                     // A Photoshop object is not supposed to have a null descriptor, use a simple class instead
535                                     desc.putClass (keyID, that.uniIdStrToId (object[0]));
536                                 }
537                                 break;
538                             case "<path>":
539                                 var fileRef = new File (value);
540                                 desc.putPath (keyID, fileRef);
541                                 break;
542                             case "<reference>":
543                                 var actionReference = new ActionReference ();
544                                 putInReference (actionReference, value);
545                                 desc.putReference (keyID, actionReference);
546                                 break;
547                             case "<string>":
548                                 desc.putString (keyID, value);
549                                 break;
550                             case "<unitDouble>":
551                                 var unitDouble = that.parseCompact (value);
552                                 desc.putUnitDouble (keyID, that.uniIdStrToId (unitDouble[0]), unitDouble[1]);
553                                 break;
554                             default:
555                                 throw new Error ("[jamEngine putInDescriptor] Unknown descriptor type: " + type);
556                                 break;
557                         }
558                     }
559                 }
560             }
561             else
562             {
563                 throw new Error ("[jamEngine putInDescriptor] JavaScript object expected");
564             }
565         }
566         //
567         // Disambiguating rules table, used to choose among conflicting StringID strings the most adequate meaningful one in context.
568         var contextRules =
569         {
570             "'Algn'":
571             {
572                 "<classKey>":
573                 {
574                     "bevelEmboss": "align",
575                     "frameFX": "align",
576                     "gradientFill": "align",
577                     "gradientLayer": "align",
578                     "patternFill": "align",
579                     "patternLayer": "align"
580                 },
581                 "<event>": "align",
582                 "<key>": "alignment"
583             },
584             "'AntA'":
585             {
586                 "<class>": "antiAliasedPICTAcquire",
587                 "<key>": "antiAlias"
588             },
589             "'BckL'":
590             {
591                 "<class>": "backgroundLayer",
592                 "<key>": "backgroundLevel"
593             },
594             "'BlcG'":
595             {
596                 "<enumType>": "blackGenerationType",
597                 "<key>": "blackGenerationCurve"
598             },
599             "'BlcL'":
600             {
601                 "<classKey>":
602                 {
603                     "'GEfc'": "blackLevel",
604                     "CMYKSetup": "blackLimit"
605                 },
606                 "<eventKey>":
607                 {
608                     "reticulation": "blackLevel"
609                 }
610             },
611             "'Blks'":
612             {
613                 "<typeValue>":
614                 {
615                     "colors": "blacks",
616                     "extrudeType": "blocks"
617                 }
618             },
619             "'BlrM'":
620             {
621                 "<enumType>": "blurMethod",
622                 "<event>": "blurMore",
623                 "<key>": "blurMethod"
624             },
625             "'BrgC'":
626             {
627                 "<class>": "brightnessContrast",
628                 "<event>": "brightnessContrast"
629             },
630             "'BrsD'":
631             {
632                 "<enumValue>": "brushesDefine",
633                 "<key>": "brushDetail"
634             },
635             "'Brsh'":
636             {
637                 "<class>": "brush",
638                 "<classKey>":
639                 {
640                     "brushPreset": "brush",
641                     "currentToolOptions": "brush",
642                     "displayPrefs": "brush"
643                 },
644                 "<key>": "brushes"
645             },
646             "'Clcl'":
647             {
648                 "<class>": "calculation",
649                 "<enumValue>": "calculations",
650                 "<key>": "calculation"
651             },
652             "'ClrP'":
653             {
654                 "<typeValue>":
655                 {
656                     "'GEft'": "coloredPencil"
657                 },
658                 "<enumType>": "colorPalette",
659                 "<event>": "coloredPencil"
660             },
661             "'Cnst'":
662             {
663                 "<classKey>":
664                 {
665                     "channelMatrix": "constant"
666                 },
667                 "<unknown>": "constrain"
668             },
669             "'CntC'":
670             {
671                 "<typeValue>":
672                 {
673                     "'GEft'": "conteCrayon"
674                 },
675                 "<event>": "conteCrayon",
676                 "<key>": "centerCropMarks"
677             },
678             "'Cntr'":
679             {
680                 "<classKey>":
681                 {
682                     "'GEfc'": "contrast",
683                     "brightnessContrast": "contrast",
684                     "document": "center",
685                     "polygon": "center",
686                     "quadrilateral": "center"
687                 },
688                 "<eventKey>":
689                 {
690                     "adaptCorrect": "contrast",
691                     "brightnessEvent": "contrast",
692                     "grain": "contrast",
693                     "halftoneScreen": "contrast",
694                     "sumie": "contrast",
695                     "tornEdges": "contrast",
696                     "waterPaper": "contrast"
697                 },
698                 "<enumValue>": "center"
699             },
700             "'CrtD'":
701             {
702                 "<enumValue>": "createDuplicate",
703                 "<event>": "createDroplet"
704             },
705             "'CstP'":
706             {
707                 "<class>": "customPhosphors",
708                 "<key>": "customPalette"
709             },
710             "'Cstm'":
711             {
712                 "<enumValue>": "customPattern",
713                 "<event>": "custom",
714                 "<key>": "custom"
715             },
716             "'Drkn'":
717             {
718                 "<enumValue>": "darken",
719                 "<key>": "darkness"
720             },
721             "'Dstr'":
722             {
723                 "<classKey>":
724                 {
725                     "'GEfc'": "distortion"
726                 },
727                 "<eventKey>":
728                 {
729                     "glass": "distortion",
730                     "addNoise": "distribution"
731                 },
732                 "<enumType>": "distribution",
733                 "<enumValue>": "distort",
734                 "<event>": "distribute"
735             },
736             "'Dstt'":
737             {
738                 "<enumValue>": "desaturate",
739                 "<event>": "desaturate",
740                 "<key>": "destWhiteMax"
741             },
742             "'FlIn'":
743             {
744                 "<typeValue>":
745                 {
746                     "fillColor": "fillInverse",
747                     "menuItemType": "fileInfo"
748                 },
749                 "<class>": "fileInfo",
750                 "<key>": "fileInfo"
751             },
752             "'Gd  '":
753             {
754                 "<class>": "guide",
755                 "<enumValue>": "good"
756             },
757             "'GnrP'":
758             {
759                 "<class>": "preferencesClass",
760                 "<enumValue>": "generalPreferences",
761                 "<key>": "generalPrefs"
762             },
763             "'GrSt'":
764             {
765                 "<class>": "graySetup",
766                 "<enumValue>": "grainStippled",
767                 "<key>": "graySetup"
768             },
769             "'Grdn'":
770             {
771                 "<class>": "gradientClassEvent",
772                 "<event>": "gradientClassEvent",
773                 "<key>": "gridMinor"
774             },
775             "'Grn '":
776             {
777                 "<typeValue>":
778                 {
779                     "'GEft'": "grain"
780                 },
781                 "<classKey>":
782                 {
783                     "'GEfc'": "grain",
784                     "RGBColor": "green",
785                     "blackAndWhite": "green",
786                     "channelMatrix": "green",
787                     "channelMixer": "green"
788                 },
789                 "<eventKey>":
790                 {
791                     "blackAndWhite": "green",
792                     "channelMixer": "green",
793                     "filmGrain": "grain"
794                 },
795                 "<enumValue>": "green",
796                 "<event>": "grain"
797             },
798             "'Grns'":
799             {
800                 "<enumValue>": "greens",
801                 "<key>": "graininess"
802             },
803             "'HstP'":
804             {
805                 "<enumValue>": "historyPreferences",
806                 "<key>": "historyPrefs"
807             },
808             "'HstS'":
809             {
810                 "<class>": "historyState",
811                 "<enumType>": "historyStateSourceType"
812             },
813             "'ImgP'":
814             {
815                 "<class>": "imagePoint",
816                 "<enumValue>": "imageCachePreferences"
817             },
818             "'In  '":
819             {
820                 "<enumValue>": "stampIn",
821                 "<key>": "in"
822             },
823             "'IntW'":
824             {
825                 "<event>": "intersectWith",
826                 "<key>": "interfaceWhite"
827             },
828             "'Intr'":
829             {
830                 "<typeValue>":
831                 {
832                     "shapeOperation": "intersect"
833                 },
834                 "<classKey>":
835                 {
836                     "GIFFormat": "interlace",
837                     "SaveForWeb": "interlace",
838                     "application": "interfaceIconFrameDimmed",
839                     "computedBrush": "interpolation",
840                     "dBrush": "interpolation",
841                     "gradientClassEvent": "interpolation",
842                     "photoshopEPSFormat": "interpolation",
843                     "sampledBrush": "interpolation"
844                 },
845                 "<eventKey>":
846                 {
847                     "convertMode": "interpolation",
848                     "imageSize": "interpolation",
849                     "transform": "interpolation"
850                 },
851                 "<event>": "intersect"
852             },
853             "'JPEG'":
854             {
855                 "<class>": "JPEGFormat",
856                 "<enumValue>": "JPEG"
857             },
858             "'LghD'":
859             {
860                 "<enumType>": "lightDirection",
861                 "<enumValue>": "lightDirectional",
862                 "<key>": "lightDirection"
863             },
864             "'LghO'":
865             {
866                 "<typeValue>":
867                 {
868                     "diffuseMode": "lightenOnly",
869                     "lightType": "lightOmni"
870                 }
871             },
872             "'LghS'":
873             {
874                 "<class>": "lightSource",
875                 "<enumValue>": "lightSpot",
876                 "<key>": "lightSource"
877             },
878             "'Lns '":
879             {
880                 "<enumType>": "lens",
881                 "<enumValue>": "lines",
882                 "<key>": "lens"
883             },
884             "'Mgnt'":
885             {
886                 "<typeValue>":
887                 {
888                     "channel": "magenta",
889                     "colors": "magentas",
890                     "guideGridColor": "magenta"
891                 },
892                 "<key>": "magenta"
893             },
894             "'MrgL'":
895             {
896                 "<enumValue>": "mergedLayers",
897                 "<event>": "mergeLayers"
898             },
899             "'Mxm '":
900             {
901                 "<enumValue>": "maximumQuality",
902                 "<event>": "maximum",
903                 "<key>": "maximum"
904             },
905             "'NTSC'":
906             {
907                 "<enumValue>": "NTSC",
908                 "<event>": "NTSCColors"
909             },
910             "'NmbL'":
911             {
912                 "<classKey>":
913                 {
914                     "'GEfc'": "numberOfLevels",
915                     "document": "numberOfLayers"
916                 },
917                 "<eventKey>":
918                 {
919                     "cutout": "numberOfLevels"
920                 }
921             },
922             "'PlgP'":
923             {
924                 "<class>": "pluginPrefs",
925                 "<enumValue>": "pluginPicker",
926                 "<key>": "pluginPrefs"
927             },
928             "'Pncl'":
929             {
930                 "<enumValue>": "pencilEraser",
931                 "<key>": "pencilWidth"
932             },
933             "'Pnt '":
934             {
935                 "<typeValue>":
936                 {
937                     "textType": "point"
938                 },
939                 "<class>": "point",
940                 "<event>": "paint"
941             },
942             "'Prsp'":
943             {
944                 "<enumValue>": "perspective",
945                 "<key>": "perspectiveIndex"
946             },
947             "'PrvM'":
948             {
949                 "<enumValue>": "previewMagenta",
950                 "<key>": "previewMacThumbnail"
951             },
952             "'Pstr'":
953             {
954                 "<class>": "posterize",
955                 "<event>": "posterize",
956                 "<key>": "posterization"
957             },
958             "'RGBS'":
959             {
960                 "<enumType>": "RGBSetupSource",
961                 "<key>": "RGBSetup"
962             },
963             "'Rds '":
964             {
965                 "<enumValue>": "reds",
966                 "<key>": "radius"
967             },
968             "'ScrD'":
969             {
970                 "<enumValue>": "screenDot",
971                 "<key>": "scratchDisks"
972             },
973             "'ShdI'":
974             {
975                 "<classKey>":
976                 {
977                     "'GEfc'": "shadowIntensity"
978                 },
979                 "<eventKey>":
980                 {
981                     "watercolor": "shadowIntensity"
982                 },
983                 "<unknown>": "shadingIntensity"
984             },
985             "'ShpC'":
986             {
987                 "<classKey>":
988                 {
989                     "application": "shapingCurve"
990                 },
991                 "<class>": "shapingCurve",
992                 "<key>": "shapeCurveType"
993             },
994             "'ShrE'":
995             {
996                 "<event>": "sharpenEdges",
997                 "<key>": "shearEd"
998             },
999             "'Shrp'":
1000             {
1001                 "<event>": "sharpen",
1002                 "<key>": "sharpness"
1003             },
1004             "'SplC'":
1005             {
1006                 "<event>": "splitChannels",
1007                 "<key>": "supplementalCategories"
1008             },
1009             "'Spot'":
1010             {
1011                 "<enumValue>": "spotColor",
1012                 "<key>": "spot"
1013             },
1014             "'SprS'":
1015             {
1016                 "<typeValue>":
1017                 {
1018                     "'GEft'": "sprayedStrokes"
1019                 },
1020                 "<enumValue>": "separationSetup",
1021                 "<event>": "sprayedStrokes"
1022             },
1023             "'StrL'":
1024             {
1025                 "<enumType>": "strokeLocation",
1026                 "<key>": "strokeLength"
1027             },
1028             "'Strt'":
1029             {
1030                 "<classKey>":
1031                 {
1032                     "currentToolOptions": "saturation",
1033                     "fileNamingRules": "start",
1034                     "HSBColorClass": "saturation",
1035                     "hueSatAdjustment": "saturation",
1036                     "hueSatAdjustmentV2": "saturation",
1037                     "lineClass": "start",
1038                     "range": "start",
1039                     "vibrance": "saturation"
1040                 },
1041                 "<eventKey>":
1042                 {
1043                     "replaceColor": "saturation",
1044                     "variations": "saturation",
1045                     "vibrance": "saturation"
1046                 },
1047                 "<enumValue>": "saturation"
1048             },
1049             "'TEXT'":
1050             {
1051                 "<enumType>": "textType",
1052                 "<key>": "textType"
1053             },
1054             "'TIFF'":
1055             {
1056                 "<class>": "TIFFFormat",
1057                 "<enumValue>": "TIFF"
1058             },
1059             "'TglO'":
1060             {
1061                 "<enumValue>": "toggleOptionsPalette",
1062                 "<key>": "toggleOthers"
1063             },
1064             "'TrnG'":
1065             {
1066                 "<classKey>":
1067                 {
1068                     "application": "transparencyGrid",
1069                     "transparencyPrefs": "transparencyGridSize"
1070                 },
1071                 "<enumType>": "transparencyGridSize",
1072                 "<enumValue>": "transparencyGamutPreferences"
1073             },
1074             "'TrnS'":
1075             {
1076                 "<classKey>":
1077                 {
1078                     "bevelEmboss": "transparencyShape",
1079                     "dropShadow": "transparencyShape",
1080                     "innerGlow": "transparencyShape",
1081                     "innerShadow": "transparencyShape",
1082                     "outerGlow": "transparencyShape"
1083                 },
1084                 "<class>": "transparencyStop",
1085                 "<unknown>": "transferSpec"
1086             },
1087             "'Trns'":
1088             {
1089                 "<enumValue>": "transparent",
1090                 "<key>": "transparency"
1091             },
1092             "'TxtC'":
1093             {
1094                 "<classKey>":
1095                 {
1096                     "'GEfc'": "textureCoverage",
1097                     "textLayer": "textClickPoint"
1098                 },
1099                 "<eventKey>":
1100                 {
1101                     "underpainting": "textureCoverage"
1102                 }
1103             },
1104             "'TxtF'":
1105             {
1106                 "<event>": "textureFill",
1107                 "<key>": "textureFile"
1108             },
1109             "'UsrM'":
1110             {
1111                 "<enumType>": "userMaskOptions",
1112                 "<key>": "userMaskEnabled"
1113             },
1114             "'null'":
1115             {
1116                 "<class>": "null",
1117                 "<enumValue>": "null",
1118                 "<event>": "null",
1119                 "<key>": "target"
1120             }
1121         };
1122         //
1123         function getFromId (context, parentContext)
1124         {
1125             var uniIdStr;
1126             var kind = context[0];
1127             var id = context[1];
1128             if (id < smallestHashValue)
1129             {
1130                 // Runtime ID
1131                 uniIdStr = app.typeIDToStringID (id);
1132             }
1133             else
1134             {
1135                 // Predefined ID
1136                 uniIdStr = "'" + app.typeIDToCharID (id) + "'";
1137                 if (that.meaningfulIds)
1138                 {
1139                     // Meaningful ID string preferred if available
1140                     if (uniIdStr in contextRules)
1141                     {
1142                         function resolveIdStr (candidates)
1143                         {
1144                             var idStr = "";
1145                             for (var parentString in candidates)
1146                             {
1147                                 if (candidates.hasOwnProperty (parentString))
1148                                 {
1149                                     if (parentContext[1] === that.uniIdStrToId (parentString))
1150                                     {
1151                                         idStr = candidates[parentString];
1152                                         break;
1153                                     }
1154                                 }
1155                             }
1156                             return idStr;
1157                         }
1158                         var resolvedIdStr = "";
1159                         var rule = contextRules[uniIdStr];
1160                         if (parentContext)
1161                         {
1162                             switch (kind)
1163                             {
1164                                 case "<key>":
1165                                     if ((parentContext[0] === "<class>") && ("<classKey>" in rule))
1166                                     {
1167                                         resolvedIdStr = resolveIdStr (rule["<classKey>"]);
1168                                     }
1169                                     else if ((parentContext[0] === "<event>") && ("<eventKey>" in rule))
1170                                     {
1171                                         resolvedIdStr = resolveIdStr (rule["<eventKey>"]);
1172                                     }
1173                                     break;
1174                                 case "<enumValue>":
1175                                     if ((parentContext[0] === "<enumType>") && ("<typeValue>" in rule))
1176                                     {
1177                                         resolvedIdStr = resolveIdStr (rule["<typeValue>"]);
1178                                     }
1179                                     break;
1180                             }
1181                         }
1182                         if (resolvedIdStr !== "")
1183                         {
1184                             uniIdStr = resolvedIdStr;
1185                         }
1186                         else if (kind in rule)
1187                         {
1188                              uniIdStr = rule[kind];
1189                         }
1190                     }
1191                     else
1192                     {
1193                         var stringIDStr = app.typeIDToStringID (id);
1194                         if (stringIDStr !== "")
1195                         {
1196                             uniIdStr = stringIDStr;
1197                         }
1198                     }
1199                 }
1200             }
1201             return uniIdStr;
1202         }
1203         //
1204         var incompatiblePlatformPath = "";
1205         //
1206         var getEventId = app.stringIDToTypeID ("get");
1207         var targetKeyId = app.stringIDToTypeID ("target");
1208         var propertyClassId = app.stringIDToTypeID ("property");
1209         //
1210         function getFromReference (ref)
1211         {
1212             var propertyId = 0;
1213             var arr = [ ];
1214             do
1215             {
1216                 // var desiredClassId = ref.getDesiredClass ();
1217                 try { var desiredClassId = ref.getDesiredClass (); } catch (e) { break; }
1218                 if (propertyId !== 0)   // Delayed property class
1219                 {
1220                     var propertyCompact = that.buildCompact ("<property>", getFromId ([ "<key>", propertyId ], [ "<class>", desiredClassId ]));
1221                     arr.push (that.buildCompact (getFromId ([ "<class>", propertyClassId ]), propertyCompact));
1222                     propertyId = 0;
1223                 }
1224                 var desiredCompact;
1225                 var aFormID = ref.getForm ();
1226                 switch (aFormID)
1227                 {
1228                     case ReferenceFormType.CLASSTYPE:
1229                         desiredCompact = that.buildCompact ("<class>", null);
1230                         break;
1231                     case ReferenceFormType.ENUMERATED:
1232                         var enumTypeContext = [ "<enumType>", ref.getEnumeratedType () ];
1233                         var enumValueContext = [ "<enumValue>", ref.getEnumeratedValue () ];
1234                         desiredCompact = that.buildCompact ("<enumerated>", that.buildCompact (getFromId (enumTypeContext), getFromId (enumValueContext, enumTypeContext)));
1235                         break;
1236                     case ReferenceFormType.IDENTIFIER:
1237                         desiredCompact = that.buildCompact ("<identifier>", ref.getIdentifier ());
1238                         break;
1239                     case ReferenceFormType.INDEX:
1240                         desiredCompact = that.buildCompact ("<index>", ref.getIndex ());
1241                         break;
1242                     case ReferenceFormType.NAME:
1243                         desiredCompact = that.buildCompact ("<name>", ref.getName ());
1244                         break;
1245                     case ReferenceFormType.OFFSET:
1246                         desiredCompact = that.buildCompact ("<offset>", ref.getOffset ());
1247                         break;
1248                     case ReferenceFormType.PROPERTY:
1249                         if (desiredClassId === propertyClassId)
1250                         {
1251                             propertyId = ref.getProperty ();
1252                         }
1253                         else
1254                         {
1255                             desiredCompact = that.buildCompact ("<property>", getFromId ([ "<key>", ref.getProperty () ], [ "<class>", desiredClassId ]));
1256                         }
1257                         break;
1258                     default:
1259                         throw new Error ("[jamEngine getFromReference] Unknown reference form type: " + aFormID);
1260                         break;
1261                 }
1262                 if (desiredClassId !== propertyClassId)
1263                 {
1264                     arr.push (that.buildCompact (getFromId ([ "<class>", desiredClassId ]), desiredCompact));
1265                 }
1266                 ref = ref.getContainer ();
1267             }
1268             while (ref);
1269             return arr;
1270         }
1271         //
1272         function getFromList (list)
1273         {
1274             var arr = [ ];
1275             var itemCount = list.count;
1276             for (var itemIndex = 0; itemIndex < itemCount; itemIndex++)
1277             {
1278                 var itemCompact;
1279                 var typeID;
1280                 // typeID = list.getType (itemIndex);
1281                 try { typeID = list.getType (itemIndex); } catch (e) { continue; }  // Data (Raw) type only from CS2 + Large integer type only from CS6
1282                 switch (typeID)
1283                 {
1284                     case DescValueType.BOOLEANTYPE:
1285                         itemCompact = that.buildCompact ("<boolean>", list.getBoolean (itemIndex));
1286                         break;
1287                     case DescValueType.CLASSTYPE:
1288                         itemCompact = that.buildCompact ("<class>", getFromId ([ "<class>", list.getClass (itemIndex) ]));
1289                         break;
1290                     /*
1291                     case DescValueType.RAWTYPE:
1292                         itemCompact = that.buildCompact ("<data>", list.getData (itemIndex)); // Only from CS2
1293                         break;
1294                     */
1295                     case DescValueType.DOUBLETYPE:
1296                         itemCompact = that.buildCompact ("<double>", list.getDouble (itemIndex));
1297                         break;
1298                     case DescValueType.ENUMERATEDTYPE:
1299                         var enumTypeContext = [ "<enumType>", list.getEnumerationType (itemIndex) ];
1300                         var enumValueContext = [ "<enumValue>", list.getEnumerationValue (itemIndex) ];
1301                         itemCompact = that.buildCompact ("<enumerated>", that.buildCompact (getFromId (enumTypeContext), getFromId (enumValueContext, enumTypeContext)));
1302                         break;
1303                     case DescValueType.INTEGERTYPE:
1304                         itemCompact = that.buildCompact ("<integer>", list.getInteger (itemIndex));
1305                         break;
1306                     /*
1307                     case DescValueType.LARGEINTEGERTYPE:
1308                         itemCompact = that.buildCompact ("<largeInteger>", list.getLargeInteger (itemIndex)); // Only from CS6
1309                         break;
1310                     */
1311                     case DescValueType.LISTTYPE:
1312                         itemCompact = that.buildCompact ("<list>", getFromList (list.getList (itemIndex)));
1313                         break;
1314                     case DescValueType.OBJECTTYPE:
1315                         var objectTypeContext = [ "<class>", list.getObjectType (itemIndex) ];
1316                         var objectValue = list.getObjectValue (itemIndex);
1317                         itemCompact = that.buildCompact ("<object>", that.buildCompact (getFromId (objectTypeContext), getFromDescriptor (objectValue, objectTypeContext)));
1318                         break;
1319                     case DescValueType.ALIASTYPE:
1320                         try
1321                         {
1322                             var fileRef = list.getPath (itemIndex);
1323                             itemCompact = that.buildCompact ("<path>", fileRef.fsName);
1324                         }
1325                         catch (e)
1326                         {
1327                             itemCompact = that.buildCompact ("<path>", incompatiblePlatformPath);
1328                         }
1329                         break;
1330                     case DescValueType.REFERENCETYPE:
1331                         itemCompact = that.buildCompact ("<reference>", getFromReference (list.getReference (itemIndex)));
1332                         break;
1333                     case DescValueType.STRINGTYPE:
1334                         itemCompact = that.buildCompact ("<string>", list.getString (itemIndex));
1335                         break;
1336                     case DescValueType.UNITDOUBLE:
1337                         var unitTypeContext = [ "<unit>", list.getUnitDoubleType (itemIndex) ];
1338                         var doubleValue = list.getUnitDoubleValue (itemIndex);
1339                         itemCompact = that.buildCompact ("<unitDouble>", that.buildCompact (getFromId (unitTypeContext), doubleValue));
1340                         break;
1341                     default:
1342                         var isRawType;
1343                         var isLargeIntegerType;
1344                         try { isRawType = (typeID === DescValueType.RAWTYPE); } catch (e) { }
1345                         try { isLargeIntegerType = (typeID === DescValueType.LARGEINTEGERTYPE); } catch (e) { }
1346                         if (isRawType)
1347                         {
1348                             itemCompact = that.buildCompact ("<data>", list.getData (itemIndex)); // Only from CS2
1349                         }
1350                         else if (isLargeIntegerType)
1351                         {
1352                             itemCompact = that.buildCompact ("<largeInteger>", list.getLargeInteger (itemIndex)); // Only from CS6
1353                         }
1354                         else
1355                         {
1356                             throw new Error ("[jamEngine getFromList] Unknown descriptor value type: " + typeID);
1357                         }
1358                         break;
1359                 }
1360                 arr[itemIndex] = itemCompact;
1361             }
1362             return arr;
1363         }
1364         //
1365         function getFromDescriptor (desc, parentContext)
1366         {
1367             if (desc)
1368             {
1369                 var obj = { };
1370                 var keyCount;
1371                 // keyCount = desc.count;   // NG in CS; OK in CS4 !!
1372                 try { keyCount = desc.count; } catch (e) { return null; }
1373                 for (var keyIndex = 0; keyIndex < keyCount; keyIndex++)
1374                 {
1375                     var keyID = desc.getKey (keyIndex);
1376                     var keyString = getFromId ([ "<key>", keyID ], parentContext);
1377                     var keyCompact;
1378                     var typeID;
1379                     // typeID = desc.getType (keyID);
1380                     try { typeID = desc.getType (keyID); } catch (e) { continue; }  // Data (Raw) type only from CS2 + Large integer type only from CS6
1381                     switch (typeID)
1382                     {
1383                         case DescValueType.BOOLEANTYPE:
1384                             keyCompact = that.buildCompact ("<boolean>", desc.getBoolean (keyID));
1385                             break;
1386                         case DescValueType.CLASSTYPE:
1387                             keyCompact = that.buildCompact ("<class>", getFromId ([ "<class>", desc.getClass (keyID) ]));
1388                             break;
1389                         /*
1390                         case DescValueType.RAWTYPE:
1391                             keyCompact = that.buildCompact ("<data>", desc.getData (keyID));  // Only from CS2
1392                             break;
1393                         */
1394                         case DescValueType.DOUBLETYPE:
1395                             keyCompact = that.buildCompact ("<double>", desc.getDouble (keyID));
1396                             break;
1397                         case DescValueType.ENUMERATEDTYPE:
1398                             var enumTypeContext = [ "<enumType>", desc.getEnumerationType (keyID) ];
1399                             var enumValueContext = [ "<enumValue>", desc.getEnumerationValue (keyID) ];
1400                             keyCompact = that.buildCompact ("<enumerated>", that.buildCompact (getFromId (enumTypeContext), getFromId (enumValueContext, enumTypeContext)));
1401                             break;
1402                         case DescValueType.INTEGERTYPE:
1403                             keyCompact = that.buildCompact ("<integer>", desc.getInteger (keyID));
1404                             break;
1405                         /*
1406                         case DescValueType.LARGEINTEGERTYPE:
1407                             keyCompact = that.buildCompact ("<largeInteger>", desc.getLargeInteger (keyID));  // Only from CS6
1408                             break;
1409                         */
1410                         case DescValueType.LISTTYPE:
1411                             keyCompact = that.buildCompact ("<list>", getFromList (desc.getList (keyID)));
1412                             break;
1413                         case DescValueType.OBJECTTYPE:
1414                             var objectTypeContext = [ "<class>", desc.getObjectType (keyID) ];
1415                             var objectValue = desc.getObjectValue (keyID);
1416                             keyCompact = that.buildCompact ("<object>", that.buildCompact (getFromId (objectTypeContext), getFromDescriptor (objectValue, objectTypeContext)));
1417                             break;
1418                         case DescValueType.ALIASTYPE:
1419                             try
1420                             {
1421                                 var fileRef = desc.getPath (keyID);
1422                                 keyCompact = that.buildCompact ("<path>", fileRef.fsName);
1423                             }
1424                             catch (e)
1425                             {
1426                                 keyCompact = that.buildCompact ("<path>", incompatiblePlatformPath);
1427                             }
1428                             break;
1429                         case DescValueType.REFERENCETYPE:
1430                             keyCompact = that.buildCompact ("<reference>", getFromReference (desc.getReference (keyID)));
1431                             break;
1432                         case DescValueType.STRINGTYPE:
1433                             keyCompact = that.buildCompact ("<string>", desc.getString (keyID));
1434                             break;
1435                         case DescValueType.UNITDOUBLE:
1436                             var unitTypeContext = [ "<unit>", desc.getUnitDoubleType (keyID) ];
1437                             var doubleValue = desc.getUnitDoubleValue (keyID);
1438                             keyCompact = that.buildCompact ("<unitDouble>", that.buildCompact (getFromId (unitTypeContext), doubleValue));
1439                             break;
1440                         default:
1441                             var isRawType;
1442                             var isLargeIntegerType;
1443                             try { isRawType = (typeID === DescValueType.RAWTYPE); } catch (e) { }
1444                             try { isLargeIntegerType = (typeID === DescValueType.LARGEINTEGERTYPE); } catch (e) { }
1445                             if (isRawType)
1446                             {
1447                                 keyCompact = that.buildCompact ("<data>", desc.getData (keyID)); // Only from CS2
1448                             }
1449                             else if (isLargeIntegerType)
1450                             {
1451                                 keyCompact = that.buildCompact ("<largeInteger>", desc.getLargeInteger (keyID));  // Only from CS6
1452                             }
1453                             else
1454                             {
1455                                 throw new Error ("[jamEngine getFromDescriptor] Unknown descriptor value type: " + typeID);
1456                             }
1457                             break;
1458                     }
1459                     obj[keyString] = keyCompact;
1460                 }
1461                 return obj;
1462             }
1463             else
1464             {
1465                 return null;
1466             }
1467         }
1468         //
1469         /**
1470          * @description Convert a descriptor object in JSON AM Data Format into an ActionDescriptor object.
1471          * @param {Object} descriptorObj Descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1472          * @returns {Object} ActionDescriptor object
1473          * @see jamEngine.classIdAndActionDescriptorToJson
1474          * @see jamEngine.eventIdAndActionDescriptorToJson
1475          * @example
1476          * app.putCustomOptions (signature, jamEngine.<strong>jsonToActionDescriptor</strong> (customOptions));
1477          */
1478         jamEngine.jsonToActionDescriptor = function (descriptorObj)
1479         {
1480             that = this;
1481             var actionDescriptor;
1482             if (descriptorObj)
1483             {
1484                 actionDescriptor = new ActionDescriptor ();
1485                 putInDescriptor (actionDescriptor, descriptorObj);
1486             }
1487             return actionDescriptor;
1488         };
1489         //
1490         /**
1491          * @description Convert a reference array in JSON AM Data Format into an ActionReference object.
1492          * @param {Array} referenceArr Reference array in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1493          * @returns {Object} ActionReference object
1494          * @see jamEngine.actionReferenceToJson
1495          * @example
1496          * var referenceArr =
1497          * [
1498          *     { "property": { "<property>": "hostName" } },
1499          *     { "application": { "<enumerated>": { "ordinal": "targetEnum" } } }
1500          * ];
1501          * var actionReference = jamEngine.<strong>jsonToActionReference</strong> (referenceArr);
1502          */
1503         jamEngine.jsonToActionReference = function (referenceArr)
1504         {
1505             that = this;
1506             var actionReference;
1507             if (referenceArr)
1508             {
1509                 actionReference = new ActionReference ();
1510                 putInReference (actionReference, referenceArr);
1511             }
1512             return actionReference;
1513         };
1514         //
1515         /**
1516          * @description Convert an event numerical ID and an ActionDescriptor object into an event unified ID string and a descriptor object in JSON AM Data Format.
1517          * @param {Number} eventId Event numerical ID
1518          * @param {Object|Null} [actionDescriptor] ActionDescriptor object (can be null)
1519          * @returns {Object} Event unified ID string and descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1520          * @see jamEngine.classIdAndActionDescriptorToJson
1521          * @see jamEngine.jsonToActionDescriptor
1522          * @example
1523          * jamEngine.meaningfulIds = true;
1524          * var playObj = jamEngine.<strong>eventIdAndActionDescriptorToJson</strong> (eventId, actionDescriptor);
1525          * $.writeln ('jamEngine.jsonPlay');
1526          * $.writeln ('(');
1527          * $.writeln (jamJSON.stringify (playObj["<event>"], '\t', '\t') + ',');
1528          * $.writeln (jamJSON.stringify (playObj["<descriptor>"], '\t', '\t'));
1529          * $.writeln (');');
1530          */
1531         jamEngine.eventIdAndActionDescriptorToJson = function (eventId, actionDescriptor)
1532         {
1533             that = this;
1534             var eventIdContext = [ "<event>", eventId ];
1535             return { "<event>": getFromId (eventIdContext), "<descriptor>": getFromDescriptor (actionDescriptor, eventIdContext) };
1536         };
1537         //
1538         /**
1539          * @description Convert a class numerical ID and an ActionDescriptor object into a class unified ID string and a descriptor object in JSON AM Data Format.
1540          * @param {Number} classId Class numerical ID
1541          * @param {Object} actionDescriptor ActionDescriptor object
1542          * @returns {Object} Class unified ID string and descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1543          * @see jamEngine.eventIdAndActionDescriptorToJson
1544          * @see jamEngine.jsonToActionDescriptor
1545          * @example
1546          * var objectObj = jamEngine.<strong>classIdAndActionDescriptorToJson</strong>
1547          * (
1548          *     jamEngine.uniIdStrToId (signature),
1549          *     app.getCustomOptions (signature)
1550          * );
1551          * var customOptions = objectObj["<descriptor>"];
1552          */
1553         jamEngine.classIdAndActionDescriptorToJson = function (classId, actionDescriptor)
1554         {
1555             that = this;
1556             var classIdContext = [ "<class>", classId ];
1557             return { "<class>": getFromId (classIdContext), "<descriptor>": getFromDescriptor (actionDescriptor, classIdContext) };
1558         };
1559         //
1560         /**
1561          * @description Convert an ActionReference object into a reference array in JSON AM Data Format.
1562          * @param {Object} actionReference ActionReference object
1563          * @returns {Array} Reference array in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1564          * @see jamEngine.jsonToActionReference
1565          * @example
1566          * jamEngine.meaningfulIds = true;
1567          * var referenceArr = jamEngine.<strong>actionReferenceToJson</strong> (actionReference);
1568          * $.writeln ('jamEngine.jsonGet');
1569          * $.writeln ('(');
1570          * $.writeln (jamJSON.stringify (referenceArr, '\t', '\t');
1571          * $.writeln (');');
1572          */
1573         jamEngine.actionReferenceToJson = function (actionReference)
1574         {
1575             that = this;
1576             return getFromReference (actionReference);
1577         };
1578         //
1579         function getReferenceClassId (ref)
1580         {
1581             classId = 0;
1582             do
1583             {
1584                 // var desiredClassId = ref.getDesiredClass ();
1585                 try { var desiredClassId = ref.getDesiredClass (); } catch (e) { break; }
1586                 if (desiredClassId !== propertyClassId)
1587                 {
1588                     classId = desiredClassId;
1589                     break;
1590                 }
1591                 ref = ref.getContainer ();
1592             }
1593             while (ref);
1594             return classId;
1595         }
1596         //
1597         /**
1598          * @description Play an event as a unified ID string with a descriptor object in JSON AM Data Format.
1599          * @param {String} eventUniIdStr Event unified ID string
1600          * @param {Object|Null} [descriptorObj] Descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>; can be null
1601          * @param {Object} [displayDialogs] Dialog mode:<br />
1602          * <ul>
1603          * <li>DialogModes.ERROR: show dialogs on error only (by default, unless jamEngine.displayDialogs has been set to another value)</li>
1604          * <li>DialogModes.ALL: show all dialogs</li>
1605          * <li>DialogModes.NO: show no dialogs</li>
1606          * </ul>
1607          * @returns {Object} Descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1608          * @see jamEngine.jsonGet
1609          * @example
1610          * jamEngine.<strong>jsonPlay</strong>
1611          * (
1612          *     "motionBlur",
1613          *     {
1614          *         "angle": { "<integer>": -45 },
1615          *         "distance": { "<unitDouble>": { "pixelsUnit": 200 } }
1616          *     }
1617          * );
1618          */
1619         jamEngine.jsonPlay = function (eventUniIdStr, descriptorObj, displayDialogs)
1620         {
1621             var eventId = this.uniIdStrToId (eventUniIdStr);
1622             var desc = this.jsonToActionDescriptor (descriptorObj);
1623             var parentContext;
1624             if (eventId === getEventId)
1625             {
1626                 var ref = desc.getReference (targetKeyId);
1627                 parentContext = [ "<class>", getReferenceClassId (ref) ];
1628             }
1629             else
1630             {
1631                 parentContext = [ "<event>", eventId ];
1632             }
1633             return getFromDescriptor (app.executeAction (eventId, desc, displayDialogs || this.displayDialogs), parentContext);
1634         };
1635         //
1636         /**
1637          * @description Get information from a reference array in JSON AM Data Format.
1638          * @param {Array} referenceArr Reference array in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1639          * @returns {Object} Descriptor object in <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-am-data-format/">JSON AM Data Format</a>
1640          * @see jamEngine.jsonPlay
1641          * @example
1642          * jamEngine.meaningfulIds = true;
1643          * var resultDescriptorObj = jamEngine.<strong>jsonGet</strong>
1644          * (
1645          *     [
1646          *         { "property": { "<property>": "numberOfDocuments" } },
1647          *         { "application": { "<enumerated>": { "ordinal": "targetEnum" } } }
1648          *     ]
1649          * );
1650          * alert ("Number of documents: " + resultDescriptorObj["numberOfDocuments"]["<integer>"]);
1651          */
1652         jamEngine.jsonGet = function (referenceArr)
1653         {
1654             var ref = this.jsonToActionReference (referenceArr);
1655             return getFromDescriptor (app.executeActionGet (ref), [ "<class>", getReferenceClassId (ref) ]);
1656         };
1657         //
1658         /**
1659          * @description Normalize a JSON AM item, according to normalizing options.
1660          * @param {Object|Array} item JSON AM item in compact format
1661          * @param {Object} [options] Normalizing options (meaningfulIds and parseFriendly, overriding currently defined jamEngine global options when present)
1662          * @returns {Object|Array} Normalized JSON AM item, according to normalizing options
1663          * @example
1664          * var item = { "<object>": { "'Grsc'": { "'Gry '": { "<double>": 50 } } } };
1665          * var normalizedItem = jamEngine.<strong>normalizeJsonItem</strong> (item, { meaningfulIds: true, parseFriendly: true });
1666          * alert (jamJSON.stringify (normalizedItem));
1667          * // -> [ "<object>", [ "grayscale", { "gray": [ "<double>", 50 ] } ] ]
1668          */
1669         jamEngine.normalizeJsonItem = function (item, options)
1670         {
1671             function normalizeItem (item)
1672             {
1673                 var explicit = that.parseCompact (item);
1674                 var type = explicit[0];
1675                 var value = explicit[1];
1676                 var normalizedValue;
1677                 switch (type)
1678                 {
1679                     case "<boolean>":
1680                     case "<data>":
1681                     case "<double>":
1682                     case "<identifier>":
1683                     case "<index>":
1684                     case "<integer>":
1685                     case "<largeInteger>":
1686                     case "<name>":
1687                     case "<offset>":
1688                     case "<path>":
1689                     case "<string>":
1690                         normalizedValue = value;
1691                         break;
1692                     case "<class>":
1693                         normalizedValue = value && getFromId ([ "<class>", that.uniIdStrToId (value) ]);
1694                         break;
1695                     case "<enumerated>":
1696                         var enumerated = that.parseCompact (value);
1697                         var enumTypeContext = [ "<enumType>", that.uniIdStrToId (enumerated[0]) ];
1698                         var enumValueContext = [ "<enumValue>", that.uniIdStrToId (enumerated[1]) ];
1699                         normalizedValue = that.buildCompact (getFromId (enumTypeContext), getFromId (enumValueContext, enumTypeContext));
1700                         break;
1701                     case "<list>":
1702                         normalizedValue = [ ];
1703                         for (var i = 0; i < value.length; i++)
1704                         {
1705                             normalizedValue.push (normalizeItem (value[i]));
1706                         }
1707                         break;
1708                     case "<object>":
1709                         var object = that.parseCompact (value);
1710                         var objectClassContext = [ "<class>", that.uniIdStrToId (object[0]) ];
1711                         var objectDescriptor = object[1];
1712                         var normalizedDescriptor;
1713                         if (objectDescriptor === null)
1714                         {
1715                             normalizedDescriptor = null;
1716                         }
1717                         else
1718                         {
1719                             normalizedDescriptor = { };
1720                             for (var key in objectDescriptor)
1721                             {
1722                                 if (objectDescriptor.hasOwnProperty (key))
1723                                 {
1724                                     var objectKeyContext = [ "<key>", that.uniIdStrToId (key) ];
1725                                     normalizedDescriptor[getFromId (objectKeyContext, objectClassContext)] = normalizeItem (objectDescriptor[key]);
1726                                 }
1727                             }
1728                         }
1729                         normalizedValue = that.buildCompact (getFromId (objectClassContext), normalizedDescriptor);
1730                         break;
1731                     case "<property>":
1732                         // Cannot be handled properly (lack of parent context), but never mind for the time being...
1733                         normalizedValue = getFromId ([ "<key>", that.uniIdStrToId (value) ]);
1734                         break;
1735                     case "<reference>":
1736                         normalizedValue = [ ];
1737                         for (var i = 0; i < value.length; i++)
1738                         {
1739                             var container = that.parseCompact (value[i]);
1740                             normalizedValue.push (that.buildCompact (getFromId ([ "<class>", that.uniIdStrToId (container[0]) ]), normalizeItem (container[1])));
1741                         }
1742                         break;
1743                     case "<unitDouble>":
1744                         var unitDouble = that.parseCompact (value);
1745                         var unitTypeContext = [ "<unit>", that.uniIdStrToId (unitDouble[0]) ];
1746                         normalizedValue = that.buildCompact (getFromId (unitTypeContext), unitDouble[1]);
1747                         break;
1748                     default:
1749                         throw new Error ("[jamEngine.normalizeJsonItem] Unknown item type: " + type);
1750                         break;
1751                 }
1752                 return that.buildCompact (type, normalizedValue);
1753             }
1754             //
1755             that = this;
1756             var saveMeaningfulIds = this.meaningfulIds;
1757             var saveParseFriendly = this.parseFriendly;
1758             if (options && (options.constructor === Object))
1759             {
1760                 if (typeof options.meaningfulIds !== 'undefined')
1761                 {
1762                     this.meaningfulIds = options.meaningfulIds;
1763                 }
1764                 if (typeof options.parseFriendly !== 'undefined')
1765                 {
1766                     this.parseFriendly = options.parseFriendly;
1767                 }
1768             }
1769             var normalizedItem = normalizeItem (item);
1770             this.meaningfulIds = saveMeaningfulIds;
1771             this.parseFriendly = saveParseFriendly;
1772             return normalizedItem;
1773         };
1774         //
1775         function simplifyRef (ref)
1776         {
1777             var simplifiedRef = [ ];
1778             for (var i = 0; i < ref.length; i++)
1779             {
1780                 var element = ref[i];
1781                 var simplifiedElement = { };
1782                 var desiredClass = element[0];
1783                 var form = element[1][0];
1784                 var value = element[1][1];
1785                 switch (form)
1786                 {
1787                     case "<class>":
1788                     case "<identifier>":
1789                     case "<index>":
1790                     case "<name>":
1791                     case "<offset>":
1792                     case "<property":
1793                         simplifiedElement[desiredClass] = value;
1794                         break;
1795                     case "<enumerated>":
1796                         simplifiedElement[desiredClass] = value[1];
1797                         break;
1798                     default:
1799                         throw new Error ("[jamEngine simplifyRef] Unexpected element form: " + form);
1800                         break;
1801                 }
1802                 simplifiedRef.push (simplifiedElement);
1803             }
1804             return simplifiedRef;
1805         }
1806         //
1807         function simplifyItem (item, hook)
1808         {
1809             var simplifiedItem;
1810             var type = item[0];
1811             var value = item[1];
1812             switch (type)
1813             {
1814                 case "<boolean>":
1815                 case "<class>":
1816                 case "<data>":
1817                 case "<double>":
1818                 case "<integer>":
1819                 case "<largeInteger>":
1820                 case "<path>":
1821                 case "<string>":
1822                     simplifiedItem = value;
1823                     break;
1824                 case "<list>":
1825                     simplifiedItem = simplifyList (value, hook);
1826                     break;
1827                 case "<enumerated>":
1828                 case "<unitDouble>":
1829                     simplifiedItem = value[1];
1830                     break;
1831                 case "<object>":
1832                     simplifiedItem = simplifyDesc (value[1], hook);
1833                     break;
1834                 case "<reference>":
1835                     simplifiedItem = simplifyRef (value);
1836                     break;
1837                 default:
1838                     throw new Error ("[jamEngine simplifyItem] Unexpected item type: " + type);
1839                     break;
1840             }
1841             return simplifiedItem;
1842         }
1843         //
1844         function simplifyList (list, hook)
1845         {
1846             var simplifiedList = [ ];
1847             for (var i = 0; i < list.length; i++)
1848             {
1849                 simplifiedList.push (simplifyItem (list[i], hook));
1850             }
1851             return simplifiedList;
1852         }
1853         //
1854         function simplifyDesc (desc, hook)
1855         {
1856             var getDefaultValue = function (desc, key) { return simplifyItem (desc[key], hook); };
1857             var simplifiedDesc = { };
1858             for (var key in desc)
1859             {
1860                 if (desc.hasOwnProperty (key))
1861                 {
1862                     var value = undefined;
1863                     if (typeof hook === 'function')
1864                     {
1865                         value = hook (desc, key, getDefaultValue);
1866                     }
1867                     if (typeof value === 'undefined')
1868                     {
1869                         value = simplifyItem (desc[key], hook);
1870                     }
1871                     simplifiedDesc[key] = value;
1872                 }
1873             }
1874             return simplifiedDesc;
1875         }
1876         //
1877         /**
1878          * @description Simplifies a JSON AM Object into a JSON object.
1879          * @param {Object|Array} object JSON AM Object
1880          * @param {Function} [hookFunction] Hook function: (desc, key, getDefaultValue); returns value or undefined
1881          * @returns {Object} Simplified JSON object
1882          * @example
1883          * jamStyles.fromLayerEffectsObject = function (layerEffectsObject)
1884          * {
1885          *     return jamEngine.<strong>simplifyObject</strong> (layerEffectsObject);
1886          * };
1887          */
1888         jamEngine.simplifyObject = function (object, hookFunction)
1889         {
1890             return simplifyDesc ((this.normalizeJsonItem (object, { meaningfulIds: true, parseFriendly: true }))[1][1], hookFunction);
1891         };
1892         //
1893         /**
1894          * @description Simplifies a JSON AM List into a JSON array.
1895          * @param {Object|Array} object JSON AM List
1896          * @param {Function} [hookFunction] Hook function: (desc, key, getDefaultValue); returns value or undefined
1897          * @returns {Object} Simplified JSON array
1898          * @example
1899          * jamHelpers.fromCurvePointList = function (curvePointList)
1900          * {
1901          *     return jamEngine.<strong>simplifyList</strong> (curvePointList);
1902          * };
1903          */
1904         jamEngine.simplifyList = function (list, hookFunction)
1905         {
1906             return simplifyList ((this.normalizeJsonItem (list, { meaningfulIds: true, parseFriendly: true }))[1], hookFunction);
1907         };
1908         //
1909         /**
1910          * @description Parse an element in compact format into type and value as a two-element array.
1911          * @param {Object|Array} compact JavaScript literal object or array in compact format: { (type): (value) } or [ (type), (value) ]
1912          * @returns {Array} Two-element array: [ (type), (value) ]
1913          * @see jamEngine.buildCompact
1914          * @example
1915          * var compactObj = { "percentUnit": 50 };
1916          * var parsed = jamEngine.<strong>parseCompact</strong> (compactObj);
1917          * // -> [ "percentUnit", 50 ]
1918          * @example
1919          * var compactArr = [ "percentUnit", 50 ];
1920          * var parsed = jamEngine.<strong>parseCompact</strong> (compactArr);
1921          * // -> [ "percentUnit", 50 ]
1922          */
1923         // Parse compact syntax element: { (type): (value) } or [ (type), (value) ] to array: [ (type), (value) ]
1924         jamEngine.parseCompact = function (compact)
1925         {
1926             var result = [ ];
1927             if (compact.constructor === Object)
1928             {
1929                 var keys = [ ];
1930                 for (var k in compact)
1931                 {
1932                     if (compact.hasOwnProperty (k))
1933                     {
1934                         keys.push (k);
1935                     }
1936                 }
1937                 if (keys.length === 1)
1938                 {
1939                     result[0] = keys[0];
1940                     result[1] = compact[keys[0]];
1941                 }
1942                 else
1943                 {
1944                     throw new Error ("[jamEngine.parseCompact] Syntax error: " + compact.toSource ());
1945                 }
1946             }
1947             else if (compact.constructor === Array)
1948             {
1949                 if (compact.length === 2)
1950                 {
1951                     result[0] = compact[0];
1952                     result[1] = compact[1];
1953                 }
1954                 else
1955                 {
1956                     throw new Error ("[jamEngine.parseCompact] Syntax error: " + compact.toSource ());
1957                 }
1958             }
1959             else
1960             {
1961                 throw new Error ("[jamEngine.parseCompact] JavaScript object or array expected");
1962             }
1963             return result;
1964         };
1965         //
1966         /**
1967          * @description Expand a special case of JavaScript literal object, from compact to explicit format.
1968          * @param {Object|Array} compact JavaScript literal object or array in compact format: { (type): (value) } or [ (type), (value) ]
1969          * @param {String} [typeKey] Type key string ("<type>" by default)
1970          * @param {String} [valueKey] Value key string ("<value>" by default)
1971          * @returns {Object} JavaScript literal object in explicit format: { (typeKey): (type), (valueKey): (value) }
1972          * @see jamEngine.explicitToCompact
1973          * @example
1974          * var compactObj = { "pixelsUnit": 5 };
1975          * var explicitObj = jamEngine.<strong>compactToExplicit</strong> (compactObj, "<unit>", "<double>");
1976          * // -> { "<unit>": "pixelsUnit", "<double>": 5 }
1977          * @example
1978          * var compactArr = [ "pixelsUnit", 5 ];
1979          * var defaultExplicitObj = jamEngine.<strong>compactToExplicit</strong> (compactArr);
1980          * // -> { "<type>": "pixelsUnit", "<value>": 5 }
1981          */
1982         jamEngine.compactToExplicit = function (compact, typeKey, valueKey)
1983         {
1984             var explicit = { };
1985             var typeValue = this.parseCompact (compact);
1986             explicit[typeKey || "<type>"] = typeValue[0];
1987             explicit[valueKey || "<value>"] = typeValue[1];
1988             return explicit;
1989         };
1990         //
1991         /**
1992          * @description Build an element in compact format from type and value.
1993          * @param {String} type Type (must be a string)
1994          * @param {Object|Array|String|Number|Boolean|Null} value Value
1995          * @returns {Object|Array} JavaScript literal object or array in compact format: { (type): (value) } or [ (type), (value) ] depending on the boolean value of jamEngine.parseFriendy
1996          * @see jamEngine.parseCompact
1997          * @example
1998          * jamEngine.parseFriendly = false;
1999          * var compact = jamEngine.<strong>buildCompact</strong> ("percentUnit", 50);
2000          * // -> { "percentUnit": 50 }
2001          * @example
2002          * jamEngine.parseFriendly = true;
2003          * var compact = jamEngine.<strong>buildCompact</strong> ("percentUnit", 50);
2004          * // -> [ "percentUnit", 50 ]
2005          */
2006         jamEngine.buildCompact = function (type, value)
2007         {
2008             var compact;
2009             if (typeof type === 'string')
2010             {
2011                 if (this.parseFriendly)
2012                 {
2013                     compact = [ type, value ];
2014                 }
2015                 else
2016                 {
2017                     compact = { };
2018                     compact[type] = value;
2019                 }
2020             }
2021             else
2022             {
2023                 throw new Error ("[jamEngine.buildCompact] String expected");
2024             }
2025             return compact;
2026         };
2027         //
2028         /**
2029          * @description Shrink a special case of JavaScript literal object, from explicit to compact format.
2030          * @param {Object} explicit JavaScript literal object in explicit format: { (typeKey): (type), (valueKey): (value) }
2031          * @param {String} [typeKey] Type key string ("<type>" by default)
2032          * @param {String} [valueKey] Value key string ("<value>" by default)
2033          * @returns {Object|Array} JavaScript literal object or array in compact format: { (type): (value) } or [ (type), (value) ] depending on the boolean value of jamEngine.parseFriendy
2034          * @see jamEngine.compactToExplicit
2035          * @example
2036          * jamEngine.parseFriendly = false;
2037          * var explicitObj = { "<unit>": "pixelsUnit", "<double>": 5 };
2038          * var compactObj = jamEngine.<strong>explicitToCompact</strong> (explicitObj, "<unit>", "<double>");
2039          * // -> { "pixelsUnit": 5 }
2040          * @example
2041          * jamEngine.parseFriendly = true;
2042          * var defaultExplicitObj = { "<type>": "pixelsUnit", "<value>": 5 };
2043          * var compactArr = jamEngine.<strong>explicitToCompact</strong> (defaultExplicitObj);
2044          * // -> [ "pixelsUnit", 5 ]
2045          */
2046         jamEngine.explicitToCompact = function (explicit, typeKey, valueKey)
2047         {
2048             var compact; 
2049             if (explicit.constructor === Object)
2050             {
2051                 compact = this.buildCompact (explicit[typeKey || "<type>"], explicit[valueKey || "<value>"]);
2052             }
2053             else
2054             {
2055                 throw new Error ("[jamEngine.explicitToCompact] JavaScript object expected");
2056             }
2057             return compact;
2058         };
2059         //
2060         // Pre-check and update the conflicting StringID strings table before using it
2061         // From CS3, for instance, "mergedLayers" is no more equivalent to "'MrgL'"...
2062         for (var charIdStr in conflictingStringIdStrs)
2063         {
2064             if (conflictingStringIdStrs.hasOwnProperty (charIdStr))
2065             {
2066                 var stringIdStrs = conflictingStringIdStrs[charIdStr];
2067                 for (var index = stringIdStrs.length - 1; index >= 0; index--)
2068                 {
2069                     var stringIdStr = stringIdStrs[index];
2070                     if (!(app.charIDToTypeID (charIdStr.substring (1, 5)) === app.stringIDToTypeID (stringIdStr)))
2071                     {
2072                         stringIdStrs.splice (index, 1);
2073                     }
2074                 }
2075                 if (stringIdStrs.length < 2)
2076                 {
2077                     delete conflictingStringIdStrs[charIdStr];
2078                 }
2079             }
2080         }
2081         // Pre-check the disambiguating rules table before using it (better safe than sorry)
2082         for (var charIdStr in contextRules)
2083         {
2084             if (contextRules.hasOwnProperty (charIdStr))
2085             {
2086                 if (charIdStr in conflictingStringIdStrs)
2087                 {
2088                     var rule = contextRules[charIdStr];
2089                     for (var kind in rule)
2090                     {
2091                         if (rule.hasOwnProperty (kind))
2092                         {
2093                             switch (kind)
2094                             {
2095                                 case "<class>":
2096                                 case "<event>":
2097                                 case "<enumType>":
2098                                 case "<enumValue>":
2099                                 case "<key>":
2100                                 case "<unknown>":
2101                                     if (app.charIDToTypeID (charIdStr.substring (1, 5)) != app.stringIDToTypeID (rule[kind]))
2102                                     {
2103                                         throw new Error ("[jamEngine] " + "\"" + charIdStr + "\" and \"" + rule[kind] + "\" are not equivalent ID strings");
2104                                     }
2105                                     break;
2106                                 case "<classKey>":
2107                                 case "<eventKey>":
2108                                 case "<typeValue>":
2109                                     for (var parent in rule[kind])
2110                                     {
2111                                         if (rule[kind].hasOwnProperty (parent))
2112                                         {
2113                                             if (app.charIDToTypeID (charIdStr.substring (1, 5)) != app.stringIDToTypeID (rule[kind][parent]))
2114                                             {
2115                                                 throw new Error ("[jamEngine] " + "\"" + charIdStr + "\" and \"" + rule[kind][parent] + "\" are not equivalent ID strings");
2116                                             }
2117                                         }
2118                                     }
2119                                     break;
2120                             }
2121                         }
2122                     }
2123                 }
2124                 else
2125                 {
2126                     delete contextRules[charIdStr];
2127                 }
2128             }
2129         }
2130     } ());
2131 }
2132 
2133 //------------------------------------------------------------------------------
2134 
2135