Skip to main content
Participant
February 11, 2025
Question

How to preserve character styles when merging text via javascript

  • February 11, 2025
  • 4 replies
  • 532 views

I'm using a script to merge multiple lines into one paragraph.

 

This is the start of a paragraph.

This line needs running back.

This line needs running back.

 

Result: 

This is the start of a paragraph. This line needs running back. This line needs running back.

 

I have code to produce this and it works perfectly. However, when there are characters that are bold or use a bold character style, when merged the bold characters are not retained or preserved.

 

I am attempting to use a custom indexof function (as i know idnesign does not support indexof) to try and match the characters or words when they shift position from their line to a different one.

 

For whatever reason i just can't get this to work. Does anyone have any suggestions i can feed to chatgpt as im inexperienced with code.

 

Please see below code that im using. Just to note, the code only works within the defined paragraph styles CAUTION, WARNING or NOTE.

 

Thanks,

// Custom indexOf function for word searching
function customIndexOf(text, searchWord) {
    var index = -1;

    // Ensure text and searchWord are strings
    if (typeof text !== "string" || typeof searchWord !== "string") {
        throw new Error("Both text and searchWord must be strings");
    }

    var wordLength = searchWord.length;

    // Iterate through the text to find exact match
    for (var i = 0; i <= text.length - wordLength; i++) {
        if (text.substr(i, wordLength) === searchWord) {
            index = i; // Found exact match
            break;
        }
    }
    return index;
}

// Function to merge paragraphs
function mergeParagraphs(paragraphsToMerge) {
    var firstParagraph = paragraphsToMerge[0];
    var mergedText = firstParagraph.contents;

    var boldedWords = [];

    // Collect all bolded words within the paragraphs to be merged
    for (var p = 1; p < paragraphsToMerge.length; p++) {  // Start from 1 to avoid duplicating first paragraph
        var paragraph = paragraphsToMerge[p];
        var paragraphText = paragraph.contents;

        var boldText = [];
        for (var i = 0; i < paragraph.texts.length; i++) {
            var text = paragraph.texts[i];
            if (text.appliedCharacterStyle.name === "Bold Copy") {
                boldText.push(text.contents); // Collect bold text
            }
        }

        // Store the bold words and their position
        boldedWords.push({
            text: paragraphText,
            bold: boldText
        });

        mergedText += " " + paragraphText;
        paragraph.remove();
    }

    mergedText = mergedText.replace(/\s{2,}/g, ' '); // Remove excessive spaces
    firstParagraph.contents = mergedText;

    // Now we need to apply the bold text in the merged paragraph
    for (var i = 0; i < boldedWords.length; i++) {
        var boldedWord = boldedWords[i].bold;
        for (var j = 0; j < boldedWord.length; j++) {
            // Using customIndexOf to find the word in the merged paragraph
            var index = customIndexOf(firstParagraph.contents, boldedWord[j]);

            if (index !== -1) {
                var charRange = firstParagraph.characters.itemByRange(index, index + boldedWord[j].length - 1);
                charRange.appliedCharacterStyle = app.activeDocument.characterStyles.itemByName("Bold Copy");
            }
        }
    }

    // Preserve the paragraph style (the style of the first paragraph)
    var firstParagraphStyle = paragraphsToMerge[0].appliedParagraphStyle;
    firstParagraph.appliedParagraphStyle = firstParagraphStyle;
}

// Function to apply style and merge paragraphs based on target style
function applyStyleAndMerge(targetStyle, stopStyles) {
    if (app.documents.length > 0) {
        var doc = app.activeDocument;

        if (app.selection.length > 0 && app.selection[0].constructor.name === "Text") {
            var selectedText = app.selection[0];
            var paragraphs = selectedText.paragraphs.everyItem().getElements();

            var applyingStyle = false;
            var rangeToMerge = [];
            var lastTargetStyleIndex = -1;

            function isInStopStyles(styleName) {
                for (var i = 0; i < stopStyles.length; i++) {
                    if (stopStyles[i] === styleName) {
                        return true;
                    }
                }
                return false;
            }

            for (var p = 0; p < paragraphs.length; p++) {
                if (paragraphs[p].appliedParagraphStyle.name === targetStyle) {
                    lastTargetStyleIndex = p;
                }
            }

            for (var p = 0; p < paragraphs.length; p++) {
                var paragraph = paragraphs[p];
                var isStopStyle = isInStopStyles(paragraph.appliedParagraphStyle.name);

                if (isStopStyle && applyingStyle) {
                    applyingStyle = false;
                    if (rangeToMerge.length > 1) {
                        mergeParagraphs(rangeToMerge);
                    }
                    rangeToMerge = [];
                }

                if (paragraph.appliedParagraphStyle.name === targetStyle) {
                    applyingStyle = true;
                    rangeToMerge.push(paragraph);
                } else if (applyingStyle && !isStopStyle) {
                    rangeToMerge.push(paragraph);
                }

                if (p === lastTargetStyleIndex && applyingStyle) {
                    applyingStyle = false;
                    if (rangeToMerge.length > 1) {
                        mergeParagraphs(rangeToMerge);
                    }
                    rangeToMerge = [];
                }
            }

            if (rangeToMerge.length > 1) {
                mergeParagraphs(rangeToMerge);
            }
        }
    }
}

var cautionStopStyles = ["WARNING", "NOTE", "Body Header", "Body Copy Bullet", "Safety Header", "Body Header CAPS", "Body Copy"];
applyStyleAndMerge("CAUTION", cautionStopStyles);

var warningStopStyles = ["CAUTION", "NOTE", "Body Header", "Body Copy Bullet", "Safety Header", "Body Header CAPS", "Body Copy"];
applyStyleAndMerge("WARNING", warningStopStyles);

var noteStopStyles = ["CAUTION", "WARNING", "Body Header", "Body Copy Bullet", "Safety Header", "Body Header CAPS", "Body Copy"];
applyStyleAndMerge("NOTE", noteStopStyles);

 

4 replies

m1b
Community Expert
Community Expert
February 13, 2025

Hi @Niko32511349sm76, I've read the posts on this page and they are all good answers, but I didn't see my specific approach so I'll share a demo script here. It sounds like it might not exactly be what you need, but maybe it helps your understanding? Test first with the attached demo.indd if you like. I apply the paragraph style without overriding local styling, as @Peter Kahrel showed, and remove the line break as @Robert at ID-Tasker showed.

- Mark

 

/**
 * Demonstration of merging paragraphs.
 *
 * @author m1b
 * @version 2025-02-13
 * @discussion https://community.adobe.com/t5/indesign-discussions/how-to-preserve-character-styles-when-merging-text-via-javascript/m-p/15145001
 */
function main() {

    var doc = app.activeDocument,
        testParagraphs = doc.stories[0].paragraphs.everyItem().getElements();

    var mergedParagraph = mergeParagraphs(testParagraphs);

    if (mergedParagraph.isValid)
        // applying a demonstration paragraph style
        mergedParagraph.applyParagraphStyle(doc.paragraphStyles.itemByName('Demo Style'), false);

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Merge Paragraphs');

/**
 * Merge multiple paragraphs into one.
 * 
 * Important: assumes paragraphs are
 * continguous in the same story.
 * @author m1b
 * @version 2025-02-13
 * @param {Array<Paragraph>} paragraphs - the paragraphs to merge.
 * @param {String} [delimiter] - a string to replace line breaks.
 * @returns {Paragraph} - the merged paragraph;
 */
function mergeParagraphs(paragraphs, delimiter) {

    var endingLinefeed = /\s*[\n\r]$/,
        para;

    for (var i = paragraphs.length - 1, match; i >= 0; i--) {

        para = paragraphs[i];
        match = para.contents.match(endingLinefeed);

        if (!match)
            continue;

        // remove the line feed and replace with delimiter
        para.characters
            .itemByRange(para.characters[-match[0].length], para.characters[-1])
            .contents = delimiter || '\u0020';
    }

    // return the first paragraph, which is the merged paragraph
    return para;

};

 

 

Robert at ID-Tasker
Legend
February 13, 2025

@m1b

 

Haven't seen INDD file - but don't see in the code any references to stop styles? 

 

m1b
Community Expert
Community Expert
February 13, 2025

Robert, it's just a quick demo of merging paragraphs, not collecting the paragraphs.

Robert at ID-Tasker
Legend
February 11, 2025

@Niko32511349sm76

 

This isn't all of the code? 

 

How do you find paragraphs to merge? 

 

Which style should stay as the final ParaStyle? 

 

Robert at ID-Tasker
Legend
February 11, 2025

@Niko32511349sm76

 

OK, found it. Sorry, I'm on my phone so it's not very convenient.

 

If I may suggest - my personal way of coding: 

- instead of iterating though the array of the "stop styles" - use indexOf() on the string - you can join the array first, if you prefer to keep list of paragraphs as an array - then you've one line instead of whole loop - will be also faster,

- can't check right now - but I would just go over the paragraphs - and if next one is "correct" and should be merged - just remove "enter" in between them(*). 

 

The above - will make your code shorter and faster. 

 

But I'm not JS guy - maybe JS requires a different approach. 

 

Robert at ID-Tasker
Legend
February 11, 2025

(*) this way - you'll achieve even more than what @Peter Kahrel suggested - you're completely shifting "responsibility" of keeping stuff organised on InDesign.

 

It's the same as if would you've done in UI manually - following paragraph gets formatting - and applied ParaStyle - from the preceding paragraph. 

 

Peter Kahrel
Community Expert
Community Expert
February 11, 2025

There are two ways of applying a paragraph style. You use this:

firstParagraph.appliedParagraphStyle = firstParagraphStyle;

which, as you found, removes local formatting. To preserve local formatting, use this:

firstParagraph.applyParagraphStyle (firstParagraphStyle, false);

where 'false' means 'leave local overrides alone'.

 

So there's no need any longer for looking for those bold words, but if you wanted to, note that JavaScript (not to be confused with InDesign) does have myString.indexOf(). Check he object model.

Community Expert
February 11, 2025

Are you tracking whole words?

Instead of tracking whole words, track the character indices of bold text before merging.
Reapply the bold character style using these indices after merging.