Skip to main content
Participating Frequently
October 14, 2022
Answered

Smart Title Script

  • October 14, 2022
  • 1 reply
  • 722 views

Hello, 

This is a script from a previous post in 2011. It works great! But I have the following request: How to apply it to a specific paragraph style like "Heading 1" instead of selecting the text?

 

 

//DESCRIPTION: Converts selected text to title case smartly

 

 

var ignoreWords = ["a", "an", "and", "the", "to", "with", "in", "on", "as", "of", "or", "at", "into", "that",

         "by", "from", "their", "then", "for", "are", "not","cannot", "be", "is", "which", "can"];

var intCaps = ["PineRidge","InDesign","NJ","UMC", "FCCLA", "SkillsUSA", "d’Oeuvres", "VAT", "VIES",];

 

// or by creating text files named ignoreWords.txt and intCaps.txt in the same folder as the script

 

ignoreWords = getIgnoreFile(ignoreWords);

intCaps = getIntCaps(intCaps);

 

try {

    myText = app.selection[0].texts[0].contents;

} catch(e) {

    exit();

}

 

theWordRanges = myText.split("/");

for (var i = theWordRanges.length - 1; i >= 0; i--) {

    theWords = theWordRanges.toLowerCase().split(" ");

 

    //First word must have a cap, but might have an internal cap

 

    myNewText = "";

    for (var j = 0; theWords.length > j; j++) {

        k = isIn(intCaps,theWords)

        if (k > -1) {

            myNewText = myNewText + intCaps + " ";

            continue;

        } else {

            if ((isIn(ignoreWords,theWords) > -1) && (j != 0)) {

                myNewText = myNewText + theWords + " ";

            } else {

                myNewText = myNewText + InitCap(theWords) + " ";

            }

        }

    }

    theWordRanges = myNewText.substring(0,myNewText.length - 1)

}

app.selection[0].texts[0].contents = theWordRanges.join("/");

 

// +++++++ Functions Start Here +++++++++++++++++++++++

 

function getIgnoreFile(theWords) {

    var myFile = File(File(getScriptPath()).parent.fsName + "/ignoreWords.txt");

    if (!myFile.exists) { return theWords }

    // File exists, so use it instead

    myFile.open("r");

    var importedWords = myFile.read();

    myFile.close();

    return importedWords.split("\n"); // Could filter these, but what's the point?

}

 

function getIntCaps(theWords) {

    var myFile = File(File(getScriptPath()).parent.fsName + "/intCaps.txt");

    if (!myFile.exists) { return theWords }

    // File exists, so use it instead

    myFile.open("r");

    var importedWords = myFile.read();

    myFile.close();

    return importedWords.split("\n"); // Could filter these, but what's the point?

}

 

function getScriptPath() {

    // This function returns the path to the active script, even when running ESTK

    try {

        return app.activeScript;

    } catch(e) {

        return e.fileName;

    }

}

 

function isIn(aList,aWord) {

    for (var i = 0; aList.length > i; i++) {

        if (aList.toLowerCase() == aWord) {

            return i;

        }

    }

    return -1;

}

 

function InitCap(aWord) {

    if (aWord.length == 1) {

        return (aWord.toUpperCase());

    }

    return (aWord.substr(0,1).toUpperCase() + aWord.substring(1,aWord.length))

}

 

Correct answer m1b

Thanks @joaquinm13464128, it's always a good idea to post a link to the original script, so people can see where it came from, and a nice courtesy to include the author's name if posting the script listing.

 

I have added a new function that finds multiple paragraph styles, and also streamlined the changeToTitleCase function. I realised there wasn't much sense in having the distinction between "ignore" words and "intCap" words and it was simpler to just have a list of "overrides". If the word is found in the overrides list, then that is how it should be capitalized (unless first word in paragraph). I have left a visual distinction in the array literal so you can see the split between "a, and, the" etc and "PineRidge, VAT" etc, but these are the same for the script's purpose.

 

See how this goes for you. You may have to expand the overrides list quite a bit.

- Mark

 

function main() {

    // the paragraph style names
    var styleNames = [
        'Heading 1',
        'Heading 2',
        'Heading 3',
        'Heading 4'
    ];

    // gather all the paragraphs in these styles
    var found = findTextInParagraphStyles(app.activeDocument, styleNames);

    // change them all to Title Case
    for (var i = 0; i < found.length; i++)
        changeToTitleCase(found[i]);


    /**
     * Find text set in all multiple
     * paragraph styles.
     * @author m1b
     * @version 2022-10-16
     * @param {Document|Page|Story} findWhere - any Indesign Object with a findGrep method.
     * @param {Array<String>} styleNames - the paragraph style names to find.
     * @returns {Array<Paragraph>}
     */
    function findTextInParagraphStyles(findWhere, styleNames) {

        if (styleNames.constructor.name == 'String')
            styleNames = [styleNames];

        if (!findWhere.hasOwnProperty('findGrep'))
            throw Error('findTextInParagraphStyles cannot find in "' + findWhere + '".');

        // reset grep prefs
        app.findGrepPreferences = NothingEnum.NOTHING;
        app.changeGrepPreferences = NothingEnum.NOTHING;

        var found = [];

        for (var i = 0; i < styleNames.length; i++) {

            try {

                // find criterion
                app.findGrepPreferences.appliedParagraphStyle = styleNames[i];

                // do the find
                found = found.concat(findWhere.findGrep());

            } catch (error) {
                /* paragraph style not found */
            }

        }

        return found;

    };


    /**
     * Convert paragraph to title case
     * with some smarts.
     * @author Dave Saunders
     * @url http://jsid.blogspot.com/2005/08/script-of-day-smart-title-case.html
     * @author m1b
     * @version 2022-10-16
     * @discussion https://community.adobe.com/t5/indesign-discussions/smart-title-script/m-p/13268183
     * @param {Paragraph} para - an Indesign Paragraph.
     */
    function changeToTitleCase(para) {

        //DESCRIPTION: Converts selected text to title case smartly

        var overrides = [

            "a", "an", "and", "the", "to", "with", "in", "on", "as", "of", "or", "at", "into", "that",
            "by", "from", "their", "then", "for", "are", "not", "cannot", "be", "is", "which", "can",

            "PineRidge", "InDesign", "NJ", "UMC", "FCCLA", "SkillsUSA", "d’Oeuvres", "VAT", "VIES"

        ];

        // or by creating text files named overrides.txt in the same folder as the script

        var overrides = getOverrides(overrides);

        var words = para.words;

        for (var i = words.length - 1; i >= 0; i--) {

            var word = words[i],
                overrideIndex = isIn(overrides, word.contents);

            if (overrideIndex != -1)

                // override capitalization but do
                // capitalize first word of paragraph.

                word.contents = i == 0
                    ? initialCap(overrides[overrideIndex])
                    : overrides[overrideIndex];

            else

                word.contents = initialCap(word.contents.toLowerCase());

        }

    };

    // app.selection[0].texts[0].contents = words.join("/");

    // +++++++ Functions Start Here +++++++++++++++++++++++

    function getOverrides(theWords) {
        var myFile = File(File(getScriptPath()).parent.fsName + "/overrides.txt");
        if (!myFile.exists) { return theWords }
        // File exists, so use it instead
        myFile.open("r");
        var importedWords = myFile.read();
        myFile.close();
        return importedWords.split("\n"); // Could filter these, but what's the point?
    }

    function getScriptPath() {
        // This function returns the path to the active script, even when running ESTK
        try {
            return app.activeScript;
        } catch (e) {
            return e.fileName;
        }
    }

    function isIn(aList, aWord) {
        aWord = aWord.toLowerCase();
        for (var i = 0; aList.length > i; i++) {
            if (aList[i].toLowerCase() == aWord) {
                return i;
            }
        }
        return -1;
    }

    function initialCap(aWord) {
        if (aWord.length == 1) {
            return (aWord.toUpperCase());
        }
        return (aWord.substr(0, 1).toUpperCase() + aWord.substring(1, aWord.length));
    }

};

app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Find & Smart TitleCase");

1 reply

m1b
Adobe Expert
October 15, 2022

Hi @joaquinm13464128, did you write that script yourself? If so, you've made a great start. I found a few problems and adjusted a few parts of it. The big change I made was to make your script into a function that I could run for each found paragraph. I also changed the focus of the function to a single paragraph, and then iterate over the words. That's why you can see me using "word.contents" a bit. Please have a read through and see if it makes sense.

 

function main() {

    var doc = app.activeDocument,
        targetParagraphStyleName = 'My TitleCase Style';

    // reset grep prefs
    app.findGrepPreferences = NothingEnum.NOTHING;
    app.changeGrepPreferences = NothingEnum.NOTHING;
    app.findGrepPreferences.appliedParagraphStyle = targetParagraphStyleName;
    // do the find
    var found = doc.findGrep();

    for (var i = 0; i < found.length; i++) {
        var para = found[i];
        changeToTitleCase(para);
    }


    /**
     * Convert paragraph to title case
     * with some smarts.
     * based on script posted by @joaquinm13464128
     * here: https://community.adobe.com/t5/indesign-discussions/smart-title-script/m-p/13268183
     * @param {Paragraph} para - an Indesign Paragraph
     */
    function changeToTitleCase(para) {

        //DESCRIPTION: Converts selected text to title case smartly

        var ignoreWords = ["a", "an", "and", "the", "to", "with", "in", "on", "as", "of", "or", "at", "into", "that",
            "by", "from", "their", "then", "for", "are", "not", "cannot", "be", "is", "which", "can"];
        var intCaps = ["PineRidge", "InDesign", "NJ", "UMC", "FCCLA", "SkillsUSA", "d’Oeuvres", "VAT", "VIES",];

        // or by creating text files named ignoreWords.txt and intCaps.txt in the same folder as the script

        ignoreWords = getIgnoreFile(ignoreWords);
        intCaps = getIntCaps(intCaps);

        var words = para.words;

        for (var i = words.length - 1; i >= 0; i--) {

            var word = words[i];

            //First word must have a cap, but might have an internal cap

            if (isIn(intCaps, word.contents) != -1)
                continue;

            if (
                isIn(ignoreWords, word.contents) != -1
                && i > 0
            )
                continue;

            word.contents = initalCap(word.contents);

        }
    }

    // +++++++ Functions Start Here +++++++++++++++++++++++

    function getIgnoreFile(theWords) {
        var myFile = File(File(getScriptPath()).parent.fsName + "/ignoreWords.txt");
        if (!myFile.exists) { return theWords }
        // File exists, so use it instead
        myFile.open("r");
        var importedWords = myFile.read();
        myFile.close();
        return importedWords.split("\n"); // Could filter these, but what's the point?
    }

    function getIntCaps(theWords) {
        var myFile = File(File(getScriptPath()).parent.fsName + "/intCaps.txt");
        if (!myFile.exists) { return theWords }
        // File exists, so use it instead
        myFile.open("r");
        var importedWords = myFile.read();
        myFile.close();
        return importedWords.split("\n"); // Could filter these, but what's the point?
    }

    function getScriptPath() {
        // This function returns the path to the active script, even when running ESTK
        try {
            return app.activeScript;
        } catch (e) {
            return e.fileName;
        }
    }

    function isIn(aList, aWord) {
        aWord = aWord.toLowerCase();
        for (var i = 0; aList.length > i; i++) {
            if (aList[i].toLowerCase() == aWord) {
                return i;
            }
        }
        return -1;
    }

    function initalCap(aWord) {
        if (aWord.length == 1) {
            return (aWord.toUpperCase());
        }
        return (aWord.substr(0, 1).toUpperCase() + aWord.substring(1, aWord.length));
    }

};

app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Find & Smart TitleCase");

 

The first part of script does the find for the paragraph style. You will need to specify the paragraph style name that you have in your document. I did not test the text file loading functions.

- Mark

Participating Frequently
October 15, 2022

Hi Mark,

Thank you for your help. I just tested your script and, it works great with lower cases titles, but somehow it does not work on UPPERCASE titles, as the original script does. I'm cleaning up documents with a mix of upper and lowercase titles and sure it would help to change both with this script. Also, it could be possible to specify multiple paragraph styles instead of one, like for heading such as Heading1, Heading2, Heading3, Heading5, Heading6, up to Heading 7. And by the way, I did not write the code. Here is the link to the original post in 2005.  Best  - Joaquin

Participating Frequently
November 19, 2025

Glad to help!


Hi!
Just found this older thread in search of a solution for Adobe's failure to incorporate a Smart Title Case option themselves, but the script doesn't work (anymore?).

Both scripts –the original and Mark's edited version– only create a java script error:

 

Error Number: 24
Error String: theWordRanges.toLowerCase is not a function
Engine: main
File: /Users/donmilano/Library/Preferences/Adobe InDesign/Version
20.0/en_US/Scripts/Scripts Panel/SmartTitleCase.jsx
Line: 41
Source:
theWords = theWordRanges.toLowerCase().split(" ");
OK

 

OR:

 

Error Number: 30477
Error String: Invalid value for set property 'appliedParagraphStyle'.
Expected String, ParagraphStyle or NothingEnum enumerator, but received "My TitleCase Style".
Engine: main
File: /Users/donmilano/Library/Preferences/Adobe InDesign/Version
20.0/en_US/Scripts/Scripts Panel/SmartTitleCase.jsx
Line: 111
Source: app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined,
UndoModes. ENTIRE_SCRIPT, "Find & Smart TitleCase");
OK

 

Can you help me out here? I'm not at all familiar with script writing btw...

Thanks a lot in advance!

Milan