2010-05-17

Generate a Unique File Name

Uses Two Other generic functions

This is a useful function to generate a unique file name. If the File object it is passed already exists then this function adds an underscore and a number from 02 to 99 to the base name of the file until it finds a unique file name. If no unique name can be generated, it returns null.

This function calls two other previously posted functions. The first determines the base name, or the name without the extension. The second pads a number to fill out a specified number of digits.

//
function uniqueFile (fileRef) {
//-------------------------------------------------------------------------
//-- U N I Q U E F I L E
//-------------------------------------------------------------------------
//-- Generic: Yes, for ExtendScript CS3 and newer
//-------------------------------------------------------------------------
//-- Purpose: To take a FileRef and check to see if it is unique. If yes,
//-- then return it, else add a suffix starting with _02 and keep trying
//-- until it is unique
//-------------------------------------------------------------------------
//-- Arguments: fileRef: a File object
//-------------------------------------------------------------------------
//-- Calls: Custom File method .nameWithoutExtension()
//-- Calls: pad
//-------------------------------------------------------------------------
//-- Returns: a File object. It might be the same one. If the function
//-- fails it returns null
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var u = uniqueFileName (File ('~/Sample.txt' )) ;
//~ if (u != null) {
//~ //-- use the file here
//~ }
//-------------------------------------------------------------------------
//-- Written: 2010.05.02 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- Do quick initial test. If the file is already unique then return it.
if (! fileRef.exists) {
return fileRef ;
}
//-- File isn't unique, get all the parts to construct a new one
var container = fileRef.parent ;
var baseName = fileRef.nameWithoutExtension() ;
var extension = fileRef.name.substr(baseName.length) ;

//-- Loop until reaching a unique file, then return it.
for (var version = 2; 99 >= version; version++) {
var uniqueTest = File (container + '/' + baseName + '_' + pad (version, 2, 0) + extension) ;
if (! uniqueTest.exists) {
return uniqueTest ;
}
}
//-- Unable to come up with a unique file, so return null
return null ;
}
//

2010-05-14

CS4 to CS5 Changes

Noticed Changes
Just getting started with CS5 and encountering a few changes.

.lockState
Interesting is that that the .lockState property has been removed from Text Frames. Well Text Frames and a few other items. I suppose that since it is the story of the frame that is locked it makes sense, but why the change now?

.checkIn () & .checkOut ()
Similar to the .lockState, the .checkIn and .checkOut methods have been moved to the story. The easy fix in all of these cases is to insert .parentStory in front of .lockState or .checkOut() or .checkIn().



2010-05-10

Paragraphs of Selection

Handy for certain tasks
This is another function that I tend to use when needing to work with paragraphs. I recently came across it again and thought it was fine time to share.
The basic premise is that if the user has some text selected and you want to automate something with regard to the whole paragraphs that have any part selected, then you need something like this.
With all the ifs, switches, etc., this code sure does go marching off to the right, but that is just the way it is. It doesn't have to be pretty, it just has to work.
Comments?

//
function getParagraphsOfSelection () {
//----------------------------------------------------------------------------
//-- G E T P A R A G R A P H S O F S E L E C T I O N
//----------------------------------------------------------------------------
//-- Generic: Yes.
//----------------------------------------------------------------------------
//-- Purpose: To return an array of paragraphs relating to the selection
//-- or false if there is no selection or an error.
//----------------------------------------------------------------------------
//-- Written: 10 March 2009 by Jon S. Winters from a prior work.
//-- eps@electronicpublishingsupport.com
//----------------------------------------------------------------------------

//-- Determine is there in an appropriate selection.
try {
//-- Initialize the object
var myObject = false ;
//-- Check for a selection.
if(app.documents.length != 0){
if(app.selection.length != 0){
//Process the objects in the selection to create a list of
//qualifying objects (text frames).
switch(app.selection[0].constructor.name){
case "TextFrame":
myObject = ( app.selection[0].parentStory.paragraphs ) ;
break;
default:
if(app.selection.length == 1){
//If text is selected, then get the parent text frame.
switch(app.selection[0].constructor.name){
case "Text":
case "InsertionPoint":
case "Character":
case "Word":
case "Line":
case "TextStyleRange":
case "Paragraph":
case "TextColumn":
myObject = ( app.selection[0].paragraphs );
break;
} //-- end of switch
} //-- end of if selections == 1
break;
} //-- end of switch
} //-- end of app.selection.length
} //-- end of app.documents.length
} //-- end of try
catch (caughtError) {
return {} ;
}
return myObject ;
}//-- End of function
//

2010-05-03

Insert Glyph

Method to insert additional special characters
A client has been asking me for create an extension of Adobe InCopy and Adobe InDesign's Insert Special Character submenus. This blog already has code posted to create menu items with keyboard shortcuts. The code below actually inserts a glyph at the current insertion point. This code takes a slightly different approach than the Insert Special Characters in that it will not replace as selection, it only works when there is an insertion point.

What is interesting is that it is driven by the same Unicode text that the Info Panel displays when the desired glyph is selected in a story. So to insert a 1/4 fraction glyph you would use:
insertGlyph ('0xBC') ;

That unicode number is internally converted to a different encoding by replacing the 0x with a %u and adding any necessary leading zeros before being unencoded and forced to be a string. There is far more error checking than actual code.

//
function insertGlyph (charCodeOfGlyphToInsert) {
//-------------------------------------------------------------------------
//-- I N S E R T G L Y P H
//-------------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign and Adobe InCopy CS3 and newer
//-------------------------------------------------------------------------
//-- Purpose: To take a code for a glyph as listed by the Info panel and
//-- insert the the glyph at the insertion point if there is an insertion
//-- point.
//-------------------------------------------------------------------------
//-- Arguments:
//-- charCodeOfGlyphToInsert: a String appearing as 0xEB pr 0x2014
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Returns: nothing truely useful. Does return true if successful, false
//-- if the arguments or the selection is invalid.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ insertGlyph ('0xBC') ; // 1/4 fraction
//~ insertGlyph ('0x401') ; // Capital E with umlaut
//~ insertGlyph ('0x2020') ; // Dagger
//-------------------------------------------------------------------------
//-- Notes: Use the Info Panel with a single character selected to learn
//-- the glyph number. These are unicode numbers or in very simple cases
//-- they are ascii number. But they are Hexidicmal based. However, they
//-- may be large 4 digit numbers or smaller 2 digit numbers based upon
//-- the specific glyph.
//-------------------------------------------------------------------------
//-- Written: 2010.04.25 by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- Verify that what we were passed was a valid glyph number with the 0x
var len = charCodeOfGlyphToInsert.length ;
if (((len != 4) && (len != 5) && (len != 6)) || (charCodeOfGlyphToInsert.substr (0, 2) != '0x')) {
return false ;
}
//-- Verify that the selection is an insertion point, but check docs first
if (1 > app.documents.length) {
return false ;
}
var curSel = app.selection ;
if ((curSel.length != 1) || (curSel[0].constructor.name != 'InsertionPoint')) {
return false ;
}
//-- create the glyph. Not really necessary to make it a spearate variable,
//-- but this makes it easier to debug.
var glyph = String (unescape ('%u' + ('00').substr (0, 6 - len) + charCodeOfGlyphToInsert.substr (2))) ;

//-- insert the glyph
curSel[0].insertionPoints[0].contents = glyph ;
return true ;
}
//

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