From 29f926c57b6f6faab0bea3056b78e2b86fead182 Mon Sep 17 00:00:00 2001 From: Shreya Gupta Date: Wed, 18 Mar 2026 18:19:50 +0530 Subject: [PATCH 1/2] added custom script template --- README.md | 2 +- .../customCapabilityTemplate/README.md | 56 ++ .../capabilityLogic.jsx | 50 ++ .../customCapabilityTemplate/errors.jsx | 167 +++++ .../customCapabilityTemplate/json2.jsx | 530 +++++++++++++ .../customCapabilityTemplate/manifest.json | 16 + .../customCapabilityTemplate/sample.jsx | 224 ++++++ .../customCapabilityTemplate/utils.jsx | 702 ++++++++++++++++++ samples.md | 2 + 9 files changed, 1748 insertions(+), 1 deletion(-) create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/README.md create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/errors.jsx create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/json2.jsx create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/manifest.json create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/sample.jsx create mode 100644 SampleScripts/ExtendScript/customCapabilityTemplate/utils.jsx diff --git a/README.md b/README.md index 87dd6c5..7c67d57 100644 --- a/README.md +++ b/README.md @@ -1045,7 +1045,7 @@ The second way is to log data in the application's log. You can use the followin ``` ### __Complete Sample script__ -A sample script can be found under [Example](Example/), where the main script is sample.jsx, and the rest are supporting scripts. +Sample capability bundles live under [SampleScripts](SampleScripts/). For a **minimal template** (logging, working folder, error handling, uploads), start from [customCapabilityTemplate](SampleScripts/ExtendScript/customCapabilityTemplate/) and edit `capabilityLogic.jsx`. - This script has the functionality to create `idml` from an InDesign document. - This script can be treated as a baseline script that handles input and output. diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/README.md b/SampleScripts/ExtendScript/customCapabilityTemplate/README.md new file mode 100644 index 0000000..45b4970 --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/README.md @@ -0,0 +1,56 @@ +# Custom Capability Template + +Minimal ExtendScript template for **InDesign Server** capabilities: add logic in `capabilityLogic.jsx` and keep standard parameter handling, logging, timings, and errors. + +## InDesign Server scripting + +Server runs headless—**do not rely on `app.activeDocument` or creating documents without a file path**. Open the job document with `app.open(File(...))` using a path relative to `workingFolder` (this template does that via `params.targetDocument`). + +For the object model (`Application`, `Document`, `app.open`, etc.), see the ExtendScript API reference, e.g. [InDesign Server 14 Application / object model](https://www.indesignjs.de/extendscriptAPI/indesign-server14/#Application.html). + +## Quick start + +1. **Required input:** `params.targetDocument` — relative path to the `.indd` under `workingFolder`. The template opens it, relinks, collects warnings, then runs your logic. + +2. **Implement your logic** in `capabilityLogic.jsx` inside `CAPABILITY.run`: + - `document` — opened document + - `parameters` — `params` from the job + - `allParameters` — full job input (`workingFolder`, `params`, …) + - `returnVal` — populate for `processedData` + +3. **Run** the capability with JSON containing `workingFolder` and `params`. + +## Files + +| File | Purpose | +|------|--------| +| `sample.jsx` | Entry: open document from `targetDocument`, then `CAPABILITY.run`. | +| `capabilityLogic.jsx` | **Edit here** — your capability inside `CAPABILITY.run()`. | +| `errors.jsx`, `json2.jsx`, `utils.jsx` | Shared helpers (same pattern as other samples). | +| `manifest.json` | Capability manifest. | + +## Example: save to output path + +In `capabilityLogic.jsx`: + +```javascript +var outputPath = UTILS.GetStringFromObject(parameters, 'outputPath') +outputPath = UTILS.GetFullPath(outputPath) +document.save(File(outputPath)) +UTILS.AddAssetToBeUploaded(UTILS.GetRelativeReturnPath(outputPath)) +returnVal.outputPath = outputPath +``` + +## Input shape + +```json +{ + "workingFolder": "/path/to/working/folder", + "params": { + "targetDocument": "doc.indd", + "outputPath": "out/result.indd" + } +} +``` + +If your capability only produces new files (no input `.indd`), you still need a document open on Server—use a small template `.indd` as `targetDocument` or extend the template to open a specific starter file your job provides. diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx new file mode 100644 index 0000000..04fa3ad --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx @@ -0,0 +1,50 @@ +/************************************************************************* + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2026 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + **************************************************************************/ + +// capabilityLogic.jsx +// Add your capability logic here. This is the only file you need to edit. +// InDesign Server: the document is always opened from params.targetDocument before this runs. +// +// - document: InDesign document (already open) +// - parameters: allParameters.params (or allParameters.input.params) +// - allParameters: full job input (workingFolder, params, etc.) +// - returnVal: object to fill with your result; will be returned as processedData + +var CAPABILITY = {} + +/** + * Implement your capability here. + * Add properties to returnVal to return data (e.g. returnVal.outputPath = path). + * Use UTILS.GetStringFromObject(parameters, 'key'), UTILS.GetFullPath(path), etc. + * Use UTILS.AddAssetToBeUploaded(path) to upload output files. + */ +CAPABILITY.run = function (document, parameters, allParameters, returnVal) { + // ========== ADD YOUR CAPABILITY LOGIC BELOW ========== + + UTILS.Log('Custom capability: no logic implemented yet.') + + // Example (uncomment and adjust for your case): + // var outputPath = UTILS.GetStringFromObject(parameters, 'outputPath') + // outputPath = UTILS.GetFullPath(outputPath) + UTILS.Log('outputPathYayyyyy: ') + app.open(File("/input.inddc")) + UTILS.Log('document openedYayyyyy') + // UTILS.AddAssetToBeUploaded(UTILS.GetRelativeReturnPath(outputPath)) + // returnVal.outputPath = outputPath + + // ========== ADD YOUR CAPABILITY LOGIC ABOVE ========== +} diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/errors.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/errors.jsx new file mode 100644 index 0000000..a0c1cfc --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/errors.jsx @@ -0,0 +1,167 @@ +/************************************************************************* + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2025 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + **************************************************************************/ +// errors.jsx + +// Error constants are defined here. +/* This document list all the error which are possible from the scripts. The error object is built such that it has an error code and some error strings. + The error strings are constructed in a way such that the first string is default and a string literal. It is to be returned anyhow. The subsequent strings can be + strings having '^1' as placeholder replacement string. This replacement string can be replaced with relevant information. Based on the information + available the final error message can be created. +*/ +/* eslint-disable no-unused-vars */ + +var ErrorReplacementString = '^1' + +var Errors = { + // Errors during processing: 1001-1999 + InternalScriptError: { + errorCode: 'internal_error', + errorStrings: [ + 'Internal script error. This should not had happened.' + ] + }, + ProcessingErrorOccurred: { + errorCode: 'internal_error', + errorStrings: [ + 'Internal error: Error during processing.' + ] + }, + PDFPresetNotSet: { + errorCode: 'capability_error', + errorStrings: [ + 'Capability error: No PDF Preset could be set.' + ] + }, + OutputDirError: { + errorCode: 'internal_error', + errorStrings: [ + 'Internal error: Unable to create specified output directory.' + ] + }, + RelinkError: { + errorCode: 'capability_error', + errorStrings: [ + 'Capability error: Unable to relink.', + 'Relink failed for ^1.' + ] + }, + PlaceError: { + errorCode: 'capability_error', + errorStrings: [ + 'Capability error: Unable to place.', + 'Place failed for ^1.' + ] + }, + + // Errors in input: 2001-2999 + ArrayExpected: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected array ', + 'for property ^1.' + ] + }, + ParsingBoolError: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected boolean ', + '^1 is the provided value ', + 'for property ^1.' + ] + }, + ParsingIntError: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected integer ', + '^1 is the provided value ', + 'for property ^1.' + ] + }, + ParsingFloatError: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected number ', + '^1 is the provided value ', + 'for property ^1.' + ] + }, + ParsingStringError: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected string ', + '^1 is the provided value ', + 'for property ^1.' + ] + }, + OutOfBound: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Incorrect range provided. ', + 'Culprit Value is ^1.' + ] + }, + MissingKey: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Key not found in object. ', + 'Missing key is ^1.' + ] + }, + EnumError: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: No matching enum value found. ', + '^1 is the provided value', + 'for property ^1.' + ] + }, + MissingParams: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Either the \'params\' is not found in the request or it is not in the correct format.' + ] + }, + ObjectExpected: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Expected object ', + 'for property ^1.' + ] + }, + AtLeastOneInternalParamShouldBePresent: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: At least one of the parameters should be present. ', + 'Missing parameters are ^1' + ] + }, + + // Errors coming from IDS and sent under ProcessingErrorOccurred. These are specific errors which need string change while returning to the user. + CannotOpenFileError: { + errorCode: 'capability_error', // kCannotOpenFileError + errorStrings: [ + 'Capability error: Cannot open file.' + ] + }, + InCorrectRelativePath: { + errorCode: 'parameter_error', + errorStrings: [ + 'Parameter error: Incorrect relative path.', + '^1 is the provided path.' + ] + } +} diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/json2.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/json2.jsx new file mode 100644 index 0000000..397349b --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/json2.jsx @@ -0,0 +1,530 @@ +// json2.js +// 2017-06-12 +// Public Domain. +// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + +// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +// NOT CONTROL. + +// This file creates a global JSON object containing two methods: stringify +// and parse. This file provides the ES5 JSON capability to ES3 systems. +// If a project might run on IE8 or earlier, then this file should be included. +// This file does nothing on ES5 systems. + +// JSON.stringify(value, replacer, space) +// value any JavaScript value, usually an object or array. +// replacer an optional parameter that determines how object +// values are stringified for objects. It can be a +// function or an array of strings. +// space an optional parameter that specifies the indentation +// of nested structures. If it is omitted, the text will +// be packed without extra whitespace. If it is a number, +// it will specify the number of spaces to indent at each +// level. If it is a string (such as "\t" or " "), +// it contains the characters used to indent at each level. +// This method produces a JSON text from a JavaScript value. +// When an object value is found, if the object contains a toJSON +// method, its toJSON method will be called and the result will be +// stringified. A toJSON method does not serialize: it returns the +// value represented by the name/value pair that should be serialized, +// or undefined if nothing should be serialized. The toJSON method +// will be passed the key associated with the value, and this will be +// bound to the value. + +// For example, this would serialize Dates as ISO strings. + +// Date.prototype.toJSON = function (key) { +// function f(n) { +// // Format integers to have at least two digits. +// return (n < 10) +// ? "0" + n +// : n; +// } +// return this.getUTCFullYear() + "-" + +// f(this.getUTCMonth() + 1) + "-" + +// f(this.getUTCDate()) + "T" + +// f(this.getUTCHours()) + ":" + +// f(this.getUTCMinutes()) + ":" + +// f(this.getUTCSeconds()) + "Z"; +// }; + +// You can provide an optional replacer method. It will be passed the +// key and value of each member, with this bound to the containing +// object. The value that is returned from your method will be +// serialized. If your method returns undefined, then the member will +// be excluded from the serialization. + +// If the replacer parameter is an array of strings, then it will be +// used to select the members to be serialized. It filters the results +// such that only members with keys listed in the replacer array are +// stringified. + +// Values that do not have JSON representations, such as undefined or +// functions, will not be serialized. Such values in objects will be +// dropped; in arrays they will be replaced with null. You can use +// a replacer function to replace those with JSON values. + +// JSON.stringify(undefined) returns undefined. + +// The optional space parameter produces a stringification of the +// value that is filled with line breaks and indentation to make it +// easier to read. + +// If the space parameter is a non-empty string, then that string will +// be used for indentation. If the space parameter is a number, then +// the indentation will be that many spaces. + +// Example: + +// text = JSON.stringify(["e", {pluribus: "unum"}]); +// // text is '["e",{"pluribus":"unum"}]' + +// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); +// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +// text = JSON.stringify([new Date()], function (key, value) { +// return this[key] instanceof Date +// ? "Date(" + this[key] + ")" +// : value; +// }); +// // text is '["Date(---current time---)"]' + +// JSON.parse(text, reviver) +// This method parses a JSON text to produce an object or array. +// It can throw a SyntaxError exception. + +// The optional reviver parameter is a function that can filter and +// transform the results. It receives each of the keys and values, +// and its return value is used instead of the original value. +// If it returns what it received, then the structure is not modified. +// If it returns undefined then the member is deleted. + +// Example: + +// // Parse the text. Values that look like ISO date strings will +// // be converted to Date objects. + +// myData = JSON.parse(text, function (key, value) { +// var a; +// if (typeof value === "string") { +// a = +// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +// if (a) { +// return new Date(Date.UTC( +// +a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6] +// )); +// } +// return value; +// } +// }); + +// myData = JSON.parse( +// "[\"Date(09/09/2001)\"]", +// function (key, value) { +// var d; +// if ( +// typeof value === "string" +// && value.slice(0, 5) === "Date(" +// && value.slice(-1) === ")" +// ) { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } +// } +// return value; +// } +// ); + +// This is a reference implementation. You are free to copy, modify, or +// redistribute. + +/*jslint + eval, for, this +*/ + +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== "object") { + JSON = {}; +} + +(function () { + "use strict"; + + var rx_one = /^[\],:{}\s]*$/; + var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; + var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var rx_four = /(?:^|:|,)(?:\s*\[)+/g; + var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + function f(n) { + // Format integers to have at least two digits. + return (n < 10) + ? "0" + n + : n; + } + + function this_value() { + return this.valueOf(); + } + + if (typeof Date.prototype.toJSON !== "function") { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? ( + this.getUTCFullYear() + + "-" + + f(this.getUTCMonth() + 1) + + "-" + + f(this.getUTCDate()) + + "T" + + f(this.getUTCHours()) + + ":" + + f(this.getUTCMinutes()) + + ":" + + f(this.getUTCSeconds()) + + "Z" + ) + : null; + }; + + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; + } + + var gap; + var indent; + var meta; + var rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? "\"" + string.replace(rx_escapable, function (a) { + var c = meta[a]; + return typeof c === "string" + ? c + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + "\"" + : "\"" + string + "\""; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var mind = gap; + var partial; + var value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if ( + value + && typeof value === "object" + && typeof value.toJSON === "function" + ) { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === "function") { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case "string": + return quote(value); + + case "number": + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return (isFinite(value)) + ? String(value) + : "null"; + + case "boolean": + case "null": + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce "null". The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is "object", we might be dealing with an object or an array or +// null. + + case "object": + +// Due to a specification blunder in ECMAScript, typeof null is "object", +// so watch out for that case. + + if (!value) { + return "null"; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === "[object Array]") { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || "null"; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? "[]" + : gap + ? ( + "[\n" + + gap + + partial.join(",\n" + gap) + + "\n" + + mind + + "]" + ) + : "[" + partial.join(",") + "]"; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === "object") { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === "string") { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + (gap) + ? ": " + : ":" + ) + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + (gap) + ? ": " + : ":" + ) + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? "{}" + : gap + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== "function") { + meta = { // table of character substitutions + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + "\"": "\\\"", + "\\": "\\\\" + }; + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ""; + indent = ""; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === "number") { + for (i = 0; i < space; i += 1) { + indent += " "; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === "string") { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== "function" && ( + typeof replacer !== "object" + || typeof replacer.length !== "number" + )) { + throw new Error("JSON.stringify"); + } + +// Make a fake root object containing our value under the key of "". +// Return the result of stringifying the value. + + return str("", {"": value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== "function") { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k; + var v; + var value = holder[key]; + if (value && typeof value === "object") { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + rx_dangerous.lastIndex = 0; + if (rx_dangerous.test(text)) { + text = text.replace(rx_dangerous, function (a) { + return ( + "\\u" + + ("0000" + a.charCodeAt(0).toString(16)).slice(-4) + ); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with "()" and "new" +// because they can cause invocation, and "=" because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we +// replace all simple value tokens with "]" characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or "]" or +// "," or ":" or "{" or "}". If that is so, then the text is safe for eval. + + if ( + rx_one.test( + text + .replace(rx_two, "@") + .replace(rx_three, "]") + .replace(rx_four, "") + ) + ) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The "{" operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval("(" + text + ")"); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return (typeof reviver === "function") + ? walk({"": j}, "") + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError("JSON.parse"); + }; + } +}()); \ No newline at end of file diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/manifest.json b/SampleScripts/ExtendScript/customCapabilityTemplate/manifest.json new file mode 100644 index 0000000..c43fbda --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/manifest.json @@ -0,0 +1,16 @@ +{ + "manifestVersion": "1.0.0", + "name": "customCapabilityTemplate", + "version": "1.0.5", + "host": { + "app": "indesign", + "appVersionStrategy": "LATEST_VERSION" + }, + "apiEntryPoints": [ + { + "type": "capability", + "path": "sample.jsx", + "language": "extendscript" + } + ] +} diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/sample.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/sample.jsx new file mode 100644 index 0000000..1d2e5c4 --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/sample.jsx @@ -0,0 +1,224 @@ +/************************************************************************* + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2026 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + **************************************************************************/ + +// Custom Capability Template - sample.jsx +// For InDesign Server: there is no reliable "active document" — always open the job document +// via params.targetDocument (see Application / Document in the ExtendScript object model). +// Add your logic in capabilityLogic.jsx. + +// @include "errors.jsx" +// @include "json2.jsx" +// @include "utils.jsx" +// @include "capabilityLogic.jsx" +// File inclusion ends. + +/* globals app, Errors, File, MeasurementUnits, PageNumberingOptions, UTILS */ + +// Globals: define any globals here. +var timingObject = {} +var warnings = {} +var document +// Globals end. + +// Constant strings +var KeyStrings = { + OutputPath: 'outputPath', + ProcessedData: 'processedData', + TargetDocument: 'targetDocument', + TimeDocumentClose: 'DocumentClose', + TimeDocumentOpen: 'DocumentOpen', + TimeProcessing: 'TimeProcessing', + TimeOverall: 'Overall', + TimeRelinkAssets: 'RelinkAssets', + Timings: 'timings', + WorkingFolder: 'workingFolder' +} + +/* Collects document warnings (missing links, fonts, etc.) into an object for the response. */ +function handleWarnings (doc) { + UTILS.Log('Handling warnings') + var errorlog = app.errorListErrors + var warningsObject = {} + var missingLinkArray = [] + var missingFontArray = [] + var otherWarningsArray = [] + var missingLinkStr = '(Link missing.; ' + var missingLinkStrLen = missingLinkStr.length + + for (var i = 0; i < errorlog.count(); i++) { + var error = errorlog[i] + UTILS.Log('Warning No. ' + (i + 1) + ': ' + error.listErrorCode + '(' + error.listErrorMessage + ')') + if (error.listErrorCode === 35842) { + var missingLink = error.listErrorMessage + missingLink = missingLink.substring(missingLink.indexOf(missingLinkStr) + missingLinkStrLen) + if (missingLinkArray.indexOf(missingLink) === -1) { + missingLinkArray.push(missingLink) + } + } else if (error.listErrorCode === 1 && error.listErrorMessage.search(/missing font/i) !== -1) { + var missingFont = error.listErrorMessage + missingFont = missingFont.substring(missingFont.search(/missing font/i) + 13) + if (missingFontArray.indexOf(missingFont) === -1) { + missingFontArray.push(missingFont) + } + } else if (error.listErrorCode === 1) { + otherWarningsArray.push(error.listErrorMessage) + } + } + if (missingLinkArray.length > 0) { + warningsObject.missingLinks = missingLinkArray + } + if (missingFontArray.length > 0) { + warningsObject.missingFonts = missingFontArray + } + if (otherWarningsArray.length > 0) { + warningsObject.otherWarnings = otherWarningsArray + } + return warningsObject +} + +function ProcessParams (parameters, allParameters) { + UTILS.Log('Processing parameters internal') + var returnVal = {} + + // Open the target document (required on InDesign Server) + var documentPath = UTILS.GetStringFromObject(parameters, KeyStrings.TargetDocument) + documentPath = UTILS.GetFullPath(documentPath) + + UTILS.Log('Opening document') + var tempTime = new Date().getTime() + document = app.open(File(documentPath)) + timingObject[KeyStrings.TimeDocumentOpen] = (new Date()).getTime() - tempTime + UTILS.Log('Opened document') + + tempTime = new Date().getTime() + UTILS.UpdateDocumentLinks(document) + timingObject[KeyStrings.TimeRelinkAssets] = (new Date()).getTime() - tempTime + UTILS.Log('Updated links in the document') + + UTILS.Log('Number of errors: ' + app.errorListErrors.count()) + if (app.errorListErrors.count() > 0) { + warnings = handleWarnings(document) + } + app.clearAllErrors() + UTILS.Log('Got the warnings from the document.') + + tempTime = new Date().getTime() + + // Execute your capability logic (implement in capabilityLogic.jsx) + CAPABILITY.run(document, parameters, allParameters, returnVal) + + timingObject[KeyStrings.TimeProcessing] = (new Date()).getTime() - tempTime + + return returnVal +} + +function main () { + var startTime = new Date().getTime() + var returnObj = {} + var parameters = {} + var tempTime + var errorOccurred = false + var data = {} + + app.clearAllErrors() + app.generalPreferences.pageNumbering = PageNumberingOptions.ABSOLUTE + app.linkingPreferences.checkLinksAtOpen = true + app.serverSettings.useErrorList = true + var previousUnit = app.scriptPreferences.measurementUnit + app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS + + try { + UTILS.InitiateLogging() + UTILS.Log('Initiating logging.') + + UTILS.CloseAllOpenDocuments() + + var input = app.scriptArgs.get('parameters') + var allParameters = JSON.parse(input) + + UTILS.Log('Parsed job input : ' + input) + parameters = allParameters.params + if (parameters === undefined) { + parameters = allParameters.input.params + } + + if (parameters === undefined || typeof parameters !== 'object' || Array.isArray(parameters)) { + UTILS.Log('No params found') + UTILS.RaiseException(Errors.MissingParams) + } + + UTILS.SetWorkingFolder(UTILS.GetStringFromObject(allParameters, KeyStrings.WorkingFolder)) + + var result + UTILS.Log('Processing Params') + tempTime = new Date().getTime() + result = ProcessParams(parameters, allParameters) + data[KeyStrings.ProcessedData] = result + + UTILS.AddAssetToBeUploaded(UTILS.logFilePath) + + data.warnings = warnings + + tempTime = new Date().getTime() + if (document && document.isValid) { + document.close() + } + + timingObject[KeyStrings.TimeDocumentClose] = (new Date()).getTime() - tempTime + UTILS.Log('End of try') + } catch (e) { + var tempObj = { + name: e.name, + message: e.message, + errorCode: e.number, + isCustom: e.isCustom, + line: e.line, + fileName: e.fileName + } + UTILS.Log('Exception occurred', tempObj) + errorOccurred = true + returnObj = UTILS.HandleError(tempObj) + } finally { + app.scriptPreferences.measurementUnit = previousUnit + UTILS.Log('In finally') + if (document && document.isValid) { + UTILS.Log('Closing document') + tempTime = new Date().getTime() + document.close() + timingObject[KeyStrings.TimeDocumentClose] = (new Date()).getTime() - tempTime + } + + var elapsedTime = (new Date()).getTime() - startTime + UTILS.Log('Time taken: ' + elapsedTime) + timingObject[KeyStrings.TimeOverall] = elapsedTime + UTILS.Log('Timing: ' + JSON.stringify(timingObject)) + data[KeyStrings.Timings] = timingObject + + if (!errorOccurred) { + UTILS.Log('Finally: No error') + returnObj = UTILS.GetSuccessReturnObj(data) + } + UTILS.Log('Final Result', JSON.stringify(returnObj)) + + UTILS.TerminateLogging() + app.clearAllErrors() + + return UTILS.GetFinalReturnPackage(returnObj) + } +} + +main() diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/utils.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/utils.jsx new file mode 100644 index 0000000..2974cda --- /dev/null +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/utils.jsx @@ -0,0 +1,702 @@ +/************************************************************************* + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2025 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + **************************************************************************/ +// utils.jsx + +// Utils functions which can be used across. +/* globals app, Errors, File, Folder, LinkStatus, SaveOptions */ + +// Globals. +var logFileObject + +// eslint-disable-next-line no-extend-native +Array.prototype.indexOf = function (item) { + var index = 0 + var length = this.length + for (; index < length; index++) { + if (this[index] === item) { + return index + } + } + return -1 +} + +// eslint-disable-next-line no-extend-native +if (typeof Array.isArray === 'undefined') { + Array.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]' + } +} + +// Create a UTILS object to store utility functions and variables. +var UTILS = {} + +// Initialize various properties of the UTILS object. +UTILS.Logs = [] // Stores log messages. +UTILS.workingFolder = '' // Path of the working folder. +UTILS.outputFolder = '' // Path of the output folder. +UTILS.assetsToBeUploaded = [] // List of assets that need to be uploaded. +UTILS.createSeparateLogFile = true // Flag to determine if a separate log file should be created. +UTILS.logFilePath = 'LogFile.txt' // File name for logging. + +// Converts the package in appropriate format, which can be returned from the capability. +UTILS.GetFinalReturnPackage = function (obj) { + UTILS.Log('Final package created') + return JSON.stringify(obj) +} + +// Creates and returns the package to be returned in case the job is successful. +UTILS.GetSuccessReturnObj = function (data) { + var obj = {} + obj.status = 'SUCCESS' + obj.assetsToBeUploaded = UTILS.assetsToBeUploaded // Attach the list of assets to be uploaded. + var dataURL = UTILS.WriteToFile(data, 'outputData') // Save data to file. + if (dataURL) { + obj.dataURL = dataURL + } + return obj +} + +// Creates and returns the package to be returned in case the job has failed. +UTILS.GetFailureReturnObj = function (errorCode, errorString, data) { + var obj = {} + obj.status = 'FAILURE' + obj.errorCode = errorCode // Attach the error code. + obj.errorString = errorString // Attach the error message. + obj.data = data // Include additional data if available. + return obj +} + +// Add an asset which is to be uploaded and sent back to the caller. +UTILS.AddAssetToBeUploaded = function (assetPath, data) { + var assetToBeUploaded = {} + assetToBeUploaded.path = assetPath + if (data !== undefined) { + assetToBeUploaded.data = data // Include additional asset data if provided. + } + UTILS.assetsToBeUploaded.push(assetToBeUploaded) +} + +// This handles errors in case an exception is raised. This results the capability returning error to the caller. +UTILS.HandleError = function (exception) { + var errorCode, errorString + UTILS.Log('Exception occurred: ' + JSON.stringify(exception)) + + // Handle specific exception cases + if (exception.message === 'open') { + exception.message = Errors.CannotOpenFileError.errorStrings[0] + exception.errorCode = Errors.CannotOpenFileError.errorCode + } + + errorString = 'Script error: ' + if (exception.isCustom === true) { + if (exception.message) { + errorString = errorString + exception.message + } + errorCode = exception.errorCode + } else { + errorCode = Errors.ProcessingErrorOccurred.errorCode + if (exception.message) { + errorString = errorString + Errors.ProcessingErrorOccurred.errorStrings[0] + ' ' + exception.message + } + if (exception.errorCode !== undefined) { + errorString = errorString + '\nInternal error code: ' + exception.errorCode + '.' + } + if (exception.line !== undefined) { + errorString = errorString + ' Line: ' + exception.line + '.' + } + if (exception.fileName !== undefined) { + errorString = errorString + ' FileName: ' + exception.fileName + '.' + } + UTILS.Log('Processing error occurred. ' + errorString) + } + + return UTILS.GetFailureReturnObj(errorCode, errorString) +} + +// This is used to raise an exception with all the required details. +// NOTE: This takes variable list of argument and then try to fill in the details in errorObj.errorStrings +UTILS.RaiseException = function (errorObj) { + UTILS.Log('RaiseException()') + var numMessageParameters = arguments.length + UTILS.Log('', 'numMessageParameters: ' + numMessageParameters) + var numErrorStrings = errorObj.errorStrings.length + UTILS.Log('', 'numErrorStrings: ' + numErrorStrings) + var numIterations = (numMessageParameters > numErrorStrings) ? numErrorStrings : numMessageParameters + var errorMessage = errorObj.errorStrings[0] + UTILS.Log('', 'Default errorMessage: ' + errorMessage) + + // Construct the detailed error message using available parameters. + for (var itr = 1; itr < numIterations; itr++) { + var parameter = arguments[itr] + if (parameter !== undefined && parameter !== '') { + var parameterMessage = errorObj.errorStrings[itr] + errorMessage = errorMessage + ' ' + parameterMessage.replace('^1', parameter) + UTILS.Log('', 'Appended errorMessage: ' + errorMessage) + } + } + + // Throw an exception object with custom details. + var exceptionObj = { + number: errorObj.errorCode, + isCustom: true, + message: errorMessage + } + + throw exceptionObj +} + +// This will update out-of-date links in a document. +UTILS.UpdateDocumentLinks = function (document) { + var links = document.links + var numLinks = links.length + var linkItr, link, uri + var outOfDateLinks = [] + + UTILS.Log('Number of links: ' + numLinks) + for (linkItr = 0; linkItr < numLinks; linkItr++) { + try { + link = links[linkItr] + uri = link.linkResourceURI + UTILS.Log(linkItr + ': URI: ' + uri) + if (link.status === LinkStatus.LINK_OUT_OF_DATE) { + outOfDateLinks.push(link.id) + } + } catch (err) { + UTILS.Log('Link status unknown : ' + err) + } + } + + // Update all out-of-date links. + numLinks = outOfDateLinks.length + for (linkItr = 0; linkItr < numLinks; linkItr++) { + link = document.links.itemByID(outOfDateLinks[linkItr]) + if (link.isValid) { + link.update() + } + } + + // Recompose the document after updating links. + var composeStartTime = new Date().getTime() + document.recompose() + var composedTime = (new Date()).getTime() - composeStartTime + UTILS.Log('document.recompose Time: ' + composedTime) +} + +// This Embeds all the links in the document. +UTILS.EmbedDocumentLinks = function (document) { + var links = document.links + var numLinks = links.length + var linkItr, link + + for (linkItr = 0; linkItr < numLinks; linkItr++) { + try { + link = links[linkItr] + if (link.status === LinkStatus.NORMAL) { + link.unlink() + } + } catch (err) { + UTILS.Log('Unable to embed link: status is unknown : ' + err) + } + } +} + +// Trims leading and trailing spaces from a string. +UTILS.Trim = function (val) { + return val.replace(/^\s+|\s+$/gm, '') +} + +// Removes all the spaces from a string. +UTILS.RemoveAllSpaces = function (val) { + return val.replace(/\s/g, '') +} + +// Converts a value to integer. Exception is thrown if the conversion fails. +UTILS.GetBoolean = function (val) { + if (val === 'true' || val === true) { + return true + } else if (val === 'false' || val === false) { + return false + } + + // throw exception. + UTILS.Log('GetBoolean() val: ' + val) + UTILS.RaiseException(Errors.ParsingBoolError, val) +} + +// Gets a boolean from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the conversion of the provided value fails. +UTILS.GetBooleanFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetBooleanFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + if (val === 'true' || val === true) { + return true + } else if (val === 'false' || val === false) { + return false + } + + UTILS.Log('GetBooleanFromObject() val: ' + val) + UTILS.RaiseException(Errors.ParsingBoolError, val, key) + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Converts a value to integer. Exception is thrown if the conversion fails. +UTILS.GetInteger = function (val) { + var returnVal = parseInt(val) + if (!isNaN(returnVal)) { + return returnVal + } + + UTILS.Log('GetInteger() val: ' + val) + UTILS.RaiseException(Errors.ParsingIntError, val) +} + +// Gets a integer from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the conversion of the provided value fails. +UTILS.GetIntegerFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetIntegerFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + var returnVal = parseInt(val) + if (!isNaN(returnVal)) { + return returnVal + } + + UTILS.Log('GetIntegerFromObject() val: ' + val) + UTILS.RaiseException(Errors.ParsingIntError, val, key) + } else if (defaultVal === undefined) { + UTILS.Log('Missing key: ' + key) + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Converts a value to float. Exception is thrown if the conversion fails. +UTILS.GetFloat = function (val) { + var returnVal = parseFloat(val) + if (!isNaN(returnVal)) { + return returnVal + } + + UTILS.Log('GetFloat() val: ' + val) + UTILS.RaiseException(Errors.ParsingFloatError, val) +} + +// Gets a float from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the conversion of the provided value fails. +UTILS.GetFloatFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetFloatFromObject ' + key) + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + var returnVal = parseFloat(val) + if (!isNaN(returnVal)) { + return returnVal + } + + UTILS.Log('GetFloatFromObject() val: ' + val) + UTILS.RaiseException(Errors.ParsingFloatError, val, key) + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Validates a value to be one of the enum definitions. +UTILS.GetEnum = function (enumDef, val) { + try { + val = UTILS.Trim(val) + return UTILS.GetValueFromObject(enumDef, val) + } catch (e) { + UTILS.RaiseException(Errors.EnumError, val) + } +} + +// Gets a enum value from an 'object' defined with 'key' and after comparing against an enum definition. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the value is not present in enum definition. +UTILS.GetEnumFromObject = function (enumDef, object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetEnumFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + try { + val = UTILS.Trim(val) + if (Array.isArray(enumDef) === false) { + return UTILS.GetValueFromObject(enumDef, val, ignoreCase) + } else { + if (enumDef.indexOf(val) >= 0) { + return val + } + } + } catch (e) { + UTILS.Log('GetEnumFromObject() val: ' + val) + UTILS.RaiseException(Errors.EnumError, val, key) + } + + UTILS.Log('GetEnumFromObject() val: ' + val) + UTILS.RaiseException(Errors.EnumError, val, key) + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Gets a string from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the value found is not string. +UTILS.GetStringFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetStringFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + if (typeof val !== 'string') { + UTILS.RaiseException(Errors.ParsingStringError, val, key) + } else { + return val + } + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Gets an array from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the value found is not an array. +UTILS.GetArrayFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetArrayFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + if (Array.isArray(val) === false) { + UTILS.RaiseException(Errors.ArrayExpected, key) + } else { + return val + } + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Gets an object from an 'object' defined with 'key'. +// A default value can be passed via 'defaultVal' which will be used if the key is missing. The case of key can be ignored via 'ignoreCase'. +// In case, key is missing and a default value is not provided, an exception will be thrown. Exception will also be thrown if the value found is not an object. +UTILS.GetObjectFromObject = function (object, key, defaultVal, ignoreCase) { + var val + UTILS.Log('GetObjectFromObject ' + key) + + if (UTILS.IsKeyPresent(object, key)) { + val = UTILS.GetValueFromObject(object, key, ignoreCase) + if (typeof val !== 'object' || Array.isArray(val)) { + UTILS.RaiseException(Errors.ObjectExpected, key) + } else { + return val + } + } else if (defaultVal === undefined) { + UTILS.RaiseException(Errors.MissingKey, key) + } else { + // default value provided. + return defaultVal + } +} + +// Gets the value from an 'object' defined with 'key'. +// The case of key can be ignored via 'ignoreCase'. In case, key is missing an exception will be thrown. +UTILS.GetValueFromObject = function (object, key, ignoreCase) { + UTILS.Log('GetValueFromObject ' + key) + + if (ignoreCase === true) { + var objectKey + for (objectKey in object) { + if (Object.prototype.hasOwnProperty.call(object, objectKey)) { + if (objectKey.toLowerCase() === key.toLowerCase()) { + return object[objectKey] + } + } + } + } else if (Object.prototype.hasOwnProperty.call(object, key)) { + return object[key] + } + + UTILS.RaiseException(Errors.MissingKey, key) +} + +// Checks whether a key is present in the object or not. +UTILS.IsKeyPresent = function (object, key) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + return true + } + return false +} + +// Sets the working folder to be used across the capability. +UTILS.SetWorkingFolder = function (workingFolder) { + workingFolder = workingFolder.replace(/\//g, '\\') + if (workingFolder.charAt(workingFolder.length - 1) !== '\\') { + workingFolder = workingFolder + '\\' + } + UTILS.workingFolder = workingFolder + UTILS.OpenLogFileHandle() +} + +// Gets the path of the directory for a given path. +UTILS.GetDirPath = function (path) { + path = path.replace(/\//g, '\\') + var indexOfSlash = path && path.lastIndexOf('\\') + if (indexOfSlash >= 0) { + return path.substr(0, indexOfSlash) + } + return '' +} + +// Gets the path of the directory for a given path. If 'withExtension is true the file name consists of the file extension and not otherwise. +UTILS.GetFileName = function (path, withExtension) { + var fileName + var indexOfSlash = path.lastIndexOf('\\') + 1 + if (indexOfSlash >= 1) { + fileName = path.substr(indexOfSlash) + if (withExtension === false) { + var indexOfDot = path.lastIndexOf('.') + fileName = path.substr(indexOfSlash, indexOfDot - indexOfSlash) + } + } else { + fileName = path + } + return fileName +} + +// Get relative path to the working directory for a path. +UTILS.GetRelativePath = function (path) { + var index = path.indexOf(UTILS.workingFolder) + if (index === 0) { + return path.substr(UTILS.workingFolder.length) + } + return path +} + +// Get relative path to the working directory for a path. This returns in unix notation. +UTILS.GetRelativeReturnPath = function (path) { + var index = path.indexOf(UTILS.workingFolder) + if (index === 0) { + var tempPath = path.substr(UTILS.workingFolder.length) + tempPath = tempPath.replace(/\\/g, '/') + return tempPath + } +} + +// Get the full path from a relative and a base path. In absence of base path, working directory is used. +UTILS.GetFullPath = function (relativePath, basePath) { + var origRelativePath = relativePath + if (relativePath.indexOf('..') !== -1 || relativePath.indexOf('/') === 0) { + this.RaiseException(Errors.InCorrectRelativePath, origRelativePath) + } + relativePath = relativePath.replace(/\//g, '\\') + if (relativePath.indexOf('\\.\\') !== -1) { + this.RaiseException(Errors.InCorrectRelativePath, origRelativePath) + } + + if (basePath === undefined) { + basePath = UTILS.workingFolder + } else { + basePath = basePath.replace(/\//g, '\\') + if (basePath.charAt(basePath.length - 1) !== '\\') { + basePath = basePath + '\\' + } + } + + if (relativePath.charAt(0) === '.') { + relativePath = relativePath.slice(1) + } + if (relativePath.charAt(0) === '\\') { + relativePath = relativePath.slice(1) + } + + var fullPath = basePath + relativePath + return fullPath +} + +// Get a unique name. +UTILS.GetUniqueName = function () { + return Math.random().toString().substr(2, 6) +} + +// Creates a directory for the path provided. +UTILS.CreateDirectory = function (path) { + var outputFolder = Folder(path) + return outputFolder.create() +} + +// Creates a directory for the outputs on the basis of relative path passed. In case nothing is passed a random directory is created in working folder. +UTILS.GetOutputDirectory = function (outputFolderPath) { + var outputPath = '' + var created = false + if (outputFolderPath) { + outputPath = UTILS.GetFullPath(outputFolderPath) + created = UTILS.CreateDirectory(outputPath) + var outputFolder = Folder(outputPath) + if (outputFolder.exists === false) { + UTILS.RaiseException(Errors.OutputDirError) + } + } else { + var tempName = '' + do { + tempName = 'tmp' + UTILS.GetUniqueName() + outputPath = UTILS.GetFullPath(tempName) + created = UTILS.CreateDirectory(outputPath) + } while (!created) + } + UTILS.outputFolder = outputPath + return outputPath +} + +// Setup to do the logging. +UTILS.InitiateLogging = function () { + UTILS.Logs = [] +} + + +// Opens a log file handle if separate logging is enabled. +UTILS.OpenLogFileHandle = function () { + if (UTILS.createSeparateLogFile === true) { + logFileObject = new File(UTILS.GetFullPath(UTILS.logFilePath)) + var exists = false + + // Check if the log file already exists + if (logFileObject.open('read')) { + exists = true + } + UTILS.Log('Creating log file at ' + UTILS.GetFullPath(UTILS.logFilePath)) + + // Open the log file in append mode if it exists, otherwise create a new file + if (exists) { + logFileObject.close() + logFileObject.open('append') + } else { + logFileObject.open('write') + } + } +} + +// Ends logging by closing the log file handle if separate logging is enabled +UTILS.TerminateLogging = function () { + if (UTILS.createSeparateLogFile === true) { + logFileObject.close() + } +} + +// This logs any provided information +UTILS.Log = function (log) { + var logText + + // Convert non-string logs to JSON format + if (log === undefined) { + logText = '' + } else if (typeof primaryLog !== 'string') { + logText = JSON.stringify(log) + } else { + logText = log + } + + if (UTILS.createSeparateLogFile === true) { + + // Write to file if log file object exists + if (logFileObject) { + if (UTILS.Logs.length > 0) { + for (var itr = 0; itr < UTILS.Logs.length; itr++) { + logFileObject.writeln(UTILS.Logs[itr]) + } + UTILS.Logs = [] + } + logFileObject.writeln(logText) + } else { + UTILS.Logs.push(logText) + } + } else { + UTILS.Logs.push(logText) + } +} + +// Writes data to a uniquely named JSON file +UTILS.WriteToFile = function (data, fileName) { + var fileURL, newFile + var exists = false + var suffix = '' + var counter = 1 + + // Generate a unique filename if none is provided + if (fileName === undefined) { + fileName = UTILS.GetUniqueName() + } + + // Ensure the file does not already exist + do { + fileURL = UTILS.GetFullPath(fileName + suffix + '.json') + newFile = File(fileURL) + if (newFile.open('read')) { + exists = true + suffix = counter++ + newFile.close() + } else { + exists = false + } + } while (exists) + + // Write data to the file + newFile.encoding = 'UTF8' + newFile.open('write') + if (newFile.write(JSON.stringify(data))) { + UTILS.Log('Data was successfully written') + newFile.close() + return (fileName + suffix + '.json') + } else { + UTILS.Log('Data write failed') + newFile.close() + } +} + +// Closes all open documents in the application. +UTILS.CloseAllOpenDocuments = function () { + UTILS.Log('Closing all the documents') + app.documents.everyItem().close(SaveOptions.NO) +} diff --git a/samples.md b/samples.md index e51e687..949e6e7 100644 --- a/samples.md +++ b/samples.md @@ -4,6 +4,8 @@ Just remember you will need to have the assets stored in one of the accepted ext ## Sample capability bundle Sample capability bundles can be found at [SampleScripts](SampleScripts/). + +**Custom script starter:** [customCapabilityTemplate](SampleScripts/ExtendScript/customCapabilityTemplate/) — boilerplate for logging, working folder, errors, and return JSON; implement logic in `capabilityLogic.jsx` only. A manifest.json must be bundled inside the capability zip. A typical manifest would look like: ```json { From 705793cd6ce3a7769003bb7f4f0fb05d18296c51 Mon Sep 17 00:00:00 2001 From: Shreya Gupta Date: Wed, 18 Mar 2026 18:21:39 +0530 Subject: [PATCH 2/2] update --- .../ExtendScript/customCapabilityTemplate/capabilityLogic.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx b/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx index 04fa3ad..c1a5bdc 100644 --- a/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx +++ b/SampleScripts/ExtendScript/customCapabilityTemplate/capabilityLogic.jsx @@ -40,9 +40,7 @@ CAPABILITY.run = function (document, parameters, allParameters, returnVal) { // Example (uncomment and adjust for your case): // var outputPath = UTILS.GetStringFromObject(parameters, 'outputPath') // outputPath = UTILS.GetFullPath(outputPath) - UTILS.Log('outputPathYayyyyy: ') - app.open(File("/input.inddc")) - UTILS.Log('document openedYayyyyy') + // app.open(File("outputPath")) // UTILS.AddAssetToBeUploaded(UTILS.GetRelativeReturnPath(outputPath)) // returnVal.outputPath = outputPath