Copy link to clipboard
Copied
I have looked all thru the documentation including the FDK and don't really see a way to do a global search and replace in extendscript. The FDK documentation refers to F_APIFIND in the index and TOC for FIND/REPLACE but I don't see the paramters to set for it when I look there. I assume there is a way to do it, and I'm just not smart enough to see it. Any clues where I should look or samples somewhere?
TIA - Jim
Jim,
I see I didn't read your post closely enough. My apologies. Indeed, you'll need to control the iteration and string replacement yourself. It doesn't take too much code to do that, though. That's with the FDK... again, I don't know about ExtendScript.
Russ
Copy link to clipboard
Copied
Hi Joe,
I can't speak for ExtendScript, but F_ApiFind() is well-defined on page 102 of the FDK Programmers Reference that I have (as installed with the FDK10). I have used it myself without issue. Are you sure you are looking in the right book?
Russ
Copy link to clipboard
Copied
I have looked and perhaps I just don't understand - but I don't see anything that lets me specify a replacement string. Perhaps there is no such thing and I have to iterate thru the finds and replace the text myself? Seems like there is a find/change dialog so there should be some equivalent functionality to that?
Thanks
Copy link to clipboard
Copied
Jim,
I see I didn't read your post closely enough. My apologies. Indeed, you'll need to control the iteration and string replacement yourself. It doesn't take too much code to do that, though. That's with the FDK... again, I don't know about ExtendScript.
Russ
Copy link to clipboard
Copied
Hi Jim,
I felt bad for accusing you of not being able to read. In an attempt to make up for that, I worked up a quick sample of how to do a search and replace with the FDK. I hope it may help some.
Russ
IntT FindAndReplaceString(F_ObjHandleT docId,
F_ObjHandleT flowId,
StringT findString,
StringT replaceString,
IntT considerCase)
{
F_TextRangeT tr,
restoreTR;
F_ObjHandleT frameId = 0;
IntT loopCounter = 0,
replacementCounter = 0;
F_PropValsT findParams;
//if the flow ID is null, assume the main flow
if(docId && !flowId)
flowId = F_ApiGetId(FV_SessionId, docId, FP_MainFlowInDoc);
//get the first text frame in the flow, a starting point to
//find the first paragraph
if(flowId)
frameId = F_ApiGetId(docId, flowId, FP_FirstTextFrameInFlow);
//At this point, if we don't have a frame ID, might as well abort.
if(!frameId)
{
F_ApiAlert("Could not find a starting point for the search. Cannot continue.",
FF_ALERT_CONTINUE_WARN);
return replacementCounter;
}
//store the original text selection as an amenity to restore after the action.
restoreTR = F_ApiGetTextRange(FV_SessionId, docId, FP_TextSelection);
//now, set up the starting text range as the very beginning
//of the flow. We'll move straight from beginning to end.
tr.beg.objId = tr.end.objId = F_ApiGetId(docId, frameId, FP_FirstPgf);
tr.beg.offset = tr.end.offset = 0;
//set up our find parameters. We want to configure it to look
//for a string and perhaps be case sensitive. We don't need
//the find to wrap because we are controlling the flow from
//beginning to end.
findParams = F_ApiAllocatePropVals(2);
findParams.val[0].propIdent.num = FS_FindText;
findParams.val[0].propVal.valType = FT_String;
findParams.val[0].propVal.u.sval = F_StrCopyString(findString);
findParams.val[1].propIdent.num = FS_FindCustomizationFlags;
findParams.val[1].propVal.valType = FT_Integer;
if(considerCase)
findParams.val[1].propVal.u.ival = FF_FIND_CONSIDER_CASE;
else
findParams.val[1].propVal.u.ival = 0;;
//initialize the errno global, which will be used to
//track the progress of the find and replace
FA_errno = FE_Success;
//and do an initial find to get started.
tr = F_ApiFind(docId, &tr.beg, &findParams);
//now, run the find and replace loop as long as we keep finding things.
//The loop counter is just an emergency back door in case something
//goes critically wrong and causes an endless loop.
while(FA_errno == FE_Success && loopCounter++ < 10000)
{
//set up the text range to clear the original text
F_ApiSetTextRange(FV_SessionId, docId, FP_TextSelection, &tr);
//clear it
F_ApiClear(docId, 0);
//insert the new text. We should be able to use the
//original beginning of the text range where the old text was
//found.
F_ApiAddText(docId, &tr.beg, replaceString);
//now, lets jimmy the text range in memory to place it directly
//after the string we just inserted, so the find picks back up after that.
tr.beg.offset += F_StrLen(replaceString);
//increment our return counter
if(FA_errno == FE_Success) replacementCounter++;
//...and find the next instance. We'll reset FA_errno again just in case
//something screwy happened while we were replacing text.
FA_errno = FE_Success;
tr = F_ApiFind(docId, &tr.beg, &findParams);
}
//done with this.
F_ApiDeallocatePropVals(&findParams);
//we're done. Restore the document to it's original area of display
F_ApiSetTextRange(FV_SessionId, docId, FP_TextSelection, &restoreTR);
F_ApiScrollToText(docId, &restoreTR);
return replacementCounter;
}
Copy link to clipboard
Copied
Rus - that's awesome - thanks.... That is a few more than a couple of lines tho...
Not sure I would have got there on my own.
Jim
Copy link to clipboard
Copied
Jim,
Text manipulation turns out to be one of the more complicated things to do with the API, something which is one of the easiest things to do in the GUI. It takes some complexity to replicate what you can easily do with eyes, a screen, and a mouse. Once you get your mind around how to operate and manipulate the TextRangeT structure, though, it becomes much easier. Let me know if any part is confusing and/or you need further clarification.
Russ
Copy link to clipboard
Copied
Thanks Russ the well commented code makes it obvious - should be easy to translate into ExtendScript.
This is a big help.
Jim
Copy link to clipboard
Copied
Here's a version of Russ's find/change function in ExtendScript. It may be useful to see the differences.
-Ian
function FindAndReplaceString(activeDoc, flow, findString, replaceString, considerCase) {
var tr = new TextRange();
var restoreTR, frame = 0;
var loopCounter = 0, replacementCounter = 0;
var findParams = new PropVals();
//if the flow object is not valid, assume the main flow
if(activeDoc.ObjectValid() && !flow.ObjectValid()){
flow = activeDoc.MainFlowInDoc;
}
//get the first text frame in the flow, a starting point to
//find the first paragraph
if(flow.ObjectValid()){
frame = flow.FirstTextFrameInFlow;
}
//At this point, if we don't have a frame object, might as well abort.
if(!frame.ObjectValid()){
Alert("Could not find a starting point for the search. Cannot continue." , Constants.FF_ALERT_CONTINUE_WARN);
return replacementCounter;
}
//store the original text selection as an amenity to restore after the action.
restoreTR = activeDoc.TextSelection;
//now, set up the starting text range as the very beginning
//of the flow. We'll move straight from beginning to end.
tr.beg.obj = tr.end.obj = frame.FirstPgf;
tr.beg.offset = tr.end.offset = 0;
//set up our find parameters. We want to configure it to look
//for a string and perhaps be case sensitive. We don't need
//the find to wrap because we are controlling the flow from
//beginning to end.
findParams = AllocatePropVals(2);
findParams[0].propIdent.num = Constants.FS_FindText;
findParams[0].propVal.valType = Constants.FT_String;
findParams[0].propVal.sval = findString;
findParams[1].propIdent.num = Constants.FS_FindCustomizationFlags;
findParams[1].propVal.valType = Constants.FT_Integer;
if(considerCase){
findParams[1].propVal.ival = Constants.FF_FIND_CONSIDER_CASE;
}
else{
findParams[1].propVal.ival = 0;
}
//initialize the errno global, which will be used to
//track the progress of the find and replace
FA_errno = Constants.FE_Success;
//and do an initial find to get started.
tr = activeDoc.Find(tr.beg, findParams);
//now, run the find and replace loop as long as we keep finding things.
//The loop counter is just an emergency back door in case something
//goes critically wrong and causes an endless loop.
while(FA_errno === Constants.FE_Success && loopCounter++ < 1000){
//set up the text range to clear the original text
activeDoc.TextSelection = tr;
//clear it
activeDoc.Clear(0);
//insert the new text. We should be able to use the
//original beginning of the text range where the old text was
//found.
activeDoc.AddText(tr.beg, replaceString);
//now, lets jimmy the text range in memory to place it directly
//after the string we just inserted, so the find picks back up after that.
tr.beg.offset += replaceString.length;
//increment our return counter
if(FA_errno === Constants.FE_Success){
replacementCounter++;
}
//...and find the next instance. We'll reset FA_errno again just in case
//something screwy happened while we were replacing text.
FA_errno = Constants.FE_Success;
tr = activeDoc.Find(tr.beg, findParams);
}
//we're done. Restore the document to it's original area of display
activeDoc.TextSelection = restoreTR;
activeDoc.ScrollToText(restoreTR);
return replacementCounter;
}
Copy link to clipboard
Copied
Hi Ian, I was wondering whether this script could be adopted to do a search for text string with a change for a variable definition?
Mark
Copy link to clipboard
Copied
Mark,
This seems to be pretty easy, although it took me a few moments to find the correct command. Untested:
Replace the line
activeDoc.AddText(tr.beg, replaceString);
with
activeDoc.NewAnchoredFormattedVar('NAME_OF_VARIABLE_FORMAT', tr.beg);
Try it,
- Michael
Copy link to clipboard
Copied
Hi Mark,
Yes that's easy to do:
Replace the line: activeDoc.AddText(tr.beg, replaceString);
with
var newVar = activeDoc.NewAnchoredFormattedVar('Current Date (Long)', tr.beg);
var varLength = newVar.TextRange.end.offset - newVar.TextRange.beg.offset;
then replace the line: tr.beg.offset += replaceString.length;
with
tr.beg.offset += varLength;
In this example I have hard coded the fm variable format name, but it would be better to pass it into the function as a variable.
Ian
Copy link to clipboard
Copied
Thanks Ian/Michael, I'll try this out tomorrow.
Many thanks
Mark
Copy link to clipboard
Copied
Must be doing something wrong, but when I run the script from the ExtendScript editor , I get 'Result: undefined' in the console.
Copy link to clipboard
Copied
Mark,
This is not relevant, this is the return value to the ExtendScript shell (if it may be called like this). It is rather good news, as there seems to be no syntax or run-time error.
The main question is: Did it work in the document?
- Michael
Copy link to clipboard
Copied
Hi Michael,
No it didn't work in the document. I even opened a new doc and imported the Variables from an existing one, to see if that was causing anything (conditional text etc), but it doesn't work on either.
Mark
Copy link to clipboard
Copied
Hi Mark,
How re you calling the function? Do you have a line such as the following?
FindAndReplaceString(activeDoc, flow, findString, replaceObject, considerCase);
Each of the arguments must be defined before you call the function. In this case the replaceObject is the name of the fm Variable type (the equivalent change would need to be made in the body of the function). Also as this function is being called directly, without being assigned to a variable it will show the return value in the ESTK JavaScript console on completion.
I have tested the revised version and it works as expected.
Ian
Copy link to clipboard
Copied
Hi Ian,
Ok, perhaps I'm not understanding how the script works (a bit new to this!)
I wondered whether the replaceString in the original version was needed, being as you removed the reference to it when referencing the Variable name.
I think where I'm stuck is setting up the find parameter for the text I need to replace (looking at your original for text find and replace, I couldn't work that out)
Thanks for being patient with me!!
Mark
Copy link to clipboard
Copied
No problem Mark,
As this is an example function, rather than a complete working script, some parts were not explained. For maximum flexibility you would probably want this function to handle any type of replace object. However, to get this working let's start with the arguments for the function. You will need to define these:
var activeDoc = app.ActiveDoc;
var flow = activeDoc.MainFlowInDoc;
var findString = 'Your search text';
var replaceObject = 'YourVariableName';
var considerCase = 0;
In the body of the function make sure that the replaceObject is used:
var newVar = activeDoc.NewAnchoredFormattedVar(replaceObject, tr.beg);
Don't worry about needing more detail, we all have to start somewhere. The only thing I would say is to start a new thread next time as we are moving away from the original topic.
Copy link to clipboard
Copied
Ian,
It seems the constants for the FS_FindCustomizationFlags are not defined in ExtendScript:
Constants.FF_FIND_CONSIDER_CASE (0x01)
Constants.FF_FIND_WHOLE_WORD (0x02)
Constants.FF_FIND_USE_WILDCARDS (0x04)
Constants.FF_FIND_BACKWARDS (0x08)
So if you want to combine Consider Case and Whole Word, you would have to write
findParams[1].propIdent.num = Constants.FS_FindCustomizationFlags;
findParams[1].propVal.valType = Constants.FT_Integer;
findParams[1].propVal.ival = 1 | 2;
BTW, I find the full property list handling awful complidated… and this is an example where FrameScript is a lot easier and less error-prone.
- Michael
Copy link to clipboard
Copied
Jim, do you know the features of the Finalyser to find/replace formats, element definitions, words, phrases, find index and brand new conditional text with conditional format.
All is to use in a document or in a book. It's really great.
- Georg