Skip to content

Instantly share code, notes, and snippets.

@indiscripts
Last active July 6, 2023 19:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indiscripts/c2742505e0e18cf7669d0c9741197d9a to your computer and use it in GitHub Desktop.
Save indiscripts/c2742505e0e18cf7669d0c9741197d9a to your computer and use it in GitHub Desktop.
Returns a item-to-subitem path strictly based on `index` access
// InDesign script (function) -- DRAFT [230706]
// Cf https://community.adobe.com/t5/indesign-discussions/indesign-script-keep-reference-of-nested-pageitem-in-group-while-duplicating-it/td-p/13917002
function indexPath(/*PageItem*/child,/*PageItem*/root,/*bool=0*/AS_SPEC, a,t,p,rk,tk,pk,i,o,x,s)
//----------------------------------
// Assuming that `root` is a parent of `child` in the DOM hierarchy, returns
// a root-to-child path in the form of a `.<collection>[<index>]` sequence,
// each element being introduced by '.', e.g. `.textFrames[1].ovals[2].textFrames[0]`
// The result is then that `child === root.textFrames[1].ovals[2].textFrames[0]`
// ---
// Optionally, set `AS_SPEC` to true to get a "partial specifier" instead, that is,
// a string in the form `/text-frame[1]/oval[2]/text-frame[0]`. This option makes
// it easier to rebuild a clean DOM specifier relative to the `root` object, using
// `mySpec = root.toSpecifier() + indexPath(child, root, true);`
// so that `resolve(mySpec)` will return the `child` object.
// ---
// => str [OK] | false|ERROR [KO]
{
// const RE_DIGS = /\d+/g; // not used!
if( !(child && child.isValid && 'Document'!=(tk=child.constructor.name)) ) return false;
if( !(root && root.isValid && 'Document'!=(rk=root.constructor.name)) ) return false;
for( a=[], t=child.getElements()[0] ; 'Document' != (pk=(p=t.parent).constructor.name) ; tk=pk, t=p )
{
// Translate singular class to collection property (roughly!)
// E.g. TextFrame -> textFrames ; Rectangle -> rectangles ; MultiStateObject -> multiStateObjects ; etc
// ---
tk = tk.charAt(0).toLowerCase() + tk.slice(1) + 's';
if( p.hasOwnProperty('parentTextFrames') )
{
// Current parent is a text object (Character) so `t` is anchored.
// ---
if( 1 === (o=p.parentTextFrames).length )
{
// Regular case -> Treat the parent frame as the actual 'parent'.
p = o[0];
pk = p.constructor.name; // Might be TextFrame or TextPath
i = -1; // Flag -> compute the index
}
else
{
// 'Ghost' anchor -> May we use the parent story as a fallback?
throw "Ghost anchor!";
}
}
else
{
// Current parent is *probably* a page item of some kind.
// ---
i = t.hasOwnProperty('index') && t.index;
if( false===i ) throw ("The .index property is unavailable in " + t);
}
if( !p.hasOwnProperty(tk) ) throw ("Unknown property: " + tk);
if( 0 > i )
{
// The anchored object has no given index that we could trust,
// let's try to find it 'manually'.
// ---
s = '|' + p[tk].everyItem().id.join('|') + '|';
x = s.indexOf('|' + t.id + '|');
i = 0 <= x && x && s.slice(1,x).split('|').length;
( (o=false!==i&&p[tk][i]).isValid && t===o.getElements()[0] ) ||
( o=false );
}
else
{
// Tests have shown that *in principle* t.index is relative
// to `tk`, but it could be pageItems-relative in weird cases...
// ---
( (o=p[tk][i]).isValid && t===o.getElements()[0] ) ||
( 'pageItems'!=tk && (o=p[tk='pageItems'][i]).isValid && t===o.getElements()[0] ) ||
( o=false );
}
if( !o ) throw ("Cannot recover " + t + " from " + p);
a.push( AS_SPEC ? p[tk][i].toSpecifier().split('/').pop() : (tk+'['+i+']') );
// Note: pk==rk is a precondition, it should run a bit faster than p===root.
// ---
if( rk==pk && p===root )
{
// e.g `.textFrames[1].ovals[2].textFrames[0]` if !(AS_SPEC)
// or `/text-frame[1]/oval[2]/text-frame[0]` if AS_SPEC
s = AS_SPEC ? '/' : '.';
return s + a.reverse().join(s);
}
}
return false;
};
// Test me.
var root = app.activeDocument.groups[0]; // Take a group (other PageItem subclass would work as well)
var child = app.selection[0]; // The selection must be a PageItem, somewhere inside that group
var path = indexPath( child, root, true ); // The path looks like "/text-frame[1]/oval[0]/text-frame[0]"
var spec = root.toSpecifier() + path; // Append the path to the root specifier.
var t = resolve(spec);
alert( t===child ); // Should be true.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment