Your prescription for increased productivity and profitability
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.
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.
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.
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
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.
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
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.
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.
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
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.