Accessing isValid property updates object reference?

Hello everyone!
I’m integrating API which will check text directly in indesign and give you recommendations to correct it. (like Grammarly).

So, after receiving response with typos my app starts to select words (typos) and highlight them with color. Each typo in UI have delete button, and while my plugin deleting typo from UI I also want to reset text highlighted in Indesign. For that I have this function:

function resetCharacterStyles(texts: Array<Text> ) {
  const defaultCharStyle = app.activeDocument.characterStyles.itemByName("[None]");
  
  texts.forEach((text) => {
    if (!text.isValid) {
    // Will have more logic to notify user if some text failed to reset highlighting (Maybe the deleted this text by themselves).
       return;
    }
    app.select(text);
    text.applyCharacterStyle(defaultCharStyle);
    text.clearOverrides(indesign.OverrideType.CHARACTER_ONLY);
  })
 }

Here is the interesting part, lets say, user added few more words before highlighted typo. If I’m accessing isValid property in highlighted typo, the text object which I will select next will have updated reference… Let me explain it below.

// Let's say, plugin selected this sentence: "Your the one who should".
// Plugin stores selected typos in object appData = {typo: {...otherInfo, selectedInIndesign: object[] }}
const selection = appData.typo.selectedInIndesign[0];

console.log(selection.contents) // Your the one who should

// Now let's add few more words in Indesign. Our text in Indesign now looks like this -> "Added few more words Your the one who should".

// Still selects "Your the one who should." even though we've added more words and text shifted.
console.log(selection.contents) 


// Accessing isValid property
selection.isValid

// Now it will show "Added few more words Your"
console.log(selection.contents)

Thats how it looks in Indesign.

image

Is this a bug or does isValid property works like that? And is there any other option to check text validity without making it to lose reference?

P.S: Sorry for possible mistakes.

Thank you.

1 Like

My understanding is that accessing some (most i think?) properties will “resolve” the reference.

Can you try

console.log(selection.toSpecifier())

before and after. That might be revealing. But might not, because after all the reference is probably something like

(/document[@id=1]//story[@id=251]/character[98] to /document[@id=1]//story[@id=251]/character[194])

Sorry I know none of that is helpful, I think your reference is wrecked as soon as the user adds more characters before it. You’ll have to get it again I think.

Edit: I think the point is that the first console.log you did, even though it showed the expected string, was already broken; you just didn’t know it yet.

1 Like

I haven’t heard about specifier before your answer. For me, specifier would show the same character indexes before and after checking object with isValid. I hope I’m using it right :sweat_smile:

May be there are other properties that would “resolve” the reference, but so far I’m glad that it saves previous reference (if you don’t try to access isValid property), this way I can safely reset character styles.

At first, I thought that may be If user will manually delete highlighted text, reference would turn invalid. However, it doesn’t always work like that (pretty much in almost all cases it remains valid). But even if reference would turn invalid, I just wrapped code inside my function with try catch to show alert with something like “Failed to reset character styles for some texts, please do it manually” and called it a day.

Thank you for your answer and attempt to help!

Note: The following is a fairly simplified explanation and there is a lot more nuance to the mechanism.
Let’s say you have something ike let txt=app.selection[0]. In the internals of the JS machine, this is converted to a specifier: txt=(/document[@id=1]//story[@id=251]/character[98] to /document[@id=1]//story[@id=251]/character[120]).
As you can see, the story and the document parts are accessed by their ID, which is stable. But characters (and all other “text objects” - paragraphs, lines etc) do not have an ID and are instead accesed by their index, which is dynamic. So, if after you initialized the txt variable, some action, either scripted or user-driven has added or deleted text in that story[@id=251], the actual text that the variable is pointing to might not be the one you expect (the one that has been selected originally). If you are lucky, you might get a error because the story now has less than 120 characters (thus you are trying to access an non-existing, invalid character), but in a lot of cases you end up silently and mistakenly modifying the wrong text.

The simplest solution, and this should be a reflex any time you are dealing with dynamic objects, is to process the data backward.

Your explanation about specifier was very helpful, thank you for that!

And, as a person without any professional experience in this field I appreciate your tip about processing data backwards, i will remember it. But there is still some thing I don’t understand quite well…Does it matter if we will process data backwards in my example? In any case, after adding new words, first and last indexes would shift and point to a different characters. I’m afraid, I haven’t fully understood the whole meaning of “process the data backward”, could you elaborate it if possible?

It means that you process an array or collection from back to front:

for (i = things.length-1; i >= 0; i--) {
  . . .
}