Copy link to clipboard
Copied
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;
};
}
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
Thnak you very much
All I can say is that this is a great groundbreaking feat.
Copy link to clipboard
Copied
@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.
Copy link to clipboard
Copied
@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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
Try this version—it returns an empty array if `cells` are undefined.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
Hi m1b.
Success!
I used your earlier convertToRowType();
You're so great.
I'm great too, hahaha.
Thank you very much.
Copy link to clipboard
Copied
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! 🙂
Copy link to clipboard
Copied
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);
};
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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);
}
};
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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);
}
};
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
You need to look at: function “convertToRowType(rows, rowType)”
It appears to only apply to: BODY_ROW to HEADER_ROW.
Find more inspiration, events, and resources on the new Adobe Community
Explore Now