ExtendScript, InDesign 2022: Return `parentPage`s of last cells of all tables

Engaged ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

I’m trying to bring this function alive:

 

$.writeln( findPagesOfLastCells() );

function findPagesOfLastCells() {
// https://community.adobe.com/t5/indesign-discussions/javascript-property-to-tell-if-text-is-in-a-continued-table/td-p/6379093
    var aDoc = app.activeDocument,
        allTables = aDoc.stories.everyItem().tables,
        thePages = []; 
    if( allTables.length > 0 ) {
        for ( var i = 0; i < allTables.length; i++ ) {
            var theTable = allTables[i];
$.writeln('***********tables************\n'+theTable);
            if ( theTable.rows.length > 1 ) {
                var lastRowPage = 0;
                lastRowPage = theTable.rows[-1].cells[-1].insertionPoints[-1].parentTextFrames[0].parentPage;
$.writeln(lastRowPage);
                thePages.push(lastRowPage.name);
$.writeln(lastRowPage.name);
            }
        }
    }
    return thePages;
}

 

 The crucial parts is here:

 

lastRowPage = theTable.rows[-1].cells[-1].insertionPoints[-1].parentTextFrames[0].parentPage;

This doesn’t work and results to undefined. It works fine with this:

theTable.rows[-1].cells[-1].insertionPoints[-1]

I get an insertion point with all properties, including a parentTextFrames property. But extending to

theTable.rows[-1].cells[-1].insertionPoints[-1].parentTextFrames[0]

I get a text frame reference with no property at all.

 

Is this a bug? Am I doing something wrong?

 

Thanks for your kind help,

Tobias

 

TOPICS
Scripting

Views

857

Likes

Translate

Translate

Report

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

correct answers 2 Correct answers

Engaged , May 14, 2022 May 14, 2022

Uwe, this is pretty nasty, puh – thank you very much for pointing this out!

So for completeness sake:

$.writeln( findPagesOfLastCells() );

function findPagesOfLastCells() {
// https://community.adobe.com/t5/indesign-discussions/javascript-property-to-tell-if-text-is-in-a-continued-table/td-p/6379093
    var aDoc = app.activeDocument,
        allTables = aDoc.stories.everyItem().tables,
        thePages = []; 
    if( allTables.length > 0 ) {
        for ( var i = 0; i < allTables.length; i++ ) {
...

Likes

Translate

Translate
Community Expert , May 16, 2022 May 16, 2022

Hi @tobias.wantzen , Uwe used the table storyOffset property answering another post this morning, and it seems like it might work here. You can get a table‘s trailing insertionPoint to find which page it ends on. Something like this:

 

var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();

alert(findPagesOfLastCells(et))


/**
* Gets the parent page of a table’s end 
* @ param a collection of tables
* @ return an array of page names 
* 
*/
function findPagesOfLastCell
...

Likes

Translate

Translate
Community Expert ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

An insertion point in a cell has no parent text frame. Tables are weird in how they interact with the DOM like that. You have to get the text frame via other means, but I forget specifically how I've done it in the past. I just know it wasn't trivial. Maybe someone else has a better, easier implementation. 

Likes

Translate

Translate

Report

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
Engaged ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Thanks, @brianp311 for your kind answer. Meanwhile I’ve done some nasty bit of code. It works, but – puh! – it’s very hacky and ugly (because intrusive):

 

$.writeln( findPagesOfLastCells() );

function findPagesOfLastCells() {
// https://community.adobe.com/t5/indesign-discussions/javascript-property-to-tell-if-text-is-in-a-continued-table/td-p/6379093
    var aDoc = app.activeDocument,
        allTables = aDoc.stories.everyItem().tables,
        thePages = []; 
    if( allTables.length > 0 ) {
        for ( var i = 0; i < allTables.length; i++ ) {
            var theTable = allTables[i];
$.writeln('***********tables************\n'+theTable);
            if ( theTable.rows.length > 1 ) {
                var lastRowPage = 0;
                var lastRowPage = getAOsParentPage( theTable.rows[-1].cells[-1].insertionPoints[-1] );
$.writeln(lastRowPage);
                thePages.push(lastRowPage.name);
$.writeln(lastRowPage.name);
            }
        }
    }
    return thePages;
}

function getAOsParentPage(theAOReference) {
    var aDoc = app.activeDocument;
    var theAO = aDoc.rectangles.add({ geometricBounds: [0,0,5,5] });
    theAO.anchoredObjectSettings.insertAnchoredObject( theAOReference, AnchorPosition.ANCHORED );
    theAO.anchoredObjectSettings.releaseAnchoredObject();
    var theAOparentPage = theAO.parentPage;
    theAO.remove();
    return theAOparentPage;
}

 

If someone has a better idea, it’ll be very welcome ...

 

Thanks, Tobias

Likes

Translate

Translate

Report

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 ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Pretty sure that's along the lines of how I've implemented it before. Yeah, not pretty. 

Likes

Translate

Translate

Report

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 ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Hi Tobias,

I think it will make a big difference when allTables is an array and not a collection of tables:

var allTables = 
aDoc.stories.everyItem().tables.everyItem().getElements();

 

Other things to think about:

Are there perhaps graphic cells?

( I guess not, because your insertion point seems to be valid. )

 

Is a table perhaps in overset?

Is the last cell of a given row in overset?

 

Are there any footer rows in the table?

You will get the wrong page as the last one if the table runs through several text frames on different pages.

 

Regards,
Uwe Laubender

( Adobe Community Professional )

Likes

Translate

Translate

Report

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
Engaged ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

My code wasn’t meant to be bullet-proof, but to show, that parentTextFrame doesn’t work in tables and to ask for help on this topic. And my second code showed an approach to deal with that limitation as a workaround.

I’m absolutely with you: To be bullet-proof there have to be much more fine grinding. Thanks for your annotations showing manyfold directions, which has to be taken into account.

 

But let me ask one question:

> I think it will make a big difference when allTables is an array and not a collection of tables:

Why is using array over object better? Are there any other reasons except performance?

 

Thanks,

Tobias

Likes

Translate

Translate

Report

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 ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

"Why is using array over object better?"

The elements in that array are resolved. So you can be sure that the target elements actually result in meaningful properties/values if a given situation allows this.

 

Also read both articles about everyItem() from Marc Autret:

https://www.indiscripts.com/post/2010/06/on-everyitem-part-1

https://www.indiscripts.com/post/2010/07/on-everyitem-part-2

 

getElements() is a topic of part 2, but don't miss part 1; it's worth the effort!

 

Quoting Marc from part 2:

"…A common way to explicitly resolve a specifier and push the resolved data into an Array is to use spec.getElements(). This method performs two operations:
(1) It causes an automatic resolution of spec;
(2) It creates and returns an Array that contains each spec's receiver converted into a resolved specifier.

Moreover, getElements() is much more reliable than any other command because it actually resolves or updates the specifier from its original path…"

 

Regards,
Uwe Laubender

( Adobe Community Professional )

 

Likes

Translate

Translate

Report

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 ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Brian: "An insertion point in a cell has no parent text frame. "

 

Hi Brian,

of course it has.

If that's not the case, the insertion point is in overset text.

 

Regards,
Uwe Laubender

( Adobe Community Professional )

Likes

Translate

Translate

Report

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 ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Hi Tobias,

theTable.rows[-1]

is always in the first frame of that table if that row has at least one footer row.

 

So best change that to:

theTable.rows[ -1-theTable.footerRowCount ]

 

Regards,
Uwe Laubender

( Adobe Community Professional )

Likes

Translate

Translate

Report

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
Engaged ,
May 14, 2022 May 14, 2022

Copy link to clipboard

Copied

Uwe, this is pretty nasty, puh – thank you very much for pointing this out!

So for completeness sake:

$.writeln( findPagesOfLastCells() );

function findPagesOfLastCells() {
// https://community.adobe.com/t5/indesign-discussions/javascript-property-to-tell-if-text-is-in-a-continued-table/td-p/6379093
    var aDoc = app.activeDocument,
        allTables = aDoc.stories.everyItem().tables,
        thePages = []; 
    if( allTables.length > 0 ) {
        for ( var i = 0; i < allTables.length; i++ ) {
            var theTable = allTables[i];
            if ( theTable.rows.length > 1 ) {
                var lastRowPage = 0;
                var lastRowPage = getAOsParentPage( theTable.rows[-1-theTable.footerRowCount].cells[-1].insertionPoints[-1] );
                thePages.push(lastRowPage.name);
            }
        }
    }
    return thePages;
}

function getAOsParentPage(theAOReference) {
    var aDoc = app.activeDocument;
    var theAO = aDoc.rectangles.add({ geometricBounds: [0,0,5,5] });
    theAO.anchoredObjectSettings.insertAnchoredObject( theAOReference, AnchorPosition.ANCHORED );
    theAO.anchoredObjectSettings.releaseAnchoredObject();
    var theAOparentPage = theAO.parentPage;
    theAO.remove();
    return theAOparentPage;
}

But please be aware what Uwe pointed out in his first post of all further issues, which may be important for reusing this code in your own context!

Likes

Translate

Translate

Report

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 ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Hi @tobias.wantzen , Uwe used the table storyOffset property answering another post this morning, and it seems like it might work here. You can get a table‘s trailing insertionPoint to find which page it ends on. Something like this:

 

var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();

alert(findPagesOfLastCells(et))


/**
* Gets the parent page of a table’s end 
* @ param a collection of tables
* @ return an array of page names 
* 
*/
function findPagesOfLastCells(t){
    var a = new Array()
    var ti, psi;

    for (var i = 0; i < t.length; i++){
        ti = t[i].storyOffset.index;
        //the parent page of the insertion point at the end of the table
        psi = t[i].parent.parentStory.insertionPoints[ti+1].parentTextFrames[0].parentPage
        a.push(psi.name)
    }; 
    return a
}

 

Screen Shot 41.png

Likes

Translate

Translate

Report

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 ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Hi Rob,

thank you for this! It's working very well with my test documents.

Your algorithm is superior* to the one where one is looking at the parent page of an insertion point in a table cell of the last body row. If Tobias does not mind I will mark your answer as the correct one.

 

Thanks,
Uwe Laubender
( Adobe Community Professional )

 

* EDIT: I have to add a comment and correct myself on this.

 

Rob's algorithm detects the right page if:

[1] There is no text after the table in the story

[2] If at least one character is rendered in the same text frame with the last row of the table.

 

But if the first character that comes after the table is rendered in a different text frame on a different page one can see that the position of the insertion point moved along with the character and the wrong page is detected as the last one of the table.

 

See Rob's reply her in the same discussion:

https://community.adobe.com/t5/indesign-discussions/extendscript-indesign-2022-return-parentpage-s-o...

 

Likes

Translate

Translate

Report

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 ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Thanks uwe, I was wondering why there wasn’t an insertionPoint property for tables until you pointed out the storyOffset in the other post.

Likes

Translate

Translate

Report

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 ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Hi Rob,

I never thought of inspecting the insertion point after the character that is the actual table, because that character is positioned in a different text frame if you have two threaded text frames where one table is running through.

 

Test this with:

characters[0].parentTextFrames.length // Returns 1

characters[0].parentTextFrames[0].parentPage.name

// Returns the page name of the page at the beginning of the table.

 

But well, let's test if this still true when there is more text after that table.

Alright. Tested just this and your algorithm is still working as expected:

AlgorithmByRob-LastPageOfTableTest-ResultStill-OK.PNG

Above: A table with one header row, one footer row and two body rows that is running through two text frames on different pages.

 

Regards,
Uwe Laubender
( Adobe Community Professional )

Likes

Translate

Translate

Report

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 ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

I think it works because the storyOffset insertion point is to the left of the table—not above it— and the next insertion point is to the right of the table—not below it.

 

I can see that if I select the insertionPoints:

 

The storyOffset selected

 

var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();
var ti = et[0].storyOffset.index;
var psi = et[0].parent.parentStory.insertionPoints[ti]
psi.select()

 

 

Screen Shot 58.png

 

And the next insertionPoint selected:

 

var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();
var ti = et[0].storyOffset.index;
var psi = et[0].parent.parentStory.insertionPoints[ti+1]
psi.select()

 

Screen Shot 57.png

 

 

I don‘t think we have to worry about parentTextFrames being longer than 1 because the insertionPoint can only be in one frame—if the selection was something like a paragraph it might crossover multiple frames.

Likes

Translate

Translate

Report

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 ,
May 18, 2022 May 18, 2022

Copy link to clipboard

Copied

LATEST

Something like this must have been what I was thinking of when I erroneously said there was no parent text frame of a cell insertion. Or I didn't have enough coffee yet. Good stuff. 

Likes

Translate

Translate

Report

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
Engaged ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Thanks Uwe for marking this as solution.

But in my eyes, there are two solutions/correct answers in this thread, because the two approaches differ in what you get in the end and it depends on your goals.

Thanks,
Tobias

Likes

Translate

Translate

Report

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 ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

Hi Tobias,

no issue to mark a second reply as the correct answer.

I'll do that for you with your reply with the full script code.

 

Regards,
Uwe Laubender
( Adobe Community Professional )

Likes

Translate

Translate

Report

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
Engaged ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

Oh – I wasn’t aware, that this is possible! Thanks!

Likes

Translate

Translate

Report

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
Engaged ,
May 16, 2022 May 16, 2022

Copy link to clipboard

Copied

Thanks, Rob – this is genious!

Actually I thought about a similar approach either, but abandoned it. (I thought about looking for the paragraph sign `\r` after the table or the first character of the paragraph after the table, because finding a table start and its contents or the character behind the table’s paragraph is comparatively easy. Alternatively we could compare your

var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();

to

app.findTextPreferences.findWhat = "<0016>";

and see, which approach is faster.)

And I’ve abandoned it, because I really wanted to know, on which page a specific row (or cell) is placed. Looking for the insertion point after the table only tells me, on which page the table ends.

 

Thanks,

Tobias

Likes

Translate

Translate

Report

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 ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

 

app.findTextPreferences.findWhat = "<0016>";

 

 

Hi Tobias,

this also has its charme.

Especially if you want to find nested tables. Tables that were added to text cells.

From the found text the table is only one step away:

 

found0016Characters[i].tables[0]

 

 

Fun facts

You can even find tables with a minimum of two body rows:

 

app.findTextPreferences.findWhat = "<0016><0017>";

 

 

Or with a minimum of three body rows:

 

app.findTextPreferences.findWhat = "<0016><0017><0017>";

 

 

Warning!

Be very careful with Find Text <0016> . Do not dare to replace a found instance with nothing or even with text.

Just tested that in the GUI. InDesign crashed instantly… The same with Find Text <0017> . That's the control character "End Of Transmission Block".

 

NOTE: If you look for <0017> only (without <0016> in front) you probably will find more than one instance per table. It depends on the number of body rows in the table. With two body rows, one instance of <0017> is found for the table. With three body rows, two instances of <0017> are found for the same table. Etc. etc.

 

It seems that the result of a Text Find with <0017> returns a "character", but it's a very strange character, a "ghost character" with only one insertion point. The position of that insertion point is the one exactly after the table character. It's always that position; regardless how many rows are in the table and so how many instances of <0017> for that particular table are found.

 

Regards,
Uwe Laubender

( ACP )

Likes

Translate

Translate

Report

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 ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

Seems like the problem with getting the character after the table is the table could have space after applied, which might force the character to the next page:

Screen Shot 59.png

 

 

Screen Shot 60.png

 

Likes

Translate

Translate

Report

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 ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

Hi Rob,

thanks for this test! Did also some testing and yes, the "position" of the insertion point we do exploit will move along with the text that is after the table. This is also the case with a default value for Space After and other reasons so that no character of the text following the table can be rendered in the text frame with the last row of the table.

 

So back to Tobias' algorithm?!

 

Regards,
Uwe Laubender
( Adobe Community Professional )

Likes

Translate

Translate

Report

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 ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

I don’t see that, in my last example the insertionPoint stays with the table even when the return after is forced to the next page via a table space after:

 

Screen Shot 61.png

 

Also, if I make a textframe and insert a single table and no additional text I get 1 character and 2 insertion points no matter how much space after is set in the table options:

 

//a selected textframe with one table inserted with any amount of space after
var s=app.activeDocument.selection[0];  

$.writeln(s.parentStory.tables.length) //returns 1
$.writeln(s.parentStory.characters.length) //returns 1
$.writeln(s.parentStory.insertionPoints.length) //returns 2, before and after the table

 

Likes

Translate

Translate

Report

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
Engaged ,
May 17, 2022 May 17, 2022

Copy link to clipboard

Copied

I think, we’re done. Both approaches work. Thanks to the both of you for your help, investigations, and testing. It’s an interesting topic, because it’s on the edge of InDesign’s scripting capabilities.

I added a uservoice for this:

https://indesign.uservoice.com/forums/913162-adobe-indesign-sdk-scripting-bugs-and-features/suggesti...

(Although I was a bit unshure, where to place scripting requests: in "Adobe InDesign: SDK/Scripting Bugs and Features" or in "Adobe InDesign: Feature Requests" ...)

Likes

Translate

Translate

Report

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