And one last addition, for anyone who is interested. I've made huge improvements under the hood, including quite good xml parsing (thanks to Peter Sirka's nifty little function—see script comments) and displays the differences with much better context. The underlying function listDifferences(items,options) can now handle two or more files, and the formatting is separated.
- Mark
/**
* Shows object style override differences
* for the selected page item.
* m1b
* 2022-07-18
* @discussion https://community.adobe.com/t5/indesign-discussions/how-to-find-whether-my-textframe-have-overriddenobjectstyle-or-not-in-my-document-using-javascript/m-p/13071120
*
* Important note:
* This is more strict than Indesign's own
* object-style-overridden criteria, so
* sometimes an object may show differences
* while not showing the (+) symbol in the
* Object Style panel.
*/
function main() {
var doc = app.activeDocument,
item = doc.selection[0];
if (item.isValid)
showObjectStyleOverrides(item);
}
/**
* Determines if item has ObjectStyle overrides
* and displays a list of the overriden properties.
*
* m1b
* 2022-07-18
* @discussion https://community.adobe.com/t5/indesign-discussions/how-to-find-whether-my-textframe-have-overriddenobjectstyle-or-not-in-my-document-using-javascript/m-p/13071120
*
* Note: this function is MORE STRICT
* than Indesign's normal determination
* of overridden-ness.
*
* {PageItem} item - an Indesign page item.
* {Boolean} - if true, then object has overrides.
*/
function showObjectStyleOverrides(item) {
if (
item == undefined
|| !item.isValid
|| typeof item.exportFile !== 'function'
)
return;
// make a duplicate item
dup = item.duplicate();
var itemSnippet = saveSnippet(dup, 'item.xml');
// with no objectStyle overrides
dup.clearObjectStyleOverrides();
// and make a snippet of that
dupSnippet = saveSnippet(dup, 'dup.xml');
// clean up
dup.remove();
// set up arguments for listDifferences
var comparisons = [
{ label: 'PageItem', file: itemSnippet },
{ label: 'Object Style', file: dupSnippet }
];
// in snippet XML, we don't care about these
var options = { ignoreThese: /x:xmpmeta/ };
// collect differences between the two
var differences = listDifferences(comparisons, options);
// display differences
if (differences.length > 0)
alerter(differences.length + ' Object Style Overrides', formatDifferences(differences), 1000, 500);
else
alert('This item has no Object Style overrides.');
}
/**
* Compares XML text or XML text files and
* returns a text summary of differences.
* m1b
* 2022-07-18
*
*
* listDifferences(
* [
* { label: 'PageItem', file: itemSnippetFile },
* { label: 'Object Style', xml: dupSnippetText }
* ], {
* onlyThese: /text/i,
* ignoreThese: /color/i
* }
* );
*
* {Array} items - an array of items to compare
* {Object} [options] - object with options
* {String|RegExp} [options.onlyThese] - test to choose keys
* {String|RegExp} [options.ignoreThese] - test to exclude keys
* {String} - the formatted different properties
*/
function listDifferences(items, options) {
var differences = [],
objs = [],
labels = [],
lines = [],
keys = [],
// constants
empty = '-- empty --';
// initialise the items
for (var i = 0; i < items.length; i++) {
var item = items[i];
// label
if (item.label == undefined)
item.label = String.fromCharCode(65 + k);
// file
if (
item.file != undefined
&& item.file.exists
)
item.obj = parseXML(readFile(item.file));
// string
if (typeof (item.xml) === 'string')
item.obj = parseXML(item.xml);
// xml
if (typeof (item.xml) === 'xml')
item.obj = parseXML(item.xml.toString());
// xml text
if (item.obj == undefined)
return ['Error: item "' + item.label + '" had no usable XML.'];
objs[i] = item.obj;
labels[i] = item.label;
lines[item.label] = [];
var itemKeys = item.obj.reflect.properties;
// collect the item's keys
for (var k = 0; k < itemKeys.length; k++)
if (item.obj.hasOwnProperty(itemKeys[k]))
keys.push(String(itemKeys[k]));
}
// combine keys from all items
var keys = uniquify(keys);
// expand the key/values
for (var k = 0; k < keys.length; k++) {
// current key to compare
var key = keys[k];
// add lines by expanding objs recursively
for (var o = 0; o < objs.length; o++)
lines[labels[o]] = lines[labels[o]].concat(expandLines(key, objs[o][key]));
}
// make an object that has an object for *every* key
// and store values in that object, using the item's label
var keys = [],
comparator = [];
for (var o = 0; o < objs.length; o++) {
var label = labels[o];
linesLoop:
for (var l = 0; l < lines[label].length; l++) {
var linesForKey = lines[label][l],
key = linesForKey[0],
value = linesForKey[1];
if (
options.ignoreThese != undefined
&& options.ignoreThese.test(key)
)
// user doesn't want this key
continue linesLoop;
if (
options.onlyThese != undefined
&& !options.onlyThese.test(key)
)
// not what user asked for
continue linesLoop;
// add to new master keys
keys.push(key);
if (comparator[key] == undefined)
// add line's key as an empty object
comparator[key] = [];
// comparator[key][label] = [];
// add value, using label as key
comparator[key][label] = value;
}
}
// tidy up new keys
var keys = uniquify(keys);
// check if values are different
// and add them to differences
for (var k = 0; k < keys.length; k++) {
var key = keys[k],
isDifferent = false,
// the value we check against
currentValue = null,
// will hold array for each object's line
linesForKey = [];
for (var o = 0; o < objs.length; o++) {
var label = labels[o],
v;
// add label for this object
if (
comparator[key] === undefined
|| !comparator[key].hasOwnProperty(label)
|| comparator[key][label] == undefined
|| comparator[key][label] == 'undefined'
)
v = empty;
else
v = comparator[key][label];
// store this line in case we need it
linesForKey.push([label, key, v]);
// initialise current value
if (currentValue === null)
currentValue = v;
// or check if values are different
else if (currentValue !== v)
isDifferent = true;
}
if (isDifferent === true)
differences.push(linesForKey);
}
return differences;
}
/**
* Format the output of listDifferences function.
* {Array} differences - listDifferences() output
*/
function formatDifferences(differences) {
var formatted = [],
labelWidth = 0,
// regex to tidy strings
firstTwoElements = /^([^\.]+\.[^\.]+\.)/,
linefeeds = /[\r\n]/g,
emptyBrackets = /\[\]/g,
tabs = /\t/g;
// calculate label width
for (var o = 0; o < differences[0].length; o++)
if (labelWidth < differences[0][o][0].length)
labelWidth = differences[0][o][0].length;
for (var d = 0; d < differences.length; d++) {
var linesForKey = [];
for (var o = 0; o < differences[d].length; o++)
linesForKey[o] = formatDifference.apply(null, differences[d][o]);
formatted.push(linesForKey.join('\n'));
}
return formatted.join('\n\n');
// helper functions
function formatDifference() { var a = arguments; return pad(a[0], labelWidth + 2) + '\t' + tidyKey(a[1]) + ': ' + tidyValue(a[2]) };
function tidyKey(key) { return key.replace(firstTwoElements, '').replace(emptyBrackets, '') };
function tidyValue(value) { return value.replace(linefeeds, '\\n').replace(tabs, '\\t') };
}
/**
* Convert a simple key/value object into
* array of [[key, value], [key, value], ...]
* if value is also an object, it will
* recursively convert that also.
* {String} key - the starting key
* {String|Array|Object} obj - the starting value
* {Array} - [[key, value], [key, value], ...]
*/
function expandLines(key, obj, i) {
key = String(key);
var lines = [];
if (obj == undefined)
lines.push([key, 'undefined']);
else if (obj.constructor.name == 'String')
lines.push([key, obj]);
else if (obj.constructor.name == 'Array')
for (var i = 0; i < obj.length; i++)
lines = lines.concat(expandLines(key + '[' + i + ']', obj[i], i));
else if (obj.constructor.name == 'Object')
for (var k in obj)
if (obj.hasOwnProperty(k))
lines = lines.concat(expandLines(key + '.' + k, obj[k], i));
return lines;
}
/**
* Reads a file contents.
* {File} file - the file to read.
* {String}
*/
function readFile(file) {
if (
file == undefined
|| !file.exists
)
return;
file.open('r');
var text = file.read();
file.close();
return text;
}
/**
* Exports item as an Indesign snippet (XML) file.
* m1b
* {PageItem} item - an Indesign page item.
* {String} name - the snippet file name (include extension).
*/
function saveSnippet(item, name) {
// put in temp folder
var snippetFile = File(Folder.temp.fsName + '/' + name);
// export as indesign snippet
item.exportFile(ExportFormat.INDESIGN_SNIPPET, snippetFile, false);
if (snippetFile.exists)
return snippetFile;
}
/**
* Show text in a multiline edit text field.
* {String} title - the title of the alerter
* {String|Array} obj - the text content (array of strings will work)
* {Number} [width] - the dialog width
* {Number} [height] - the dialog height
*/
function alerter(title, obj, width, height) {
width = width || -1;
height = height || -1;
if (obj instanceof Array)
obj = obj.join("\r");
var w = new Window("dialog", title),
et = w.add("edittext", undefined, obj, { multiline: true, scrolling: true });
et.maximumSize.height = w.maximumSize.height - 100;
et.minimumSize.height = 350;
et.minimumSize.width = 350;
et.preferredSize = [width, height];
w.add("button", undefined, "Close", { name: "ok", alignment: ['right', 'center'] });
w.show();
}
function uniquify(arr) {
var seen = {},
out = [],
j = 0;
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
/**
* Simple XML parser
* Peter Sirka
* @url https://gist.github.com/petersirka/9e79b1d43cf6e579fc62
* {String} xml
* {Object}
*/
function parseXML(xml) {
var beg = -1;
var end = 0;
var tmp = 0;
var current = [];
var obj = {};
var from = -1;
while (true) {
beg = xml.indexOf('<', beg + 1);
if (beg === -1)
break;
end = xml.indexOf('>', beg + 1);
if (end === -1)
break;
var el = xml.substring(beg, end + 1);
var c = el[1];
if (c === '?' || c === '/') {
var o = current.pop();
if (from === -1 || o !== el.substring(2, el.length - 1))
continue;
var path = current.join('.') + '.' + o;
var value = xml.substring(from, beg);
if (typeof (obj[path]) === 'undefined')
obj[path] = value;
else if (obj[path] instanceof Array)
obj[path].push(value);
else
obj[path] = [obj[path], value];
from = -1;
continue;
}
tmp = el.indexOf(' ');
var hasAttributes = true;
if (tmp === -1) {
tmp = el.length - 1;
hasAttributes = false;
}
from = beg + el.length;
var isSingle = el[el.length - 2] === '/';
var name = el.substring(1, tmp);
if (!isSingle)
current.push(name);
if (!hasAttributes)
continue;
var match = el.match(/\w+\=\".*?\"/g);
if (match === null)
continue;
var attr = {};
var length = match.length;
for (var i = 0; i < length; i++) {
var index = match[i].indexOf('"');
attr[match[i].substring(0, index - 1)] = match[i].substring(index + 1, match[i].length - 1);
}
obj[current.join('.') + (isSingle ? '.' + name : '') + '[]'] = attr;
}
return obj;
};
/**
* Pad a string.
* m1b
* {String} str - the text to pad.
* {Number} width - number of characters to pad to.
* {String} [paddingChar] - the character to pad with.
* {Boolean} [rightAligned] - if true, will right align text.
*/
function pad(str, width, paddingChar, rightAligned) {
width = Number(width);
paddingChar = paddingChar || ' ';
var padding = '';
while (width > padding.length)
padding += paddingChar;
if (rightAligned)
str = (padding + str).slice(-width);
else
str = (str + padding).slice(0, width);
return str
}
// polyfill
if (!String.prototype.indexOf) {
String.prototype.indexOf = function (obj) {
var i;
for (i = 0; i < this.length; i++) if (this[i] == obj) return i;
return -1;
}
}
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Show ObjectStyle Overrides');
Example 1: First I alt-click on the object style in the Object Style panel (to clear overrides) then I change the column number of the text box to 2 column (the object style is 1 column).

Example 2: Again I start by alt-clicking on the object style in the Object Style panel (to clear overrides) then I move the rectangle's graphic.

- Mark