20 Replies Latest reply on Nov 17, 2011 11:04 AM by Michael Müller-Hillebrand

    Find/Replace?

    JoeMihalik Level 1

      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

        • 1. Re: Find/Replace?
          Russ Ward Level 4

          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

          • 2. Re: Find/Replace?
            JoeMihalik Level 1

            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

            • 3. Re: Find/Replace?
              Russ Ward Level 4

              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

              • 4. Re: Find/Replace?
                Russ Ward Level 4

                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;

                 

                }

                1 person found this helpful
                • 5. Re: Find/Replace?
                  JoeMihalik Level 1

                  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

                  • 6. Re: Find/Replace?
                    Russ Ward Level 4

                    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

                    • 7. Re: Find/Replace?
                      JoeMihalik Level 1

                      Thanks Russ the well commented code makes it obvious - should be easy to translate into ExtendScript.

                       

                      This is a big help.

                      Jim

                      • 8. Re: Find/Replace?
                        Ian Proudfoot Level 3

                        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;
                        }

                        • 9. Re: Find/Replace?
                          Mark Southee Level 2

                          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

                          • 10. Re: Find/Replace?
                            Michael Müller-Hillebrand Level 4

                            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

                            • 11. Re: Find/Replace?
                              Ian Proudfoot Level 3

                              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

                              • 12. Re: Find/Replace?
                                Mark Southee Level 2

                                Thanks Ian/Michael, I'll try this out tomorrow.

                                 

                                Many thanks

                                 

                                Mark

                                • 13. Re: Find/Replace?
                                  Mark Southee Level 2

                                  Must be doing something wrong, but when I run the script from the ExtendScript editor , I get 'Result: undefined' in the console.

                                  • 14. Re: Find/Replace?
                                    Michael Müller-Hillebrand Level 4

                                    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

                                    • 15. Re: Find/Replace?
                                      Mark Southee Level 2

                                      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

                                      • 16. Re: Find/Replace?
                                        Ian Proudfoot Level 3

                                        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

                                        • 17. Re: Find/Replace?
                                          Mark Southee Level 2

                                          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

                                          • 18. Re: Find/Replace?
                                            Ian Proudfoot Level 3

                                            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.

                                            • 19. Re: Find/Replace?
                                              SQUIDDS Level 1

                                              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

                                              • 20. Re: Find/Replace?
                                                Michael Müller-Hillebrand Level 4

                                                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