Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

With script, How to duplicate header rows that have "cross rows and columns"?

Guide ,
Jul 17, 2025 Jul 17, 2025

The original script doesn't seem to work anymore?

function dupeTopRow(t){
    var newRow = t.rows.add(LocationOptions.BEFORE, t.rows[0]);
    var newCell = newRow.cells
    var lr = t.rows[1].cells
    for (var i = 0; i < newCell.length; i++){
        newCell[i].properties = lr[i].properties;
    };  
}

 

across.png

TOPICS
Scripting
974
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 18, 2025 Jul 18, 2025

Hi @dublove can you test this function. It will probably need some improvements, but is a good start I think.

- Mark

 

/**
 * @file Duplicate Selected Table Rows.js
 * 
 * Example of `duplicateRows` function.
 * Select one or more table cells, then run script.
 * It will duplicate the rows of the cell(s) you selected.
 *
 * @author m1b
 * @version 2025-07-19
 * @discussion https://community.adobe.com/t5/indesign-discussions/how-can-i-accurately-perform-a-table-header-row-conversion-operation-with-a-script/m-p/15295606
 */
function main() {

    var doc = app.activeDocument;

    var sourceRows = getRows(doc.selection[0], undefined, true);

    if (0 === sourceRows.length)
        return alert('Please select some table rows and try again.');

    var newRows = duplicateRows(sourceRows);

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Duplicate Rows');

/**
 * Duplicates `sourceRows`. The new rows
 * are added before the source rows.
 * @author m1b
 * @version 2025-07-18
 * @param {Array<Row>} sourceRows - the rows to duplicate.
 * @returns {Array<Row>} - the new rows.
 */
function duplicateRows(sourceRows) {

    var table = sourceRows[0].parent;

    // add a temporary structural column at the start
    table.columns.add(LocationOptions.BEFORE, table.columns.item(0)).unmerge();

    var indices = [];
    for (var r = 0; r < sourceRows.length; r++)
        indices[r] = sourceRows[r].index;

    // make new empty rows
    var newRows = []
    for (var r = 0; r < indices.length; r++)
        newRows.push(table.rows.add(LocationOptions.BEFORE, sourceRows[0]));

    // unmerge newRows
    for (var r = 0; r < newRows.length; r++)
        newRows[r].unmerge();

    // update source row references
    for (var r = 0; r < indices.length; r++)
        sourceRows[r] = table.rows.item(indices[r] + newRows.length);

    // duplicate spanning
    for (var r = 0; r < sourceRows.length; r++) {

        var sourceRow = sourceRows[r];
        var colSpans = sourceRow.cells.everyItem().columnSpan;
        var rowSpans = sourceRow.cells.everyItem().rowSpan;
        var span;
        var advance = 0;

        // column spanning
        while (span = colSpans.shift()) {

            if (span > 1)
                newRows[r].cells.item(advance).merge(newRows[r].cells.item(advance + (span - 1)));

            advance++;

        }

        // row spanning
        advance = 0;

        while (span = rowSpans.shift()) {

            if (span > 1)
                newRows[r].cells.item(advance).merge(newRows[r + (span - 1)].cells.item(advance));

            advance++;

        }

    }

    // update new row references
    for (var r = 0; r < indices.length; r++)
        newRows[r] = table.rows.item(indices[r]);

    // match the cells
    for (var r = 0; r < sourceRows.length; r++)
        for (var c = 0; c < sourceRows[r].cells.length; c++)
            newRows[r].cells.item(c).properties = sourceRows[r].cells.item(c).properties;

    // remove structural column
    table.columns.item(0).remove();

    return newRows;

};

/**
 * Returns the merged rows of a given row.
 * @author m1b
 * @version 2025-05-01
 * @param {Row} row - the target row.
 * @param {Function} [isUnique] - private function to check for uniqueness.
 * @returns {Array<Row>}
 */
function getMergedRows(row, isUnique) {

    isUnique = isUnique || uniqueChecker();

    var rows = [],
        rowIndex = row.index,
        table = row.parent;

    if (isUnique(row.index))
        rows.push(row);

    // 1. check for spans going down from this row
    var cells = table.rows[rowIndex].cells;
    for (var i = 0; i < cells.length; i++)
        for (var j = 0; j < cells[i].rowSpan; j++)
            if (isUnique(table.rows[rowIndex + j].index))
                rows.push(table.rows[rowIndex + j]);

    // 2. check for cells in earlier rows that reach into this one
    for (var r = 0, testRow; r < rowIndex; r++) {

        testRow = table.rows[r];
        span = testRow.rowSpan;

        if (r + span - 1 < rowIndex)
            continue;

        for (var j = 0; j < span; j++)
            if (isUnique(table.rows[r + j].index))
                rows.push(table.rows[r + j]);

    }

    rows.sort(function (a, b) { return a.index - b.index });

    return rows;

};

/**
 * Given cells, returns the cells' rows.
 * @author m1b
 * @version 2025-07-19
 * @param {Cell|Cells|Array<Row>} cells - the cells to get rows from.
 * @param {RowTypes} rowType - the desired row type.
 * @param {Boolean} [includeMergedRows] - whether to include merged rows (default: false).
 * @param {Table} [table] - the rows' table.
 * @param {Function} [isUnique] - private function to check for uniqueness.
 * @returns {Array<Row>}
 */
function getRows(cells, rowType, includeMergedRows, table, isUnique) {

    var rows = [],
        isUnique = isUnique || uniqueChecker();

    if (!cells)
        return [];

    if ('function' === typeof cells.getElements)
        cells = cells.getElements();

    if (
        'Array' === cells.constructor.name
        && cells[0].hasOwnProperty('cells')
    ) {
        // to handle the case of expanding an array of rows to include merged rows
        for (var i = 0; i < cells.length; i++)
            rows = rows.concat(getRows(cells[i].cells, rowType, includeMergedRows, table, isUnique))

        return rows;
    }

    if (
        cells.hasOwnProperty('cells')
        && cells.cells.length > 0
    )
        cells = cells.cells;

    if (
        !cells.hasOwnProperty('0')
        || !cells[0].hasOwnProperty('parentRow')
    )
        // can't get rows from `cells`
        return [];

    var table = table || cells[0].parent;

    while (
        'Table' !== table.constructor.name
        && table.hasOwnProperty('parent')
    )
        table = table.parent;

    for (var i = 0, row; i < cells.length; i++) {

        spanLoop:
        for (var j = 0; j < cells[i].parentRow.rowSpan; j++) {

            row = table.rows[cells[i].parentRow.index + j];

            if (
                undefined != rowType
                && rowType !== row.rowType
            )
                continue spanLoop;

            if (includeMergedRows)
                rows = rows.concat(getMergedRows(row, isUnique));

            else if (isUnique(row.index))
                rows.push(row);

        }

    }

    return rows;

};

/**
 * Returns a function that, given a String, will
 * return true if that String has not been seen before.
 *
 * Example:
 *   var isUnique = uniqueChecker();
 *   if (isUnique(myThing))
 *     // do something with myThing
 *
 * @author m1b
 * @version 2025-05-01
 * @returns {Function}
 */
function uniqueChecker() {

    var unique = {};

    return function (str) {

        if (unique[str])
            return false;

        unique[str] = true;
        return true;

    };

};

Edit 2025-07-19: added check for undefined cells in getRows function.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 18, 2025 Jul 18, 2025

If it's already a table header, it will report an error.
Nothing else found.

Still want to be able to execute scripts with table headers already present.

Because sometimes the table header already exists, but I still need to add rows at the top or at the end.

dublove_0-1752850987194.jpeg

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 18, 2025 Jul 18, 2025

Thnak you very much

All I can say is that this is a great groundbreaking feat.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 18, 2025 Jul 18, 2025

@m1b Hi m1b.

Sorry.

It turns out that the error is not caused by an existing table header.
It was caused by an existing table footer.

You don't need to add the footer, I'll add  table footer later.
Becausetable footer is simple, I add my own easier to control the point.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 18, 2025 Jul 18, 2025

@dublove  Does my code above give the error? If so, I'd like you to share the table that triggers the problem so that I can improve the function.

 

Also, remember if you are actively scripting and debugging, you should disable the "app.doScript" line because it swallows the error details and we don't know anything. Comment out the app.doScript and add main();

// app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Duplicate Rows');
main();

and for your final script, put the app.doScript back (because it nicely handles Undo states). Then errors will show which line threw them.

- Mark 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 18, 2025 Jul 18, 2025

Hi @m1b 

It's weird, it appeared once just now and then never again.
It just prompted this error:

if ('function' === typeof cells.getElements)

I don't have a screenshot.

But then, I've tried multiple scenarios and everything works fine.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 18, 2025 Jul 18, 2025

That error will occur when you pass `cells` that are undefined, for example when there are no cells selected. I will put in a check for it. - Mark

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 18, 2025 Jul 18, 2025

Edit 2025-07-19: added check for undefined cells in getRows function.

The problem is still there.
Need to check that no objects are selected.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 18, 2025 Jul 18, 2025

Try this version—it returns an empty array if `cells` are undefined.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 19, 2025 Jul 19, 2025

HI @m1b 

I haven't tested the step of converting the table header.
I have a hunch:
Converting to table headers across multiple rows may be problematic.

 

I added a new line.

Turn the table header downwards in order (row[0]. . . row[e])

But you can't add it to the main program, wouldn't that be too illiberal.

 

There's still a problem, and I'm back to the old ways.

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 19, 2025 Jul 19, 2025

Hi m1b.

Success!
I used your earlier convertToRowType();
You're so great.
I'm great too, hahaha.

Thank you very much.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 19, 2025 Jul 19, 2025

Excellent! I think you are learning that if I write a function for a particular purpose, do not change it; just use it for that one purpose. If it is "too long" it is as short as I could make it. Just use it the way I wrote it. If it fails to do that one particular job, then there is a bug we can fix.

 

Yes we are great! 🙂

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 19, 2025 Jul 19, 2025

Hi @m1b 

To exclude that the currently selected row is already a table header, I need to convert the header row to a table body row first.
Example : there are alread  2 table header rows.   I am selecting them.  Try the script,  Will pop up: Cannot set row type.

 

Convert in order:
rows[1] coverto bodyRows,
rows[0] coverto bodyRows.
Is there an error in this listing?

 

I just realized that this for doesn't have {}

 

I tried to convert for the whole table and that also failed.
for (var i = 0; i < table.rows.length-1; i++)

function toBody(sourceRows) {
    // collect all the rows that now *should* be body rows
    var bodyRows = [],
        table = sourceRows[0].parent;
    var e = sourceRows.length - 1;

    for (var i = e; i > 0; i--)
     alert(i);
    if (RowTypes.BODY_ROW !== table.rows[i].rowType)
        bodyRows.push(table.rows[i]);
    // convert them to body rows
    var bodyRows = convertToRowType(bodyRows, RowTypes.BODY_ROW);

};

 009.png022.png

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 19, 2025 Jul 19, 2025

Hi @m1b 

It's weird.
The selected line, if it's body, goes to header with no problem.
On the flip side, if it goes to header to body, it says that the row type may not be set.

 

I tested that May posting directly, and it also fails to go from headRow to bodyRow when spanning rows.

 

I tried to change duplicateRows(sourceRows);
to: coverHeaderToBody(sourceRows);
without success.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 21, 2025 Jul 21, 2025

Hi @m1b 

I still haven't solved the bug : headerRows  converted to bodyRows.

Pop up: You cannot set the row type.

The merger was canceled, but not merged again.

It seems to have failed halfway through the split.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 22, 2025 Jul 22, 2025

HI @m1b 

I'm really frustrated and can't figure it out.

I've tried n types.

The first, converting to the header row is possible.


The second, converting to the body row is not possible.

Hint: This sentence
rows[il.rowType = rowType;

Pop-up: Unable to set row type. 

 

The first: This is feasible,
but the order of the table headers seems to be the opposite of what is customary.

//Convert Body row to header row
function toHeader(sourceRows) {
    // collect all the rows that now *should* be body rows
    var headerRows = [],
        table = sourceRows[0].parent;
    var e = sourceRows.length;
    //That's strange, the header row is rotating backwards? Shouldn't the first row rotate first?
    for (var i = e; i > 0; i--) {
        if (RowTypes.HEADER_ROW !== table.rows[i].rowType)
            headerRows.push(table.rows[i]);
        // convert them to body rows
        var headerRows = convertToRowType(headerRows, RowTypes.HEADER_ROW);
    }
};

 

Second:

This is wrong. Pop-up: Unable to set row type. 

//Convert all header rows to body rows
function toBody(sourceRows) {
    // collect all the rows that now *should* be body rows
    var bodyRows = [],
        table = sourceRows[0].parent;
    var e = sourceRows.length;

    for (var i = e; i > 0; i--) {
        if (RowTypes.BODY_ROW !== table.rows[i].rowType)
            bodyRows.push(table.rows[i]);
        // convert them to body rows
        var bodyRows = convertToRowType(bodyRows, RowTypes.BODY_ROW);
    }
};

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 22, 2025 Jul 22, 2025

Hi @dublove I am trying to get to the bottom of this. One of the operations keeps crashing Indesign. I will have another look when I get time. - Mark

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jul 22, 2025 Jul 22, 2025

Hi @m1b 

If that doesn't work, I suggest dividing convertToRowType into two parts:
After canceling the merge, set the BODY or READER type, and then restore the merge.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Aug 05, 2025 Aug 05, 2025

Hi @m1b 

Life must go on, and the hustle and bustle cannot stop.


Can “Cancel Merge” and “Restore Merge” be separated into two independent parts?
This way, you can first convert to the header row or body row in the middle.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Aug 18, 2025 Aug 18, 2025

Hi @m1b 

It seems that most of the issues have been resolved.


The only remaining major bug is: cross-column (spread rows)  to HEADER_ROWS.

Perhaps you could provide detailed comments on your code for splitting and merging the table header rows so that I can study them.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Aug 26, 2025 Aug 26, 2025

@m1b 

This operation can be achieved in InDesign.

Then the script should also be able to achieve it?
Perhaps only InDesign developers know the answer to this.

 

Just like how right-clicking an image edited in Photoshop in the Links panel automatically updates it.

626.png

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Aug 26, 2025 Aug 26, 2025

@m1b 

BODY_ROW can transition to HEADER_ROW.
However
HEADER_ROW cannot transition to BODY_ROW.

 

function toHeader(sourceRows) {
    // collect all the rows that now *should* be body rows
    var headerRows = [],
        table = sourceRows[0].parent;
    var e = sourceRows.length;
    //That's strange, the header row is rotating backwards? Shouldn't the first row rotate first?
    for (var i = e; i > 0; i--) {
        if (RowTypes.HEADER_ROW !== table.rows[i].rowType)
            headerRows.push(table.rows[i]);
        // convert them to body rows
        var headerRows = convertToRowType(headerRows, RowTypes.HEADER_ROW);
    }
};

 

 This won't do. I haven't found any issues. Should we modify the code for splitting and merging?

//Convert all header rows to body rows
function toBody(sourceRows) {
    // collect all the rows that now *should* be body rows
    var bodyRows = [],
        table = sourceRows[0].parent;
    var e = sourceRows.length;
    alert("here");
    for (var i = e; i > 0; i--) {
        if (RowTypes.BODY_ROW !== table.rows[i].rowType)
            bodyRows.push(table.rows[i]);
        // convert them to body rows
        var bodyRows = convertToRowType(bodyRows, RowTypes.BODY_ROW);
    }
};

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 26, 2025 Aug 26, 2025

I am trying to get to the bottom of this

 

Hi @m1b , I get an scripting error when I try to make rows that have cells that have been divided or merged via the UI. You can make rows with divided or merged cells into headers if you construct the table or handle the initial merge or divides via scripting. See the script I posted here.

 

https://community.adobe.com/t5/indesign-discussions/brainstorming-with-script-how-to-transform-sprea...

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Aug 26, 2025 Aug 26, 2025

@rob day 

You need to look at: function “convertToRowType(rows, rowType)”
It appears to only apply to: BODY_ROW to HEADER_ROW.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines