Copy link to clipboard
Copied
My table header usually has 1-4 rows, most of the time 1-2rows.
I sometimes need to determine which table header rows already exist.
Next I need to convert to body rows, or copy the table header rows.
Before I applied the style, I determined the number of table headers by manually selecting them.
The table header rows are the rows I selected.
How should this code be expressed?
After applying table styles, if I ever have table headers.
How do I determine which rows are table headers?
Okay @dublove you just want a script to simply convert the selected cells to header rows and other rows to body rows. Easy right? Haha, here you go...
- Mark
/**
* @file Convert Table Row Types.js
*
* Will convert the selected rows to header rows
* and also convert any non-selected header rows
* to body rows.
*
* Note: will convert the minimum number of rows
* necessary to obey the rules around converting rows
* with merged cells. This may be *more* than the
* selected rows.
*
* @a...
Copy link to clipboard
Copied
Hi @dublove here is a quick example.
- Mark
(function () {
var doc = app.activeDocument,
table = doc.stories.everyItem().tables.everyItem().getElements()[0];
// we only want one header row
var headerCount = 1;
// loop backwards because you can't convert a header row
// to a body row if there is a header row after it
for (var r = table.rows.length - 1; r >= headerCount; r--) {
if (RowTypes.HEADER_ROW === table.rows[r].rowType)
// convert header row to body row
table.rows[r].rowType = RowTypes.BODY_ROW;
}
})();
Copy link to clipboard
Copied
Hi @m1b
You're too fast.
I reworked my idea.
Before preparing to apply the table style,
I also wanted to determine the number of header rows in the current table by mouse selection.
Thank you.
Copy link to clipboard
Copied
Haha, okay how about this:
/**
* @author m1b
*/
(function () {
var doc = app.activeDocument,
table = getTable(doc.selection[0]);
if (!table || !table.isValid)
return alert('Please select a table and try again.');
$.writeln('headerRowCount = ' + table.headerRowCount);
$.writeln('bodyRowCount = ' + table.bodyRowCount);
})();
/**
* Attempts to return a Table, given an object.
* @author m1b
* @version 2023-02-06
* @Param {InsertionPoint|Text|Cells|Table|TextFrame} obj - an object related to a Cell.
* @Returns {Cell}
*/
function getTable(obj) {
try {
if (undefined == obj)
return;
} catch (error) {
// Indesign bug - not sure why this sometimes occurs.
// Reverting or re-opening document fixes it.
return alert('Table found, but was temporarily invalid. Please close and re-open document and try again.');
}
if ('Array' === obj.constructor.name) {
for (var i = 0, table; i < obj.length; i++) {
table = getTable(obj[i]);
if (table && table.isValid)
return table;
}
return;
}
if (obj.constructor.name == 'Cell')
return obj.parent;
if (obj.parent.constructor.name == 'Cell')
return obj.parent.parent;
if (
obj.hasOwnProperty('cells')
&& obj.cells.length > 0
)
return obj.cells[0].parent;
if (
obj.hasOwnProperty('tables')
&& 0 !== obj.tables.length
)
return obj.tables[0];
};
Copy link to clipboard
Copied
I can't understand and get it,
For the 1st one (left page), I want the script to determine the rows I'm currently selecting.
And I will convert them to table headers.
For the 2nd (right page), it's determining if there is a table header, and if there is a table header, returning which rows are table headers, and I'm going to repeat the table header, or cancel the table header.
That's two cases.
No need to combine them in one script
Copy link to clipboard
Copied
Actually, my intention is:
When no style is applied, the selection is used to know how many rows of table headers need to converts.
Alternatively, another script:
I need to know if the current table has header rows, and if so, which rows are header rows.
These are two scripts, not two cases of one script.
Copy link to clipboard
Copied
For the right one, my current thinking is that:
first, determines that at least row 1 is the table header, then row 2, row 3, and up until the first body row are all table header rows.
If the first rows is not header rows, this table does not have a header set.
Copy link to clipboard
Copied
Hi @dublove
> I need to know if the current table has header rows, and if so, which rows are header rows.
table.headerRowCount
will do this. If it is greater than zero, then there are header rows. Header rows are always the first rows in the table. If headerRowCount === 0 then there is no header rows set.
If that doesn't answer your questions, please specify *exactly* what you expect before and after running script. Don't try to describe what the script does, just say what happened after the script has finished.
- Mark
Copy link to clipboard
Copied
function Rh () {
var doc = app.activeDocument;
var cell = app.activeDocument.selection[0].parent;
var table = cell.parent;
for(r=table.headerRowCount;r>0;r--){
table.rows[r].rowType = RowTypes.BODY_ROW;
}
}
Rh ();I tested this code.
It says it can't set the row type.
Is it true that if there are two table header rows, they must both be set to body or they fail?
How to do this.
Copy link to clipboard
Copied
> It says it can't set the row type.
Most likely because all header rows must be at the start of the table, so if there are two header rows, you can change the first row into a body row until you've changed the second row.
Copy link to clipboard
Copied
Hi @m1b
It is better to think in terms of the "currently selected rows".
I won't think of anything else.
I want to determine the header from the "currently selected rows".
But I can't understand the second part of your code.
Copy link to clipboard
Copied
for(r=table.headerRowCount;r>0;r--){
table.rows[r].rowType = RowTypes.BODY_ROW;
}This sentence is also executed from the last row of the table header row.
I don't know why it's not correct.
For example, if there are 3 header rows, doesn't it work like this?
table.rows[2].rowType = RowTypes.BODY_ROW;
table.rows[1].rowType = RowTypes.BODY_ROW;
table.rows[0].rowType = RowTypes.BODY_ROW;Do you have to do it all at once? Is there such a way to write it?
table.rows[2,1,0].rowType = RowTypes.BODY_ROW;
Copy link to clipboard
Copied
Hi @m1b
This worked ......
--r postponed the work?
for(r=table.headerRowCount; r>0;--r){
table.rows[r-1].rowType = RowTypes.BODY_ROW;
}That leaves only one other question:
How do I convert the selected rows to table headers?
Copy link to clipboard
Copied
Hi @m1b
I apologize for the trouble.
The preceding thought is a bit confusing.
Maybe I should have phrased it like this:
Judge this table, if there is no table header, convert the selected row to a table header.
If there is already a header, and record which rows are headers (I'm stubborn), then cancel the header.
Copy link to clipboard
Copied
Okay @dublove you just want a script to simply convert the selected cells to header rows and other rows to body rows. Easy right? Haha, here you go...
- Mark
/**
* @file Convert Table Row Types.js
*
* Will convert the selected rows to header rows
* and also convert any non-selected header rows
* to body rows.
*
* Note: will convert the minimum number of rows
* necessary to obey the rules around converting rows
* with merged cells. This may be *more* than the
* selected rows.
*
* @author m1b
* @version 2025-07-18
* @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,
selectedRows = getRows(doc.selection[0]);
if (0 === selectedRows.length)
return alert('Please select some table rows and try again.')
// convert the selected rows to header rows
var headerRows = convertToRowType(selectedRows, RowTypes.HEADER_ROW);
// collect all the rows that now *should* be body rows
var bodyRows = [],
table = headerRows[0].parent,
lastHeaderRowIndex = headerRows[headerRows.length - 1].index;
for (var i = lastHeaderRowIndex + 1; i < table.rows.length; i++)
if (RowTypes.HEADER_ROW === table.rows[i].rowType)
bodyRows.push(table.rows[i]);
// convert them to body rows
var bodyRows = convertToRowType(bodyRows, RowTypes.BODY_ROW);
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Convert to Header Rows');
/**
* Converts each row in `rows` to the given `rowType`.
* Note that this will expand the rows to include all merged rows
* because you can't set the rowType of a partially merged row.
* @author m1b
* @version 2025-05-01
* @param {Array<Row>} rows - the target rows.
* @param {RowTypes} rowType - the desired row type.
* @returns {Array<Row>} - the rows that were converted.
*/
function convertToRowType(rows, rowType) {
if (0 === rows.length)
return [];
var merges = [],
table = rows[0].parent;
// this will expand the rows to include all merged rows
// because you can't set the rowType of a partially merged row
rows = getRows(rows, undefined, true);
rows.sort(
// footers must be converted in reverse order
(RowTypes.FOOTER_ROW === rowType)
? function (a, b) { return b.index - a.index }
: function (a, b) { return a.index - b.index }
);
if (RowTypes.HEADER_ROW === rowType) {
// because header rows must be at the start of the table
// add any rows until the start
while (rows[0].index > 0)
rows.unshift(table.rows[rows[0].index - 1]);
}
else if (RowTypes.FOOTER_ROW === rowType) {
// because footer rows must be at the end of the table,
// add rows until the end
while (rows[rows.length - 1].index + rows.length < table.rows.length)
rows.unshift(table.rows[rows[rows.length - 1].index + rows.length]);
}
// find and unmerge cells that span across the selected rows
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
cellsLoop:
for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j];
var rs = cell.rowSpan;
var cs = cell.columnSpan;
if (1 === rs && 1 === cs)
// not merged
continue cellsLoop;
merges.push({
row: cell.parentRow.index,
column: cell.parentColumn.index,
rowSpan: rs,
columnSpan: cs
});
cell.unmerge();
}
}
// set the rowType of each row
for (var i = 0; i < rows.length; i++)
if (rows[i].rowType !== rowType)
rows[i].rowType = rowType;
// re-merge the previously merged cells
for (var i = merges.length - 1; i >= 0; i--) {
var m = merges[i],
topLeft = table.rows[m.row].cells[m.column],
bottomRight = table.rows[m.row + m.rowSpan - 1].cells[m.column + m.columnSpan - 1];
topLeft.merge(bottomRight);
}
return rows;
};
/**
* 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-18: fixed bug in getRows function.
Edit 2025-08-26: updated getRows function.
Copy link to clipboard
Copied
Hi m1b.
The script runs fine.
Thank you very much.
Just, a bit too long?
It seems to contain all possible bug solutions.
This is very valuable to study and use, I will learn it well.
Copy link to clipboard
Copied
Yes it is too long and probably has bugs. The reason it is long is that there are times when you can't simply convert rows, even from Table menu > Convert and to workaround those cases is... a bit long.
Also could you please edit your post and change the title to something like "[ExtendScript] How to convert selected table rows to Header?" Let me know if you can't edit it and I will ask a moderator.
Edit: I see you already changed the title. Thanks!
Copy link to clipboard
Copied
Already changed the posting title.
Copy link to clipboard
Copied
The script is so long I don't even know where to step in.
I need to add some of my own features to make them articulate.
Would it be possible to pop up a dialog box and include options:
a. Cancel all table headers / (convert selected rows to table headers[Also, copy the table header again])
b. add “continue” table row to the top / (cancel the top “contine” table header row)
c. Skip the header row / (do not skip the header row)
d. Add a blank row at the end , then set it as FOOTER_ROW
Sad, things are getting complicated with only one script.
This could be split into at least 3 scripts ......
Copy link to clipboard
Copied
Actually the script isn't long—it is just the part in the "main" function: 12 lines without the whitespace. All the other code is functions that you shouldn't need to change unless they have bugs.
I'm starting to understand what you actually want now: you want to create a table that appears to have normal header, but actually it has (continued) in a fake row at the top of the header but it doesn't appear in the first frame. In the first frame is a copy of the header rows (without the continued). Is that right? If so, it is a good question, but please post as a new question, not here. Also remember to post .indd before and after.
- Mark
Copy link to clipboard
Copied
Hi @m1b
can you help me to create a dialog box where I can choose which actions to perform
For example, after checking the box to create a “continue row”
I want to execute the following code
dupeTopRow(myTable);
myTable.rows.add(LocationOptions.BEFORE, myTable.rows[0]);
/**
* duplicate the first row af a myTable
* @ param the myTable
* @ return void
*/
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;
};
I'd also like to be optional.
[ ] Row to header [ ] Row to body
Thank you.
Copy link to clipboard
Copied
How to copy all the table header common rows to its back?
Maybe copy and then convert the header rows?
Copy link to clipboard
Copied
Hi @m1b
This script probably doesn't need to be this long.
My key point, still, is to get the currently selected row.
Subsequent ones can be filled in as needed.
The header rows need to be converted to body rows first, and the operation might not need to be so complicated.
Turns out I never found a reasonable thought.
This idea is either clearer and more reasonable:
Select the header rows (or what will be header rows), convert them to body rows, then copy them, and then convert the original header rows at the top to header rows.
Copy link to clipboard
Copied
I'm trying to convert selected rows to body, and I initially converted row 2 and then row 1, and never found a problem.
But then I realized that when there is a spanning column, both rows must be converted together.
I couldn't understand your code after, I used the previous one directly.
Now it reports an error and cannot set the row type.
You're probably wondering why I'm turning the head of the table to the body of the table again.
It's because I can't control your code, but the table header lines are something I can use.
Regardless of whether the selected rows are table header rows or not (say 2 rows are selected), first convert row[0] rows[1] to body rows, then copy row[0] rows[1], then convert row[0] rows[1] to table headers.
This gives me two table headers, one real and one fake.
var headerRows = convertToRowType(selectedRows, RowTypes.HEADER_ROW);
// collect all the rows that now *should* be body rows收集所有现在*应该是正文行的行
var bodyRows = [],
myTable = headerRows[0].parent,
le = headerRows[headerRows.length - 1].index;
alert(le);
//转换表头行为表体行 covert selected rows to body row
for (var i = le + 1; i >= 0; --i) {
if (RowTypes.HEADER_ROW === myTable.rows[i].rowType)
//bodyRows.push(myTable.rows[i]);
myTable.rows[i].rowType = RowTypes.BODY_ROW;
}
Copy link to clipboard
Copied
I want to copy the table header rows.
Don't know how to do it. The previous method didn't seem to work for crossrows.
It's a bit complicated to read.
The samples are here.
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more