FIND AND CHANGE TEXT

One of the most popular of the sample scripts provided by Adobe for InDesign is FindChangeByList. The script is designed for the non-scripter in that the parameters for find/change procedures are supplied by a plain text file. This is then read by the script for performing the find/change. Each line of the text file consists of a set of 5 find/change attributes delineated by a tab.

The attributes in order are:

  • a string value “grep”, “text”, or “glyph” to define whether the search will be a literal text, a regular expression (grep), or glyph.
  • a record defining the find what string, grep expression, or glyph
  • a record defining the change to string, grep expression, or glyph
  • a record listing the options to be used in the find/change such as include footnotes, include master page items, include hidden layers, and whole word all of which take a true or false value
  • a string that acts as a comment that describes the find/change statement

At the top of the script the author provides a few examples of a typical find/change entry in the text file:

   --Very simple example:
   --text   {find what:"--"}   {change to:"^_"}	{include footnotes:true, include master pages:true, include hidden layers:true, whole word:false}   Find all double dashes and replace with an em dash.
--
--More complex example:
--text	{find what:"^9^9.^9^9"}	{applied character style:"price"}   {include footnotes:true, include master pages:true, include hidden layers:true, whole word:false}	Find $10.00 to $99.99 and apply the character style "price".

Note the use of meta characters in the second example above.

Procedure

The script reads the text file and uses the information to perform the find/change procedures. The scope of the find/change is defined by the user. If a text item, text frame, or insertion point is selected when the script is run, the user is given the option of targeting the entire document or the selection. In the event of a text frame or insertion point, this will be its parent story. This option is provided by a custom dialog which provides the following choices:

  1. Entire document
  2. Selected story or Selection (if there is a text item selected when the script is run

ROLL YOUR OWN

As a person interested in writing scripts, you may want to look at this sample script to see how it was written. Even better, you might want to write your own script and avoid the hassle of having to create a separate text file each time you want to do a find/change. For this, instead of creating a text file, provide the list of find/change parameters at the top of the script.

For a script designed to find and change a string of text, the list could be defined as follows. Note that the last item in the parameter list is itself a list of true/false values for the following, in order: include footnotes, include master pages, include hidden layers, whole word.

set myFindChangeList to {"text", {find what:"text to find"}, {change to:"text to replace"}, {true, false, false, true}}

Your script will then need to perform the following:

  • define the object to perform the find/change (this can be returned from a custom dialog)
  • clear the find text and change text preferences (set them to nothing)
  • set find what for find text preferences
  • set change to for change text preferences
  • set find change text options
  • have the object defined perform a find text
  • if text is found, have the target object defined perform a change text
  • clear the find text and change text preferences

To demonstrate how this could be done, the following skeleton for the proposed script is provided:

FindChange_List

   tell application "Adobe InDesign CC 2015"
      set myFindChangeList to {"text", {find what:"aid of their country"}, {change to:"assistance of their leaders"}, {true, false, false, true}}
      set objRef to my getTargetObject()
      copy myFindChangeList to {searchType, findRecord, changeRecord, optionList}
      if searchType is "text" then
         my doFindChangeText (objRef, findRecord, changeRecord, optionList)
      end if
   end tell
   (*Handler performs actual find/change*)    
   on doFindChangeText (objRef, findRecord, changeRecord, optionList)
      tell application "Adobe InDesign CC 2015"
         set find text preferences to nothing
         set change text preferences to nothing
         set properties of find text preferences to findRecord
         set properties of change text preferences to changeRecord
         my setOptions (optionList)
         tell objRef
            set myFoundItems to find text
            if myFoundItems is not {} then
               change text
           end if
         end tell
      end tell
      return myFoundItems
    end doFindChangeText
   (*Handler sets options for find/change*)
    on setOptions (optionList)
       tell application "Adobe InDesign CC 2015"
          tell find change text options
             set include footnotes to item 1 of optionList
             set include master pages to item 2 of optionList
             set include hidden layers to item 3 of optionList
             set whole word to item 4 of optionList
          end tell
       end tell
    end setOptions
    (*returns object for performing find change; assumes complete document is target for range*)
    on getTargetObject()
       tell application "Adobe InDesign CC 2015"
          if (count of documents) > 0 then
             return active document
          end if
       end tell
    end getTargetObject

There is a lot more that needs to be included in the getTargetObject handlert to allow for a selection option, but for now we will let this suffice.

FIND GLYPH, FIND GREP

Notice that the script skeleton was written using handlers to facilitate adding other functionality. In keeping with the sample FindChangeByList script, you could expand your script to allow grep and glyph find/change. For this, you will need to write a handler for doing a grep find/change as well as one for a glyph find/change.

In the main section of the script, add code to test to see if the first word of the list is “text”, “grep”, or “glyph”.

   if searchType is "text" then
      my doFindChangeText (objRef, findRecord, changeRecord, optionList)
   else if searchType is "grep" then
      my doFindChangeGrep (objRef, findRecord, changeRecord, optionList)
   else
      my doFindChangeGlyph (objRef, findRecord, changeRecord, optionList)
   end if 

Following the pattern for the doFindChangeText() handler, add a handler for grep (doFindChangeGrep) and one for glyph (doFindChangeGlyph). Hint: Change references to find text preferences to find grep preferences and change text preferences to change grep preferences. Also, change the find text method to find grep and change text to change grep.

Change the definition of the myFindChangeList for a grep find/change. To remove spaces after a line return the myFindChangeList variable definition could be written:

   set myFindChangeList to {"grep", {find what:"\r "}, {change to:"\r"}, {true, false, false, false}}

MULTIPLE FIND CHANGE

To provide for multiple find/change operations, the script becomes a little more involved. Essentially, all that is needed is to provide multiple lists for the myFindChangeList variable definition. Then add a repeat loop to the body of the script to iterate over the lists.

   tell application "Adobe InDesign CC 2015"
   --change this list of lists for multiple find/change operations
      set myFindChangeList to {{"text", {find what:"aid of their party"}, {change to:"assistance of their leaders"}, {true, false, false, true}},{"grep", {find what:"\r "}, {change to:"\r"}, {true, false, false, false}}}
      set objRef to my getTargetObject()
      repeat with i from 1 to length of myFindChangeList
         copy item i of myFindChangeList to {searchType, findRecord, changeRecord, optionList}
         if searchType is "text" then
            set myFoundItems to my doFindChangeText (objRef, findRecord, changeRecord, optionList)
         else if searchType is "grep" then
            set myFoundItems to my doFindChangeGrep (objRef, findRecord, changeRecord, optionList)
         else
            set myFoundItems to my doFindChangeGlyph (objRef, findRecord, changeRecord, optionList)
         end if
      end repeat
   end tell
   myFoundItems --for testing

TEST FOR TARGET OBJECT

To give the user the option of using a selected item or the entire document for the target text range, your script will need to expand on the getTargetItem handler. It first needs to check for a selection. If there is a valid selection, the script will then need to create a customDialog to provide choices for the user. You may want to look at Adobe’s sample script (FindChangeByList) to see how they wrote this portion of the code. An alternate version for the getTargetObject() follows:

(*Checks for selection and empty selection. If selected item is a text item, insertion point, or text frame, user is given option of targeting the selection or the entire document.*)
   on getTargetObject()
	set objRef to missing value
	set dialogName to "Find Change Range"
	set allowSelection to false
	tell application "Adobe InDesign CC 2015"
	   set selList to selection
		if selList is {} then set allowSelection to false
		set selItem to item 1 of selList
		if class of selItem is text frame then
		   set objRef to object reference of text 1 of selItem
		   if contents of contents of objRef is not "" then
			set allowSelection to true
		   end if
		else if class of selItem is insertion point then
		   set parentStory to parent story of selItem
		   if contents of parentStory is not "" then
			set allowSelection to true
		   end if
		end if
		if allowSelection then
		   set objRef to my customDialog("Find Change Range", selItem)
		else
		   return active document
		end if
	end tell
   end getTargetObject
   (*Gives user choice of entire document or selection for search find range*)
   on customDialog(dlgName, selItem)
      tell application "Adobe InDesign CC 2015"
	 set origLevel to user interaction level of script preferences
	 set user interaction level of script preferences to interact with all
	 set dlgRef to make dialog with properties {name:dlgName, can cancel:false}
	 tell dlgRef
	    tell (make dialog column)
	       set radioGroup to (make radiobutton group)
		   tell radioGroup
		      make radiobutton control with properties {static label:"Entire Document", checked state:true}
		      if class of parent of selItem is story then
			  make radiobutton control with properties {static label:"Selected Story"}
		      else
			  make radiobutton control with properties {static label:"Selection"}
		      end if
		   end tell--radioGroup
		end tell--dialog column
	   end tell --dialog
	   set dlgResult to show dlgRef
	   if dlgResult is true then
	      set rangeIndex to selected button of radioGroup
	      if rangeIndex is 0 then
		 set objRange to document 1
	      else if rangeIndex is 1 then
		 if class of selItem is not story then
	            set objRange to parent story of selItem
		 else
	            set objRange to selItemf
		 end if
	      end if
	   end if
	   set user interaction level of script preferences to origLevel
	   return objRange
	end tell
   end customDialog

 Custom dialog for choosing target object

USING YOUR SCRIPT

Once you test your script and have it working as desired, place the script in the Scripts Panel folder of your choice (Application or User). Make sure the target document has been saved so you can revert back to the last saved version, if needed. Then, to run the script, control-click on the script in the Scripts Panel list to edit the lists for defining the myFindChangeList variable. Save the modified script and then run. In my opinion, this is a lot easier than creating the text file and copying it to the FindChangeSupport folder inside the application’s Scripts Panel folder.

ONWARD AND UPWARD

Compare how the above skeleton script was written with that of Adobe’s sample script. Do you find one structure easier to read than the other? Besides adding handlers for findChangeGrep and findChange Glyph, what additional functionality is needed by the sample script above to make it a real world script?