Answered
Hi @dublove try this script. - Mark

/**
* @file Link Fake Footnotes with Fake Sources.js
*
* Create real footnotes (source reference and actual footnote)
* from fake footnote sources and fake footnotes.
*
* Usage:
* 1. place cursor into story
* 2. run script.
*
* Requirements:
* - must have fake sources styled with a character style (see settings).
* - must have fake footnotes styled with a paragraph style (see settings).
*
* @author m1b
* @version 2025-07-25
* @discussion https://community.adobe.com/t5/indesign-discussions/can-it-automatically-establish-footnote-relationships-between-static-footnote-text-and-the-main-text/m-p/15429016
*/
function main() {
var settings = {
fakeSourceCharacterStyleName: 'Source',
fakeSourceParagraphStyleName: 'myfooter',
matchFakeSourceNumberGrep: '\\[\\d+\\]',
matchFakeFootnoteNumberRegexp: /^\[\d+\]\s*/,
};
var doc = app.activeDocument;
if (
0 === doc.selection.length
|| 'InsertionPoint' !== doc.selection[0].constructor.name
)
return alert('Please place your insertion point into a story and try again.');
// get the styles
var sourceStyle = getThing(doc.allCharacterStyles, 'name', settings.fakeSourceCharacterStyleName);
if (!sourceStyle)
return alert('Did not find any character style "' + settings.fakeSourceCharacterStyleName + '".');
var footerStyle = getThing(doc.allParagraphStyles, 'name', settings.fakeSourceParagraphStyleName);
if (!footerStyle)
return alert('Did not find any paragraph style "' + settings.fakeSourceParagraphStyleName + '".');
// document footnote options (see https://www.indesignjs.de/extendscriptAPI/indesign-latest/index.html#FootnoteOption.html)
doc.footnoteOptions.properties = {
footnoteTextStyle: footerStyle,
markerPositioning: FootnoteMarkerPositioning.NORMAL_MARKER,
prefix: '[',
separatorText: '\t',
showPrefixSuffix: FootnotePrefixSuffix.PREFIX_SUFFIX_BOTH, // or FootnotePrefixSuffix.PREFIX_SUFFIX_REFERENCE,
suffix: ']',
};
// collect the fake footnote sources
var fakeSources = getTexts({
items: doc.selection[0],
findGrepPreferences: { findWhat: settings.matchFakeSourceNumberGrep, appliedCharacterStyle: sourceStyle },
});
if (0 === fakeSources.length)
return alert('Did not find any fake footnote sources styled with "' + settings.fakeSourceCharacterStyleName + '".');
// collect the fake footnotes
var fakeFootnotes = getTexts({
items: doc.selection[0],
findGrepPreferences: { findWhat: '^.*(?:\\r?|$)', appliedParagraphStyle: footerStyle },
});
if (0 === fakeFootnotes.length)
return alert('Did not find any fake footnotes styled with "' + settings.fakeSourceParagraphStyleName + '".');
var len = Math.min(fakeSources.length, fakeFootnotes.length);
var story = fakeSources[0].parentStory;
// create a temporary text frame for the fake footnotes
var tempFrame = story.textContainers[0].parentPage.textFrames.add({ contents: '\r' });
var tempStory = tempFrame.texts[0].parentStory;
// move the fake footnotes to the temporary frame
for (var i = len - 1; i >= 0; i--)
fakeFootnotes[i].move(LocationOptions.AT_BEGINNING, tempStory);
for (var i = len - 1, footnote, fakeSource, fakeFootnote, index, start, end; i >= 0; i--) {
fakeSource = fakeSources[i];
fakeFootnote = tempStory.paragraphs[i];
index = fakeSource.index;
fakeSource.contents = '';
// make a new footnote
footnote = story.footnotes.add(
LocationOptions.AFTER,
fakeSource.parentStory.insertionPoints[index]
);
// identify the start and end of the footnote's actual text, after the number and before carriage return
start = (fakeFootnote.contents.match(settings.matchFakeFootnoteNumberRegexp) || [[]])[0].length;
end = ('\r' === fakeFootnote.characters.lastItem().contents) ? fakeFootnote.contents.length - 2 : fakeFootnote.contents.length - 1;
// add the text from the fake footnote to the real footnote
fakeFootnote.characters.itemByRange(start, end).move(LocationOptions.AT_END, footnote.texts[0]);
}
// clean up
tempFrame.remove();
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Link Fake Footnotes');
/**
* Returns texts found in `items`.
*
* Note: this is intended to use with
* findGrep - it won't work without
* a `findGrepPreferences` object.
* ----------------------------------------------
* Example - styled paragraphs of document:
*
* var texts = getTexts({
* items: doc,
* findGrepPreferences: {
* appliedParagraphStyle: 'My Style',
* },
* });
* ----------------------------------------------
* @author m1b
* @version 2024-04-24
* @param {Object} options
* @param {*} options.items - the DOM objects to extract text from, eg. Document or Document.selection.
* @param {Object} options.findGrepPreferences - properties to configure findGrep.
* @param {Function} [options.filter] - function that, given the object, returns true to collect its text (default: no filter).
* @returns {Array<Text>}
*/
function getTexts(options) {
options = options || {};
if (undefined == options.items)
throw Error('getTexts: expected `options.items` parameter.');
if ('undefined' == options.findGrepPreferences)
throw Error('getTexts: expected `options.findGrepPreferences` parameter.');
var items = options.items,
texts = [];
if ('Document' === items.constructor.name)
items = items.stories;
if (!items.hasOwnProperty(0))
items = [items];
// set up find grep
app.findGrepPreferences = NothingEnum.NOTHING;
app.changeGrepPreferences = NothingEnum.NOTHING;
for (var key in options.findGrepPreferences)
if (options.findGrepPreferences.hasOwnProperty(key))
app.findGrepPreferences[key] = options.findGrepPreferences[key];
for (var i = 0; i < items.length; i++) {
if (
undefined != options.filter
&& true !== options.filter(items[i])
)
continue;
if ('InsertionPoint' === items[i].constructor.name)
items[i] = items[i].parentStory.texts[0];
if (
undefined != items[i].contents
&& 0 === items[i].contents.length
)
continue;
if ('function' !== typeof items[i].findGrep)
continue;
texts = texts.concat(items[i].findGrep());
}
return texts;
};
/**
* Returns a thing with matching property.
* If `key` is undefined, evaluate the object itself.
* @author m1b
* @version 2024-04-21
* @param {Array|Collection} things - the things to look through.
* @param {String} [key] - the property name (default: undefined).
* @param {*} value - the value to match.
* @returns {*?} - the thing, if found.
*/
function getThing(things, key, value) {
for (var i = 0; i < things.length; i++)
if ((undefined == key ? things[i] : things[i][key]) == value)
return things[i];
};Sign up
Already have an account? Login
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inSign in to Adobe Community
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inEnter your E-mail address. We'll send you an e-mail with instructions to reset your password.

