2010-04-26

Determine Publication Volume and Issue

Not just for print publications

This very generic function was designed to determine the publication volume and issue numbers for newspapers publishing 7 days a week. It can be used, however to compare any two dates. Just let volume represent years and issue represent days.

Of interest in this function is the methods of validating the arguments. ECMA script, JavaScript, and ExtendScript don't do nice jobs of validating object types. Looking at a constructor is as about as good as you can get, and even that isn't totally foolproof -- though it would take more than a fool to break it.

Also in this function is one of several ways to create a duplicate date object. In this case creating a new date by parsing the original date. Setting one date to another date with just an = will cause both variables to respond to a method applied to either one.

//
function determineVolumeAndIssue (pubDate, refDate, refVolume) {
//-------------------------------------------------------------------------
//-- D E T E R M I N E V O L U M E A N D I S S U E
//-------------------------------------------------------------------------
//-- Generic: Yes. Should work with any ECMA based scripting language
//-- including ExtendScript and JavaScript.
//-------------------------------------------------------------------------
//-- Purpose: To return an object with the publication volume and
//-- publication issue for a passed publication date given a reference
//-- date and the volume number on that reference date. This function
//-- can also be used to count forwards or backwards in years and days
//-- between
//-------------------------------------------------------------------------
//-- Arguments:
//-- pubDate: a Date object for the publication date. Or, if you are
//-- comparing dates, this is the date you want to investigate.
//-- refDate: a Date object. This date is the date that Issue 1 for the
//-- passed volume number was published (will be published).
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: a custom object with two properties:
//-- .volume: a Number for the volume number
//-- .issue: a Number for the issue number
//-- NOTE: both values are set to -1 if any of the arguments are
//-- incorrect.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var pubDate = new Date (Date.parse('April 21, 2010')) ;
//~ var refDate = new Date (Date.parse('August 4, 2009')) ;
//~ var refVolume = 101 ;
//~ var vi = determineVolumeAndIssue (pubDate, refDate, refVolume) ;
//~ var volume = vi.volume ;
//~ var issue = vi.issue ;
//-------------------------------------------------------------------------
//-- Notes:
//-- The refDate is the date of Issue 1 for the refVolume. The
//-- function assumes a daily publication schedule.
//-- This version does not work for 1-6 days per week. That code is more
//-- complex and not posted with this version.
//-- Issues are always positive numbers. If the refDate is after the
//-- pubDate, the Volume number will be decremented, but the Issue
//-- number will still be a postive.
//-- See the note about return values
//-------------------------------------------------------------------------
//-- Written: 2010.04.21 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- create the return object with the default error values
var ro = {volume:-1, issue:-1} ;
//-- Verify that the arguments are valid, leaving them as separate if
//-- statuements to make it easier to debug.
if ((typeof pubDate != 'object') || (pubDate.constructor.name != 'Date')) {
return ro ;
}
else if ((typeof refDate != 'object') || (refDate.constructor.name != 'Date')) {
return ro ;
}
else if (isNaN (refVolume = parseInt (refVolume))) {
return ro ;
}
//-- Get a two key comparisons
var y0 = refDate.getYear () ;
var y1 = pubDate.getYear () ;
//-- Create a comparison date to determine if the current date
//-- is before or after the volume referece date.
//-- Using the Date.parse because just setting the date creates
//-- a pointer which then destroys the volumeReferenceDate.
//-- this is slightly slower, but not a deathly slow
var comparisonDate = new Date (Date.parse (refDate)) ;
//-- Move to the same year as the passed date
comparisonDate.setYear (1900 + y1) ;
var timeDifference = comparisonDate.getTime () - pubDate.getTime () ;
//-- Using the time difference determine if we are a before or after the
//-- reference date, add {using the normal ids for Issue and Volume}
//-- calcuate the volume and issue numbers and put them back into
//-- the passed object.
if (0 >= timeDifference) {
//-- we are this number of issues past the volume referece date
ro.issue = String (1 + Math.ceil (Math.abs(timeDifference) / (1000 * 60 * 60 * 24))) ;
ro.volume = String (Math.floor (y1 - y0) + refVolume) ;
}
else {
//-- back up a year.
comparisonDate.setYear (1900 + (y1 - 1)) ;
timeDifference = comparisonDate.getTime () - pubDate.getTime () ;
ro.issue = String (1 + Math.ceil (Math.abs (timeDifference) / (1000 * 60 * 60 * 24))) ;
ro.volume = String (Math.floor ((y1 -1) - y0) + refVolume) ;
}
//-- all done
return ro ;
}
//
//

2010-04-19

Set Text Variables

Part of a routine to set folios
I used to set folios by leaving misspelled tags on pages such as 'mMonth' and then scripting their replacement for folios. The advantage of this is that if a user fails to run the script that does the swap, or if the document doesn't conform to the site's naming convention, the misspelled tags remain and hopefully they would be found during a spell check of the document. Alas, users don't seem to follow the rules.

So, I've changed to using Adobe InDesign text variables for folios. The template would be set to have no values and then at key moments (like opening, and saving as) the values would get set.

The function below makes it easy to set values as it adds the text variable if it doesn't exist.

//
function setCustomTextVariable (docRef, name, value) {
//-------------------------------------------------------------------------
//-- S E T C U S T O M T E X T V A R I A B L E
//-------------------------------------------------------------------------
//-- Generic: Yes for InDesign CS4 and newer.
//-------------------------------------------------------------------------
//-- Purpose: To set a Custom Text Variable value. These can be used for
//-- a multitude of items, but they are commonly used for folios.
//-------------------------------------------------------------------------
//-- Arguments:
//-- docRef: The reference to the document to alter
//-- name: The name of the CustomText Variable
//-- value: The text that the CustomText Variable should be set to.
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: true unless an error occurs, which returns false.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ setCustomTextVariable (app.documents[0], 'Publication', 'Star-Ledger')
//-------------------------------------------------------------------------
//-- Notes: The Text Variable does not have to exist, this will create it.
//-- But if the text variable is not a Custom Text Variable, its value
//-- won't be able to be set, and this function will fail silently. See
//-- returns above.
//-------------------------------------------------------------------------
//-- Written: 2010.04.19 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
try {
var atv = docRef.textVariables.everyItem() ;

if (atv.hasOwnProperty (name)) {
//-- Note, not testing to see if the value can be set. If the TV
//-- isn't a 'Custom' TV, this will fail.
atv [name].variableOptions.contents = value ;
}
else {
//-- Create the TV and set the value.
var newTV = docRef.textVariables.add({name:name, variableType:VariableTypes.CUSTOM_TEXT_TYPE}) ;
newTV.variableOptions.contents = value ;
}
return true ;
}
catch (failSilently) {
var localError = failSilently ;
return false ;
}
}
//

2010-04-16

Get Document that Triggered Event

The correct document isn't always the one in front...

Event handlers are ways to run scripts when certain key events happen in Adobe InDesign or Adobe InCopy. Most things can be caught if there is a menu version of that function. Adobe's documentation does a decent job of explaining the common events, but not what to do with the 'event' itself, and is downright poor at the rest of the event handler issues.

But below is a very simple function that you can call inside the event handlers for 'open' and for 'close' event. Imagine you had multiple documents open. Now imagine that the windows are stacked so that you can see the close button for all the windows. Now assume you have an event handler that will do something useful (like look for overset text, preflight, adjust folios, for log a version comment). Well your event handler needs to know which document you just tried to close. That is where this function comes into play.

Event handlers always reference a function. But the function is a specified only in name. You can't pass it anything. Well your event handler will always receive a single argument -- an event. And the event has a property called .target and that .target property has another property .name. And the name of the target of the event is the name of the document.


//
function docRefFromEvent (event) {
//-------------------------------------------------------------------------
//-- D O C R E F F R O M E V E N T
//-------------------------------------------------------------------------
//-- Generic: Yes for ExtendScript CS3, CS4, etc.
//-------------------------------------------------------------------------
//-- Purpose: To return the document triggering the event handler or the
//-- default of the frontmost document.
//-------------------------------------------------------------------------
//-- Arguments: event: the event which triggered some other function which
//-- in turn called this function. Likely it would be an open or a close
//-- function so that you can catch the correct document. For example,
//-- you need this for open handlers becasue if you have two documents
//-- open, documents[0] or .activeDocument will point to the first doc
//-- and not the document you just opened. Likewise with closing files,
//-- your windows could be tiled and you can close a file other than the
//-- active or the documents[0].
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: Either a reference to the front document or the document that
//-- triggered the passed event.
//-------------------------------------------------------------------------
//-- Written: 2010.04.06 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- Created for version 3.52
if ((event != undefined) && event.hasOwnProperty ('target') && event.target.hasOwnProperty ('name')) {
var testDocRef = app.documents.itemByName (event.target.name) ;
if (testDocRef != null) {
return testDocRef ;
}
}
return app.documents[0] ;
}
//

2010-02-12

Compare Two Objects for Matching Properties


hasMatchingProperty

I've been way too busy with a large project recently to post anything here, but I created this nice function to compare properties of two objects. It would be great as an extension to the Object class if you felt so inclined.

Note, with the way the properties are compared using three equal signs === there is no need to do a separate verification that the property of each object is of the same variable type. The === is only true if the variable type is the same without any coercion.

If you wanted to check every property of the two objects then you would need a little more code to iterate through the properties of one object with the properties of another object. I set that up, but alas didn't write it as a generic function to share.

//
function hasMatchingProperty ( o1 , o2 , pn ) {
//-------------------------------------------------------------------------
//-- H A S M A T C H I N G P R O P E R T Y
//-------------------------------------------------------------------------
//-- Generic: Yes! should work for any ECMA 2.62 based language including
//-- JavaScript and ExtendScript
//-------------------------------------------------------------------------
//-- Purpose: To determing if two objects possess the same property and the
//-- value of that property is an exact match with now cooresion. If
//-- neither has that property then the result is also true -- the value
//-- matches for each object
//-------------------------------------------------------------------------
//-- Arguments:
//-- o1: The first object
//-- o2: The second object
//-- pn: The string name of the property
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: a Boolean true if the objects have matching properties, else
//-- false
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var a = hasMatchingProperty ( {A:"z", C:0, E:false} , {B:"Y", C:0, E:0} , 'A' ) ;//=false
//~ var b = hasMatchingProperty ( {A:"z", C:0, E:false} , {B:"Y", C:0, E:0} , 'B' ) ;//=false
//~ var c = hasMatchingProperty ( {A:"z", C:0, E:false} , {B:"Y", C:0, E:0} , 'C' ) ;//=true
//~ var d = hasMatchingProperty ( {A:"z", C:0, E:false} , {B:"Y", C:0, E:0} , 'D' ) ;//=true
//~ var e = hasMatchingProperty ( {A:"z", C:0, E:false} , {B:"Y", C:0, E:0} , 'E' ) ;//=false
//-------------------------------------------------------------------------
//-- Notes: for a match with cooresion change the === to ==
//-------------------------------------------------------------------------
//-- Written: 2010.01.27 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- Check to see if both Have the Property And Both of the Property
//-- Values Exactly Match without coorersion
if ( ( o1.hasOwnProperty( pn ) == o2.hasOwnProperty ( pn ) ) && ( o1[ pn ] === o2[ pn ] ) ) {
return true ;
}
//-- Check to see if both are missing the property
if ( ( ! o1.hasOwnProperty( pn ) ) && ( ! o2.hasOwnProperty( pn ) ) ) {
return true ;
}
//-- No matching
return false ;
}
//

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