MUCH USED HANDLERS

In our previous blog we started a script that creates a document prepared to receive XML import. Because it used a number of handlers, many of which can be used for a number of other scripts, it was our decision to leave these handlers for another post. This way we could do more than just list the code. We will start with the handler docFromPresetMake.

DOCFROMPRESETMAKE

As the name implies, this handler creates a document preset and makes a document using the preset. For our example, the property values for the document preset are contained in a list at the top of the script. Of course, defining variables at the top of a script is just one step in the script-building process to be replaced eventually by a custom dialog. The values for the page margins are in a list in the order of top, left, bottom, right.

Before a preset is created, the handler checks to see if there is a document preset with the same name. If there is one, the user is given the option of using or updating the existing preset. How the script responds to the user’s answer to the prompt takes a little thought. The logic we came up with was to use a variable, presetToSet, that would act as a flag to indicate if there is an existing preset that will be updated or a new preset that needs to have its properties defined. If the value for the variable is not missing value then the properties for presetToSet are set. In any event, a new preset or existing preset, updated or not, is used to create a document. The reference to the document created is then passed back to the statement that calls the handler.

set presetName to "BizCard_6up"
(*Parameters: page width, page height, marginList, facing pages, 
master frame, number pages, columnCount*)
set propList to {48, 44, {2, 2, 2, 2}, false, true, 1, 1}

The call to the handler receives the result in the variable docRef. We place it inside a try/error trap.

try
set docRef to docFromPresetMake (presetName, propList)
on error errStr
activate
display alert "Error: " & errStr
end try
(*Returns reference to document created using the document preset named. If preset does not
exist, it is created. If it does exist user is given option of updating.*)
on docFromPresetMake(presetName, propList)
   tell application "Adobe InDesign CC 2019"
	set measurement unit of script preferences to picas
	if modal state = true then
	   error "Please close dialogs before running script"
	end if
	set presetToSet to missing value
	if exists document preset presetName then
	   --get verification from user
	   set doContinue to my doConfirm("Document Preset " & presetName & ¬ 
" exists " & return & "Do you want to update the existing preset?")
	   if doContinue then
		set presetToSet to document preset presetName
	   end if
	else
	   set presetToSet to make document preset with properties {name:presetName}
	end if
	if presetToSet is not missing value then
	   copy propList to {pgWid, pgHgt, mList, doFacing, masterFrame, numPages, colCount}
	      if pgWid > pgHgt then
		 set doOrient to landscape
	      else
		 set doOrient to portrait
	      end if
	      copy mList to {mTop, mLeft, mBot, mRight}
	      set marginPrefs to {top:mTop, left:mLeft, bottom:mBot, right:mRight, column count:colCount}
	      set properties of presetToSet to {page width:pgWid, page height:pgHgt, intent:print intent, ¬
 top:mTop, left:mLeft, bottom:mBot, right:mRight, page orientation:doOrient, facing pages:doFacing, ¬
create primary text frame:masterFrame, pages per document:numPages, column count:colCount}
	end if
	   set presetRef to document preset presetName
	   set docRef to make document with properties {document preset:presetRef}
	end tell
   return docRef
end docFromPresetMake
(*Gets confirmation from user*)
on doConfirm(myMsg)
   set userResponse to display alert myMsg buttons {"Yes", "No"} 
   return button returned of userResponse = "Yes"
end doConfirm 

Challenge: What would be your solution for handling the situation presented by an existing same-named document preset? Alternatively, you might give the user the option of submitting another name. But this could be a little tricky since there could be the possibility of an existing preset for the new name submitted.

DOCUMENT FRAMEWORK

Once the document is created, the work begins in creating guides, and a grid of threaded text containers to receive the imported XML. This all takes place on the document’s default master spread.

Master Guides

The code for the masterGuides handler is straight forward. It simply repeats through a list of values for both the vertical guides and horizontal guides and creates the guides at the positions specified. The problem is in coming up with the values for the lists. This takes a little math and is a subject we would like to treat thoroughly in the next blog post. Notice that the measurement unit for the positions are the same as that established for script preferences: picas. This is an easy measurement to use since this works well for page margins as well as business card sizes (21 picas wide by 12 picas high).

With the reference to the document and values for positions horizontal (hList) and vertical (vList), the following handler does the work:

--measurements are in picas
set vList to {11, 23, 25, 34}
set hList to {14, 16, 28, 30, 42}
set pageRef to masterGuides(docRef, vList, hList)
(*Creates guides on page 1 of master spread 1 using position values in vlist and hList*)
on masterGuides(docRef, vList, hList)
   tell application "Adobe InDesign CC 2019"
      set measurement unit of script preferences to picas
      set pageRef to page 1 of master spread 1 of docRef
      tell pageRef
	 repeat with i from 1 to length of vList
	    make guide with properties {guide type:ruler, location:item i of vList, orientation:vertical}
	 end repeat
	 repeat with i from 1 to length of hList
	    make guide with properties {guide type:ruler, location:item i of hList, orientation:horizontal}
	 end repeat
      end tell
   end tell
   return pageRef
end masterGuides 

masterFrames

To create the text frames for the master page, the geometric bounds for the items are returned from another handler, getBounds (see below). Unique to the masterFrames handler is the fact that the first frame of the grid is actually the primary text frame for the master page. This is needed to support the method used to set up the structure on pages that are created as needed for the imported XML. As the remaining text frames for the master page are created they are threaded to the previous text frame.

getBounds

The getBounds handler is a much used handler as many are the scripts that require a grid of page items to be created. It returns a list of lists with each item in the list being the geometric bounds for an item in the grid. It requires a list of the bounds for the containing area, plus a list that contains the values for number of rows (nRows), columns (nCols), gut (gutter or space horizontally between items), gap (space vertically between items, and the width and height for the individual items (wid and hgt). The value for gBounds is the bounds for the page which is passed to the handler from the masterFrames handler.

For the calculation it uses a nested repeat loop (again a subject we would like to cover in the next post).

set gridList to {3, 2, 11, 4, 12, 10} --rows, cols, gut, gap, wid, hgt}
set marList to {2, 2, 2, 2}
(*Parameters: page width, page height, marList,  facing pages, master frame, number pages, columnCount*)
set propList to {48, 44, marList, false, true, 1, 1}
(*Returns a list of lists for creating a grid of page items.*)
on getBounds(gridList, gBounds)
   copy gridList to {nRows, nCols, gut, gap, wid, hgt}
   set beginX to gut
   set y0 to gap
   set boundsList to {}
   set x0 to beginX
   repeat with i from 1 to nRows
	repeat with j from 1 to nCols
	   set y1 to y0 + hgt
	   set x1 to x0 + wid
	   set gBounds to {y0, x0, y1, x1}
	   set end of boundsList to gBounds
	   set x0 to x1 + gut
	end repeat
	set x0 to beginX
	set y0 to y1 + gap
   end repeat
   return boundsList
end getBounds

LOADRESOURCES

To make things easy, the script loads text styles and XML tags into the active document (docRef) from a standing template. The one caveat for this template is that the names for paragraph styles match those used for the XML tags which, of course, must conform to XML naming standards (no spaces or special characters allowed). To define the template the user is asked to choose from the list of templates found in InDesign’s Templates folder (inside its application folder).

(*Imports paragraph styles and XML tags from a template chosen by the user from files found in InDesign's Templates folder (in its application folder). Once imported XML tags are mapped to paragraph styles with matching names.*)
loadResources(docRef)
on loadResources(docRef)
   tell application "Adobe InDesign CC 2019"
	set appPath to file path as string
	set templatePath to appPath & "Templates"
	set fileList to list folder templatePath without invisibles
	set listChoice to choose from list fileList without multiple selections allowed
	if listChoice is false then
	   error ("Requires template choice for styles and XML tags")
	else
	   set templateChoice to item 1 of listChoice
	end if
	set fileRef to templatePath & ":" & templateChoice
	tell docRef
	   load XML tags from fileRef
	   import styles format text styles format from fileRef
	   map XML tags to styles
	end tell
   end tell
end loadResources

The template used can be a fairly standard InDesign template where the names for paragraph styles and XML tags match. Using this criteria, a big workload for writing a script for XML import becomes a simple matter of mapping XML tags to styles. Additionally, the same names can be used for dummy text. With XML associated with paragraph styles and dummy text placed in the first text frame of the document, the document could be saved as a template to be used for any number of XML projects using the same tagging convention.

IMPORT XML

There are a number of ways an XML text file can be placed into the document. With a document open as prepared above, the following script gets the reference to an XML text file chosen by the user. It then imports the XML into the prepared document and associates it with the first text frame of the document (threaded to text frames on the master page). The process listed below requires a list of the names used for the paragraph styles and XML tags.

ImportRepeatedXML

set nameList to {"Name", "Title", "Ephone", "Email"}
tell application "Adobe InDesign CC 2019"
   set docRef to document 1
   tell docRef
	set rootElement to XML element 1
	set frameRef to text frame 1 of page 1
	--map tags to styles
	repeat with i from 1 to length of nameList
	   set theName to item i of nameList
	   set styleRef to paragraph style theName
	   set tagRef to XML tag theName of docRef
	   make XML import map with properties {mapped style:styleRef, markup tag:tagRef}
	end repeat
	--set XML import preferences
	my xmlImportPrefs(docRef, true) --true indicates repeat XML elements
	set fileRef to my getXMLFile()
	--import file and place into first text frame 
	tell rootElement
	   import XML from fileRef
	   place XML using frameRef with autoflowing
	end tell
   end tell
end tell
(*User chooses XML text file from open file dialog*)
on getXMLFile()
   set filePrompt to "Select XML file for placing"
   activate
   set fileRef to choose file with prompt filePrompt
   set theInfo to info for fileRef
   if name extension of theInfo is not "xml" then
	error "Requires file having XML name extension"
   end if
   return fileRef
end getXMLFile
(*Sets XML import preferences with repeat text elements
dependent on value of doRepeat variable*)
on xmlImportPrefs(docRef, doRepeat)
   tell application "Adobe InDesign CC 2019"
	tell XML import preferences of docRef
		set import style to merge import
		set repeat text elements to doRepeat
		set ignore whitespace to false
	end tell
   end tell
end xmlImportPrefs

ONWARD AND UPWARD

To present a script that requires a number of fairly complex handlers is problematic for a blog where the topic tries to maintain a narrow focus. It is our hope that, in breaking the script into reusable modules, users would appreciate the power of handlers. That handlers can be combined to make more than the demonstration script but, with possibly a modification or two, could be used for any number of applications. With this, we leave you the challenge of using handlers in a future worksaving script.

Disclaimer:
Scripts provided are for demonstration and educational purposes. No representation is made as to their accuracy or completeness. Readers are advised to use the code at their own risk.