解決済み
/*
Add @m2 Styles to TOC.jsx
Version: 3.1
Change in v3.1:
Adds comments and changes the default regexes so they work with
@2, @3, @5 as well as @m2, @m3, @m5.
Change in v3.0:
Allows the TOC level to be a fixed number or extracted from each
paragraph style name with a regular expression.
Adds paragraph styles matching a regular expression to a selected
Table of Contents Style. Defaults to styles named like:
@m2 Heading
@m2-Middle Title
@m3 H3
@2 Heading
@3 H3
The default paragraph style regex is ^@m?\d+(?!\d).
The default level regex is ^@m?(\d+).
*/
(function () {
if (app.documents.length === 0) {
alert("Open an InDesign document first.");
return;
}
app.doScript(
main,
ScriptLanguage.JAVASCRIPT,
undefined,
UndoModes.ENTIRE_SCRIPT,
"Add @m2 Styles to TOC"
);
function main() {
var doc = app.activeDocument;
if (doc.tocStyles.length === 0) {
alert("This document has no Table of Contents Styles.");
return;
}
// Build the list of Table of Contents Styles shown in the dropdown.
var tocNames = [];
for (var i = 0; i < doc.tocStyles.length; i++) {
tocNames.push(doc.tocStyles[i].name);
}
// Build a small dialog so the user can choose the TOC Style,
// choose which paragraph styles to include, and choose how levels are set.
var dialog = new Window("dialog", "Add Styles to TOC");
dialog.orientation = "column";
dialog.alignChildren = ["fill", "top"];
dialog.margins = 16;
dialog.add("statictext", undefined, "Table of Contents Style:");
var tocDropdown = dialog.add("dropdownlist", undefined, tocNames);
tocDropdown.selection = getDefaultTocSelection(tocNames);
dialog.add("statictext", undefined, "Paragraph style name regex:");
var regexField = dialog.add("edittext", undefined, "^@m?\\d+(?!\\d)");
regexField.characters = 34;
dialog.add("statictext", undefined, "TOC level number or regex:");
var levelField = dialog.add("edittext", undefined, "^@m?(\\d+)");
levelField.characters = 34;
var updateExisting = dialog.add("checkbox", undefined, "Update matching styles already in the TOC Style");
updateExisting.value = false;
var buttons = dialog.add("group");
buttons.alignment = ["right", "top"];
buttons.add("button", undefined, "Cancel", { name: "cancel" });
buttons.add("button", undefined, "Preview", { name: "ok" });
if (dialog.show() !== 1) {
return;
}
// Read the user's choices from the dialog.
var regexText = trim(regexField.text);
var levelText = trim(levelField.text);
if (regexText === "") {
alert("Enter a regular expression.");
return;
}
if (levelText === "") {
alert("Enter a TOC level number or a regular expression that extracts one.");
return;
}
var matcher;
try {
// This regex decides which paragraph styles should be added.
matcher = new RegExp(regexText);
} catch (error) {
alert("Invalid regular expression:\r" + error);
return;
}
// The level resolver decides what TOC level each matching style should use.
// It can use one fixed level, or extract a number from each style name.
var levelResolver = makeLevelResolver(levelText);
if (!levelResolver.ok) {
alert(levelResolver.message);
return;
}
var tocStyle = doc.tocStyles.itemByName(tocDropdown.selection.text);
if (!tocStyle.isValid) {
alert("No matching TOC Style was found.");
return;
}
var existing = getExistingEntryMap(tocStyle);
var matches = getMatchingParagraphStyles(doc, matcher);
if (matches.length === 0) {
alert("No paragraph styles matched:\r" + regexText);
return;
}
var toAdd = [];
var toUpdate = [];
var alreadyThere = [];
var levelFailures = [];
// Sort every matching paragraph style into one of three groups:
// add it, update it, or leave it alone because it is already included.
for (var j = 0; j < matches.length; j++) {
var style = matches[j];
var entryName = getTocEntryName(style);
var entry = existing[entryName] || existing[style.name];
var levelResult = levelResolver.getLevel(style.name);
if (!levelResult.ok) {
levelFailures.push(style.name + " - " + levelResult.message);
continue;
}
if (entry && entry.isValid) {
if (updateExisting.value && entry.level !== levelResult.level) {
toUpdate.push({ style: style, entry: entry, entryName: entryName, level: levelResult.level });
} else {
alreadyThere.push(style);
}
} else {
toAdd.push({ style: style, entryName: entryName, level: levelResult.level });
}
}
if (levelFailures.length > 0) {
alert(
"These styles matched the paragraph style regex, but no valid TOC level could be extracted:\r\r" +
levelFailures.join("\r")
);
return;
}
if (toAdd.length === 0 && toUpdate.length === 0) {
alert("The matching styles are already included in \"" + tocStyle.name + "\".");
return;
}
if (!confirm(buildPreview(tocStyle.name, regexText, levelText, toAdd, toUpdate, alreadyThere))) {
return;
}
var added = 0;
var updated = 0;
var failed = [];
// Update existing TOC entries only if the user ticked the checkbox.
for (var k = 0; k < toUpdate.length; k++) {
toUpdate[k].entry.level = toUpdate[k].level;
updated++;
}
// Add missing paragraph styles to the selected TOC Style.
for (var m = 0; m < toAdd.length; m++) {
try {
tocStyle.tocStyleEntries.add(toAdd[m].entryName, { level: toAdd[m].level });
added++;
} catch (error) {
failed.push(toAdd[m].entryName + " - " + error);
}
}
var message = "Finished updating \"" + tocStyle.name + "\".\r\r" +
"Added: " + added + "\r" +
"Updated: " + updated + "\r" +
"Already included: " + alreadyThere.length;
if (failed.length > 0) {
message += "\r\rFailed:\r" + failed.join("\r");
}
alert(message);
}
function getMatchingParagraphStyles(doc, matcher) {
var result = [];
var styles = doc.allParagraphStyles;
// doc.allParagraphStyles includes styles inside paragraph style groups.
for (var i = 0; i < styles.length; i++) {
var style = styles[i];
if (!style.isValid || style.name === "[No Paragraph Style]") {
continue;
}
matcher.lastIndex = 0;
if (matcher.test(style.name)) {
result.push(style);
}
}
return result;
}
function getExistingEntryMap(tocStyle) {
var map = {};
// Store current TOC entries so the script does not add duplicates.
for (var i = 0; i < tocStyle.tocStyleEntries.length; i++) {
var entry = tocStyle.tocStyleEntries[i];
map[entry.name] = entry;
}
return map;
}
function makeLevelResolver(levelText) {
var fixedLevel = parseInt(levelText, 10);
// If the user enters 2, 3, 5, etc., use that same level for every style.
if (/^\d+$/.test(levelText) && fixedLevel > 0) {
return {
ok: true,
getLevel: function () {
return { ok: true, level: fixedLevel };
}
};
}
var levelRegex;
try {
// Otherwise, treat the field as a regex that extracts the level.
// Example: ^@m?(\d+) reads 2 from @2 or @m2.
levelRegex = new RegExp(levelText);
} catch (error) {
return {
ok: false,
message: "Invalid TOC level regular expression:\r" + error
};
}
return {
ok: true,
getLevel: function (styleName) {
levelRegex.lastIndex = 0;
var match = levelRegex.exec(styleName);
if (!match) {
return {
ok: false,
message: "No level match from \"" + levelText + "\""
};
}
// If the regex has a capture group, use group 1.
// Otherwise, use the whole matched text.
var value = match.length > 1 ? match[1] : match[0];
var parsedLevel = parseInt(value, 10);
if (isNaN(parsedLevel) || parsedLevel < 1) {
return {
ok: false,
message: "Matched \"" + value + "\", which is not a level of 1 or higher"
};
}
return { ok: true, level: parsedLevel };
}
};
}
function getTocEntryName(style) {
var names = [style.name];
var parent = style.parent;
// Paragraph styles inside groups need a full path for TOC scripting:
// Group Name:Subgroup Name:Style Name
while (parent && parent.constructor && parent.constructor.name === "ParagraphStyleGroup") {
names.unshift(parent.name);
parent = parent.parent;
}
return names.join(":");
}
function buildPreview(tocName, regexText, levelText, toAdd, toUpdate, alreadyThere) {
return "TOC Style: " + tocName + "\r" +
"Regex: " + regexText + "\r" +
"TOC level: " + levelText + "\r\r" +
"Will add: " + toAdd.length + "\r" +
listNames(toAdd) +
"\rWill update: " + toUpdate.length + "\r" +
listNames(toUpdate) +
"\rAlready included, left alone: " + alreadyThere.length + "\r\r" +
"Continue?";
}
function listNames(items) {
var lines = "";
var limit = Math.min(items.length, 25);
for (var i = 0; i < limit; i++) {
lines += " " + (items[i].entryName || items[i].name);
if (items[i].level !== undefined) {
lines += " -> level " + items[i].level;
}
lines += "\r";
}
if (items.length > limit) {
lines += " ...and " + (items.length - limit) + " more\r";
}
return lines;
}
function getDefaultTocSelection(names) {
for (var i = 0; i < names.length; i++) {
if (names[i] !== "[Default]") {
return i;
}
}
return 0;
}
function trim(value) {
return value.replace(/^\s+|\s+$/g, "");
}
}());
Enter your E-mail address. We'll send you an e-mail with instructions to reset your password.
