2009-11-13

Gather Assigned Frames

The Text Frames associated with Adobe InCopy Assignments

On to more things with Adobe InCopy Assignments in Adobe InDesign.
The generic function below will generate a return object with two properties. One contains references to every text frame in the passed document reference that has assigned content. The second segregates the assigned frames by assignment.
Either property will allow you to locate the frames that are used in an assignment.

//
function gatherAssignedFrames ( docRef ) {
//-------------------------------------------------------------------------
//-- G A T H E R A S S I G N E D F R A M E S
//-------------------------------------------------------------------------
//-- Generic: Yes.
//-------------------------------------------------------------------------
//-- Purpose: To return an associative array of all the text frames in the
//-- passed coument that have associated Adobe InCopy Assignments
//-------------------------------------------------------------------------
//-- Arguments:
//-- docRef: An [object Document] to investigate
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: a custom object with two properties
//-- .assignmentFrames: An Associatve Array of Assignment IDs using
//-- the $ prefix as described below.
//-- Each element will contain a property for every text frame
//-- with the text frame ID with the $ prefix.
//-- The contents of these properties will be a reference
//-- to the text frame itself.
//-- .assignedTextFrames: An Associative Array of Text Frame IDs
//-- with each Text Frame ID have a $ prefix. The contents
//-- of each property is a reference to the text frame itself.
//-------------------------------------------------------------------------
//-- $ Prefix on IDs
//-- IDs are prefixed with a $. Thus Text Frame 188 will be returned as
//-- $188. This is done because objects cannot be named with numbers.
//-------------------------------------------------------------------------
//-- Sample Use:
//-- gatherAssignedFrames ( docRef )
//-------------------------------------------------------------------------
//-- Notes: Decide how you want to handled the Unassigned InCopy Content
//-- There will be references to that assignment as well. It is valid.
//-------------------------------------------------------------------------
//-- Written: 2009.11.12 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- Note, we are using a $ prefix on the object names in the associative
//-- array because pure numbers are not allowed.

//-- Create the return object.
var returnObject = new Object () ;
returnObject.assignmentFrames = new Object () ;
returnObject.assignedTextFrames = new Object () ;

//-- Gather all assignments.
var allAssignments = app.documents[0].assignments ;

//-- Loop through each of the assignments -- backwards for efficiency.
for ( var ai = allAssignments.length - 1 ; ai >= 0 ; ai-- ) {

//-- Get a reference to the current assignment in the loop and its ID.
var activeAssignment = allAssignments[ai] ;
var activeAssignmentID = '$' + activeAssignment.id ;
//-- Add the property to the return object for the current assignment.
returnObject.assignmentFrames[ activeAssignmentID ] = new Object () ;
//-- Get a reference to every assigned story for the assignment.
//-- There can be one or many. Most CMS's use many.
var allAssignedStories = activeAssignment.assignedStories ;

//-- Loop backwards through each story of the active assignment.
for ( var asi = allAssignedStories.length - 1 ; asi >= 0 ; asi-- ) {

//-- Get a reference to the story indicated by the loop index.
var activeStory = allAssignedStories[asi].storyReference ;
//-- Get all the text containers for the active story.
//-- There could be more than one if the text frame is
//-- manually threaded to additonal frames.
var allTextContainers = activeStory.textContainers ;

//-- Finally...
//-- Loop through all the text containers (text frames in most
//-- cases) and add the container ID with a $ prefix to both
//-- main properties of the return object.
for ( var tci = allTextContainers.length - 1 ; tci >= 0 ; tci-- ) {
//-- Get a reference to the container indicated by the loop.
var activeTextContainer = allTextContainers[tci] ;
var activeTextContainerID = '$' + activeTextContainer.id ;

//-- Add the IDs to the two objects
returnObject.assignedTextFrames[ activeTextContainerID ] = activeTextContainer ;
returnObject.assignmentFrames[ activeAssignmentID ][ activeTextContainerID ] = activeTextContainer ;
}
}
}
return returnObject ;
}
//

2009-11-12

Delete Empty Assignments

Part of a Document Cleanup Routine

I've been working with Adobe InCopy Assignments again.
When users delete frames that used to have assigned stories, the assignment remains with the Adobe InDesign document. If the assignment manages to get updated (it can happen easily) then the assignment file will contain no Adobe InCopy stories. What will happen if the user opens the Adobe InCopy story is they will get an ugly message when opening the story and they will see the page where the assigned stories used to be, but they can't edit it.
To put things back, the story needs to get reassigned and re-saved.
But if the original Adobe InDesign document still has the assignment is saved _last_ then the user sees the original page.
This function does nothing other than delete the empty assignments. To fix all ills, you could also delete the assignment file from the file server -- but the better solution is to save the page where the assignment currently exists.


//
function deleteEmptyAssignments ( docRef ) {
//-------------------------------------------------------------------------
//-- D E L E T E E M P T Y A S S I G N M E N T S
//-------------------------------------------------------------------------
//-- Generic: Yes
//-------------------------------------------------------------------------
//-- Purpose: To delete all assignments with no assignedStories
//-------------------------------------------------------------------------
//-- Arguments: docRef: a reference to the document to be processed
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: Boolean true if successful. Boolean false if an error occured
//-------------------------------------------------------------------------
//-- Sample Use: deleteEmptyAssignments ( app.documents[0] )
//-------------------------------------------------------------------------
//-- Notes: Part of a larger function.
//-------------------------------------------------------------------------
//-- Written: 2009.11.12 by Jon S. Winters of electronic publishing support
//-- at the Hilton Universal City, Los Angeles, CA
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

try {
//-- Loop through every assignment.
var allAssignments = docRef.assignments ;
for ( var aai = allAssignments.length - 1 ; aai >= 0 ; aai-- ) {
//-- if the assignment isn't the special 'Unassigned' assignmetn and if
//-- the assignment has no assigned stories, then remove it.
if ( ( allAssignments[aai].assignedStories.length == 0 ) && ( allAssignments[aai].name != 'Unassigned InCopy Content' ) ) {
allAssignments[aai].remove() ;
}
}
return true ;
}
catch ( failSilently ) {
var errorObject = failSilently ;
return false ;
}
}

2009-11-03

Remove Column of Text from Adobe InDesign or Adobe InCopy Story

Parent function from a prior post

A prior post listed the removeColumn function which can remove a delimited column from a string. This is the wrapper that calls that function to remove a column of text from a specified set of paragraphs within an Adobe InDesign or Adobe InCopy story.

If you want it to work on a set of selected paragraphs then pass it:
app.selection[0].paragraphs
which will provide the paragraphs of the selection even if the user does an imperfect (read faster ) selection. The sample as shown would do the entire story.

As an alternative, check out the paragraphs collection method which is oddly named
.itemByRange(start,stop)
as that allows you to point to a specific range of paragraphs.

As with with the removeColumn function, this is great for cleaning up sports agate text where you need to remove columns from a set of paragraphs.


//
function removeColumnFromParagraphs ( pgraphs , colToKill , delimiter ) {
//-------------------------------------------------------------------------
//-- R E M O V E C O L U M N F R O M P A R A G R A P H S
//-------------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign or Adobe InCopy CS3 or newer
//-------------------------------------------------------------------------
//-- Purpose: To remove a column from some tab delimited text in a
//-- collection of paragraphs.
//-------------------------------------------------------------------------
//-- Arguments: 3
//-- pgraphs: an Adobe InDesign or Adobe InCopy collection of
//-- paragraphs. This is can be generated by something like this:
//-- var pgraphs = story[0].paragraphs ;
//-- colToKill: An integer of zero based column of text to remove.
//-- in tab delimited columns
//-- delimiter: OPTIONAL. If not specified a tab will be used.
//-------------------------------------------------------------------------
//-- Calls: the removeColumn function -- see prior post
//-------------------------------------------------------------------------
//-- Returns: nothing, but alters the text of tha passed text.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ removeColumnFromParagraphs ( app.selection[0].parentStory.paragraphs , 3 )
//~ removeColumnFromParagraphs ( app.selection[0].parentStory.paragraphs , 0 )
//~ removeColumnFromParagraphs ( app.selection[0].parentStory.paragraphs , -1 )
//-------------------------------------------------------------------------
//-- Notes: Can alter formatting of the paragraphs if they are not
//-- consistently formatted. Uses regular expressions and some array
//-- methods which are not format friendly
//-------------------------------------------------------------------------
//-- Edited: 2009.11.01 by Jon S. Winters of electronic publishing support
//-- in the business center at CLT while waiting on a flight.
//-- The basis for the posted function was taken from a long running
//-- section of code.
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- check the third parameter and if not passed
if ( delimiter == undefined ) {
delimiter = '\t' ; // or ' '
}
//-- Get a count of the number of pargraphs in the collection
var numPgraphs = pgraphs.length ;
//-- Loop through every paragraph
for ( var pIndex = numPgraphs-1 ; pIndex >= 0 ; pIndex-- ) {
var textWithColumnRemoved = removeColumn ( pgraphs[pIndex].contents , colToKill , delimiter ) ;

//-- See the original had a return and if the final didn't.
//-- Note the order is important to maximize the speed
if ( ( ! new RegExp ( '\r' ).test ( textWithColumnRemoved ) ) && ( new RegExp ( '\r' ).test ( pgraphs[pIndex].contents ) ) ) {
//-- Add the return back in as the paragraph contents is replaced
pgraphs[pIndex].contents = textWithColumnRemoved + '\r' ;
}
else {
//-- Just set contents to the text without the column
pgraphs[pIndex].contents = textWithColumnRemoved ;
}
//-- continue with loop
}
}
//

2009-11-02

Remove Column of Text

Great for Sports Agate

The following function is something I regularly use for removing a column of text in sports agate cleanup scripts. It is very generic. Pass it a string, a zero based column index, and the delimiter (usually a tab specified as '\t' ) and the function will remove that column.

Note, you would need to call this with something that can process all the paragraphs. As this will only handle one paragraph at a time unless you get real fancy with the calls.

Look for another function to call this one in an upcoming post.

//
function removeColumn ( orig, col, delimiter ) {
//-------------------------------------------------------------------------
//-- R E M O V E C O L U M N
//-------------------------------------------------------------------------
//-- Generic: Yes. Should work for any ECMAScript based languages such as
//-- ExtendScript and JavaScript.
//-------------------------------------------------------------------------
//-- Purpose: To remove a 'delimiter' separated 'col'umn of text from the
//-- 'orig'inal passed string.
//-------------------------------------------------------------------------
//-- Arguments: 3
//-- orig: A string which delimited columns
//-- col: A column number. This can be negative to work from the back
//-- delimiter: a string which is used to delimit columns
//-------------------------------------------------------------------------
//-- Calls: Nothing, but requires the .split(), .splice(), and .join()
//-------------------------------------------------------------------------
//-- Returns: The original string with the specified column removed.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ removeColumn ( "a\tb\tc\td\t\e\tf", -1, '\t' ) ; // "a\tb\tc\td\t\e"
//~ removeColumn ( "a\tb\tc\td\t\e\tf", 2, '\t' ) ; // "a\tb\td\t\e\tf"
//-------------------------------------------------------------------------
//-- Notes: Works great with negative column indexes. -1 is the last column
//-- If removing the last column be careful if you need the text to
//-- retain the final return or line break as this will delete it.
//-------------------------------------------------------------------------
//-- Edited: 2009.11.01 by Jon S. Winters of electronic publishing support
//-- Originally an internal function of a larger function.
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- break the string into an array using the passed delimiter
var sa = orig.split(delimiter) ;
//-- Call inbuilt splice method to remove the specified element of the array
sa.splice(col,1) ;
//-- Join the array into a string using the passed delimater.
return sa.join(delimiter) ;
} //-- end of internal function.
//

2009-10-23

Delete Unused Master Pages

Master Pages in Adobe InDesign are a great way to automate the production of pages. But Master Pages, also called Master Spreads, can have a serious impact of production in a negative way too. Every Master Page added to a template or document increases the file size. And sometimes, if the Master Pages aren't built in a efficient and productive manner, the increase in size can be substantial.
And late in the production cycle, that extra file size slows down every save, every print, every export, every open, everything.
The partial solution is to delete the unused Master Pages. And this function does that.


function xUnusedMasters( docRef ) {
//-------------------------------------------------------------------------
//-- X U N U S E D M A S T E R S
//-------------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign.
//-------------------------------------------------------------------------
//-- Purpose: To delete all the master pages that are not applied to a
//-- a document page or a Master Page that is applied to a document.
//-------------------------------------------------------------------------
//-- Arguments: docRef: the document to examine.
//-------------------------------------------------------------------------
//-- Calls: addMasterName() an internal recursive function to generate
//-- an associative array of master page names that are in use.
//-------------------------------------------------------------------------
//-- Returns: nohting.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ xUnusedMasters( app.documents[0] ) ;
//-- OR:
//~ xUnusedMasters( returnDocumentReference ( pageOfWindow () ) ) ;
//-------------------------------------------------------------------------
//-- Notes: Preserves the entire hierachy of all the master pages that
//-- are based on other master pages.
//-------------------------------------------------------------------------
//-- Written: 2009.10.22 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- Create an object to record the names of the master pages to keep.
var mpNames = new Object () ;

//-- get a reference to all the pages of the passed document.
var allPages = docRef.pages ;
//-- loop through each page and add to the list of
for ( var pIndex = allPages.length - 1 ; pIndex >= 0 ; pIndex-- ) {
//-- Add the name of the master page applied to this page
//-- to the list of master pages to keep. Using and
//-- Associative Array allows the list to only contain
//-- a single instance if a single master page is used
//-- more than once in the document.
mpNames = addMasterName ( mpNames , allPages[pIndex] ) ;
}
//-- At this point the mpNames object has a property for the
//-- name of every master page that needs to be kept.

//-- Get a reference to all the master pages.
//-- NOTE: the '[None]' master page will not show up in this.
var allMasterPages = docRef.masterSpreads ;
//-- Loop through all the master pages.
for ( var mpIndex = allMasterPages.length - 1 ; mpIndex >= 0 ; mpIndex-- ) {
//-- compare this against the database of master pages to keep
if ( ! mpNames[allMasterPages[mpIndex].name] ) {
//-- If the associative array dosn't have that master page name
//-- then delete the master page.
allMasterPages[mpIndex].remove() ;
}
}
return ; //-- nothing, just return
//-- Below is an internal function to keep the main function generic
function addMasterName ( MPDB , pageRef ) {
//---------------------------------------------------------------------
//-- A D D M A S T E R N A M E
//---------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign
//---------------------------------------------------------------------
//-- Purpose: A recursive function that adds the name of the master
//-- page applied to the passed pageRef to the passed
//-- associative array MPDB
//---------------------------------------------------------------------
//-- Arguements: 2
//-- MPDB: An Associative Array with a value for each Master Page
//-- used.
//-- pageRef: The page or master page to get and add the master
//-- page name
//---------------------------------------------------------------------
//-- Calls: itself. This is a recursive function.
//---------------------------------------------------------------------
//-- Returns: The associative array MPDB with any new master page
//-- names from the passed pageRef
//---------------------------------------------------------------------
//-- Written entirely from scratch to replace a flawed version.
//-- Written 2009.10.22 by Jon S. Winters
//-- eps@electronicpublishingsupport.com
//---------------------------------------------------------------------

//-- Because the function is recursive, check to see if it was sent
//-- a reference to a non-existent master page. If so, return
//-- to the caller the same object it was sent.
if ( pageRef == null ) { return MPDB ; }

//-- Check to see if the passed pageRef is a page.
//-- If it is a page don't add page name.
//-- But if pageRef is master page then add its name.
if ( pageRef.constructor.name == 'MasterSpread' ) {
MPDB[pageRef.name] = true ;
}
//-- Call the function recursively, but send the appliedMaster
//-- of the passed pageRef and return the value.
return addMasterName ( MPDB , pageRef.appliedMaster ) ;
}//-- end of internal function
}//-- end of xUnusedMasters
//

2009-10-22

Get Page of Window

What is the user looking at?

I've found this function very handy lately. If you are opening files without showing the window ( as strange as that might seem, it can be very useful ), you will need a reference to what the user is viewing. This function returns the page the the Adobe InDesign thinks the user is looking at.

From that page reference you can easily get the document reference (remember it might not be app.activeDocument or app.documents[0]. See some prior posts to know why, but opening documents without showing the window is a really good reason!

Now, for some ideas. If you know the page that the user is looking at, you have ways to control printing of just that page, exporting just that page, adding items to just that page, applying master pages to just that page, etc...

//
function pageOfWindow () {
//-------------------------------------------------------------------------
//-- P A G E O F W I N D O W
//-------------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign, but not InCopy
//-------------------------------------------------------------------------
//-- Purpose: To return a reference to a the page that Adobe InDesign
//-- considers the active page.
//-------------------------------------------------------------------------
//-- Arguments: None
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: A page reference to the page that Adobe InDesign considers
//-- the active page. If no pages can be found it returns null ;
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var currentPage = pageOfWindow() ;
//-------------------------------------------------------------------------
//-- Notes: The page returned can be a Master Page
//-------------------------------------------------------------------------
//-- Written: 2009.09.22 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- 2009.09.24 added error handling

try {
//-- close any StoryWindows
while ( new RegExp ('Story','i').test( app.activeWindow.reflect.name ) ) {
app.activeWindow.close() ;
}
} catch (err) {
var errorObject = err ;
return null ;
}
//-- Get the page reference for the window displayed in the
//-- active Layout Window
return app.activeWindow.activePage ;
}
//

2009-10-20

Build Menu Item

This is both some simple and some complex code to construct a menu item in a menu.

One advantage of what this does is that it creates a stand alone script in the Scripts Panel folder that can be used to Assign a Keyboard Shortcut to a custom menu item -- something normally not possible.


function buildMenuItem (aMenu, menuItemName, eventFunction , checkItem , addShortcutScript , forceUpdateShortcut ) {
//-------------------------------------------------------------------------
//-- B U I L D M E N U I T E M
//-------------------------------------------------------------------------
//-- Generic: Yes, but calls an external function for creating the
//-- shortcut scripts.
//-------------------------------------------------------------------------
//-- Purpose: To add items and event listners to a passed menu in the
//-- menu bar.
//-------------------------------------------------------------------------
//-- Parameters: 6
//-- aMenu: A menu object created with the 'makeMainMenu' function.
//-- menuItemName: A string for the text as it should appear in the
//-- menu.
//-- eventFunction: This is the name of the function that should be
//-- invoked when the menu item is accessed
//-- checkItem: OPTIONAL. A boolean to indicate that the item should
//-- be checked. The default is false.
//-- addShortcutScript: OPTIONAL. a boolean to indicate that an
//-- auxilary script should be added which could be used as
//-- the recipitant of a keyboard shortcut. The default is false.
//-- forceUpdateShortcut: OPTIONAL, a boolean that when true will
//-- rewrite the shortcut script regarless of if it already exists
//-- we don't want to do this all the time because of the speed
//-- and the chance that it would trigger the keyboard shortcut
//-- to get lost.
//-------------------------------------------------------------------------
//-- Calls:
//-- findScriptsPanelFolder()
//-- buildMenuFolderStructure()
//-------------------------------------------------------------------------
//-- Returns: Nothing.
//-------------------------------------------------------------------------
//-- Written by Jon S. Winters.
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- Edited: 2009.01.23 to add the option to check the menu item.
//-- Edited: 2009.10.06 to start the process of making stand alone scripts
//-- that can be used for shortcuts for each menu item added.
//-- Verify that the checked item has been sent, else assume false
if ( checkItem == undefined ) {
checkItem = false ;
}
//-- Build the pat menu by constructing the action and then the menu item
var mMenuItem = aMenu.menuItems.item( menuItemName ) ;
if ( mMenuItem == null ) {
var mAction = app.scriptMenuActions.add( menuItemName ) ;

//-- Version 2r, add check
mAction.checked = checkItem ;
//alert ("Making menu: " + menuItemName + " has Action ID: " + mAction.id ) ;
var mListener = mAction.eventListeners.add( "onInvoke", eventFunction , false ) ;
var mMenuItem = aMenu.menuItems.add( mAction , LocationOptions.AT_END ) ;
//-- 2009.10.06 start the process of adding keyboard shortcut scripts.
//-- 2009.10.11 Some sub menus use '-' to create separators
//-- 2009.10.11 Also skip when the name doesn't exist
if ( addShortcutScript && ( menuItemName != '-' ) && ( menuItemName != '' ) ) {
try {
//-- Call an external function to determine the location of the
//-- application's Scripts Panel Folder. Then get the name
//-- of the parent menu of the menu item being created.
var shortcutDestination = Folder ( findScriptsPanelFolder() + '/' + buildMenuFolderStructure ( aMenu ) ) ;
//-- Verify that the folder exists because ExtendScript will
//-- lie about the file if the folder isn't there.
if ( shortcutDestination.verify ( ) ) {
//-- Add to that the name of the menu and create a file
//-- Object.
//-- Version 2.67 remove any / in name
var f = File ( shortcutDestination + '/' + menuItemName.replace(new RegExp ( "/","gm"),encodeURIComponent ('⁄')) + '.jsx' ) ;
//-- Test that file object. If it already exists and the function
//-- wasn't asked to overwrite it, skip the process.
if ( ( ! f.exists ) || forceUpdateShortcut ) {
//-- Create a string which will be written to a file with a
//-- .jsx file extension. The string will include a
//-- preprocessor directive to match the running target
//-- engine. No need to match the host applicaiton.
//-- Create a fake event object that can be passed for when
//-- the menu item is created in an loop of an array.
//-- To the reciving function it will look like:
//-- event.target.name
//-- Write a commented header
var fileContents = '//-- Created: ' + new Date () + ' by the ' + arguments.callee.name +' function.\r' ;
//-- if there is a target engine active, include this.
if ( $.engineName != undefined ) {
fileContents += '#targetengine ' + $.engineName + '\r'
}
//-- Now call the passed function and supply it with an object
//-- literal of the menu name to be used when the function
//-- is called from within a loop of an array.
//-- Version 2.67 escape any quotes in the menu item name.
fileContents += eventFunction.name + "({target:{name:\'" + menuItemName.replace (new RegExp ( "([\'\"“”‘’])",'gm'), "\\$1" ) + "\'}})" ;
//-- write the actual file.
f.open ('w') ;
f.write (fileContents) ;
f.close ();
}
}
}
catch ( err ) {
var errorObject = err ;
try {
f.close() ;
}
catch ( err ) {
var errorObject = err ;
}
}
}
}
}
//