printsphere autopilot 1.2 script development tutorial
TRANSCRIPT
PrintSphere AutoPilot 1.2
Script Development Tutorial
DRAFT Version 1.2.4
July 4, 2018
Agfa Graphics/Prepress Software R&D
Revisions
Rev. Date Author Description
0.1.0 August 8, 2017 Chris Tuijn Creation
0.2.0 August 21, 2017 Chris Tuijn Use Cases StoreFront
Use Cases Prepress
0.3.0 August 28, 2017 Chris Tuijn Advanced Use Cases (to be continued)
0.4.0 September 8, 2017 Chris Tuijn Incorporated input from JH
0.5.0 October 16, 2017 Chris Tuijn Splitting and merging of PDF files
0.6.0 November 2, 2017 Chris Tuijn Resources and Folder Resources
Support for multi-level ZIP archive operations
0.7.0 November 3, 2017 Chris Tuijn Deleting old resources
0.8.0 December 8, 2017 Chris Tuijn PrintSphere AutoPilot branding
Distribute resources exported by Prepress
0.9.0 January 3, 2018 Chris Tuijn Integration feedback from JH
1.0.0 February, 2018 Chris Tuijn Describe the PDF form processing functionality
1.2.0 April, 2018 Chris Tuijn PDF form processing with images
Preflighting
Mail retrieval
1.2.1 April, 2018 Chris Tuijn CSV Parsing
1.2.2 May, 2018 Chris Tuijn Integration feedback from JH
1.2.3 July, 2018 Chris Tuijn PDF form processing with bar codes
1.2.4 December, 2019 Chris Tuijn Trigger scripts
Retrieving text from PDF files
i
Contents
Revisions i
Contents iii
List of Figures vii
Listings ix
Introduction xi
1 PrintSphere AutoPilot Concepts 1
1.1 Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 A First Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Script Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Analyzing Resources 7
2.1 Dedicated Resource Classes . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Different Resources and their Properties . . . . . . . . . . . . . . . . . . . 8
2.3 Use Case: Delete Old Resources . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 Use Case: Routing Incoming Resources . . . . . . . . . . . . . . . . . . . . 13
2.5 Use Case: Analyzing PDF Files . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Use Case: Analyzing XML Files . . . . . . . . . . . . . . . . . . . . . . . . 15
2.7 Use Case: Preflighting PDF Files . . . . . . . . . . . . . . . . . . . . . . . 20
iii
iv CONTENTS
2.8 Use Case: Parsing CSV Files . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.9 Use Case: Retrieve Text from PDF files . . . . . . . . . . . . . . . . . . . . 24
3 Creating/Updating Resources 27
3.1 Use Case: Correct PDF Boxes . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2 Use Case: Splitting a PDF File . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3 Use Case: Merging PDF Files . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Use Case: Splitting Printer’s Spread PDF . . . . . . . . . . . . . . . . . . 34
3.5 Use Case: Merging Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.6 Use Case: Generate XML Files . . . . . . . . . . . . . . . . . . . . . . . . 35
3.7 Use Case: Apply XSLT transforms to XML Files . . . . . . . . . . . . . . 36
3.8 Use Case: Generate PDF Reports . . . . . . . . . . . . . . . . . . . . . . . 39
3.9 Use Case: Generate Nested PDF Reports . . . . . . . . . . . . . . . . . . . 43
3.10 Use Case: Forms with Variable Images and Barcodes . . . . . . . . . . . . 45
4 Apogee StoreFront Use Cases 53
4.1 Use Case: Reroute StoreFront Prepress Packages . . . . . . . . . . . . . . . 53
4.2 Use Case: Manipulate StoreFront Prepress Packages . . . . . . . . . . . . . 59
5 Apogee Prepress Use Cases 61
5.1 Convert APOXML files to JDF . . . . . . . . . . . . . . . . . . . . . . . . 61
5.2 Submit Jobs to Prepress in the Cloud . . . . . . . . . . . . . . . . . . . . . 63
5.3 Distribute Resources generated by Prepress . . . . . . . . . . . . . . . . . . 66
6 More Advanced Use Cases 71
6.1 Triggering PrintSphere AutoPilot Scripts . . . . . . . . . . . . . . . . . . . 71
6.2 Interfacing with Asynchronous Web Services . . . . . . . . . . . . . . . . . 75
6.3 Retrieving Mail from a Mail Server . . . . . . . . . . . . . . . . . . . . . . 76
List of Figures
3.1 HTML visualization of Listing 3.8 . . . . . . . . . . . . . . . . . . . . . . . 40
3.2 Making an Adobe Form Template PDF . . . . . . . . . . . . . . . . . . . . 41
3.3 A sample (cropped) view of the result (record 4) . . . . . . . . . . . . . . . 44
3.4 Making an Adobe Form Template PDF with image form fields . . . . . . . 50
3.5 A sample (cropped) view of the result with images . . . . . . . . . . . . . . 51
6.1 E-mail sent to the User to Approve/Reject a Document . . . . . . . . . . . 75
vii
Listings
1.1 Sorting Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1 Listing Resources in a PrintSphere Folder . . . . . . . . . . . . . . . . . . . 8
2.2 Transforming ZIP files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Delete Old Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4 Sorting PDF Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 Sample XML Setup file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.6 Parsing an XML File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.7 Output of the Parse XML Script . . . . . . . . . . . . . . . . . . . . . . . 19
2.8 Accessing the PrintSphere AutoPilot Preflight Engine . . . . . . . . . . . . 22
2.9 Parsing a CSV File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.10 Extracting Text from a PDF File . . . . . . . . . . . . . . . . . . . . . . . 25
3.1 Correcting PDF Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2 Splitting a PDF File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3 Merging PDF Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Splitting Printer’s Spreads . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.5 Apply XSLT Transform to an XML File . . . . . . . . . . . . . . . . . . . 36
3.6 XSLT Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.7 XML Input for XSLT Transform . . . . . . . . . . . . . . . . . . . . . . . . 37
3.8 Output HTML of XSLT Transform . . . . . . . . . . . . . . . . . . . . . . 38
3.9 XML Input for generating PDF Reports . . . . . . . . . . . . . . . . . . . 40
3.10 Script for generating PDF Reports . . . . . . . . . . . . . . . . . . . . . . 42
3.11 Script for generating PDF Reports with variable images . . . . . . . . . . . 48
3.12 XML Input for generating PDF Reports with images . . . . . . . . . . . . 49
4.1 Rerouting StoreFront Prepress Jobs . . . . . . . . . . . . . . . . . . . . . . 53
5.1 Sample APOXML file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.2 Convert APOXML files to JDF . . . . . . . . . . . . . . . . . . . . . . . . 62
5.3 Submit a Job to Apogee Prepress . . . . . . . . . . . . . . . . . . . . . . . 63
5.4 Distribute Resources generated by Prepress . . . . . . . . . . . . . . . . . . 67
6.1 Trigger AutoPilot Scripts from an e-mail . . . . . . . . . . . . . . . . . . . 72
ix
x LISTINGS
6.2 Template for e-mail boddy . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.3 Retrieve Mail Messages from a Mail Server . . . . . . . . . . . . . . . . . . 77
Introduction
PrintSphere vs. PrintSphere AutoPilot
PrintSphere is an on-line storage platform that implements easy integration with existing
products of the Agfa Apogee family such as Apogee Prepress and Apogee StoreFront.
Agfa customers can subscribe to the PrintSphere storage service and manage their data
on-line. They can use the existing stand-alone applications to synchronize their data on
the Windows or Mac platforms or use the dedicated Android and IOS apps to access the
remote data on their mobile devices.
From within Apogee Prepress, it is possible to use the PrintSphere platform to receive
input files from existing customers. In Apogee StoreFront, it is possible to export the
Prepress jobs that result from a StoreFront Order to a PrintSphere folder. PrintSphere can
also be used to manage image resources that can be used in the on-line editor embedded
in the StoreFront platform.
PrintSphere AutoPilot has been created on top of PrintSphere to allow even more au-
tomation possibilities. In the PrintSphere AutoPilot back-end (called SphereCenter), it is
now possible to create scripts that can watch incoming resources in PrintSphere AutoPilot
folders. These scripts can then manipulate those resources and generate output in various
PrintSphere output folders (in a PrintSphere AutoPilot context known as OutputLoca-
tions).
Organization of this document
This document can be used as a tutorial for Script developers to create their own Scripts.
In the first chapter, a number of basic concepts will be explained that are important to
understand how Scripts will be run in the PrintSphere AutoPilot environment. In the
subsequent chapters, a number of demo scripts will be presented that use the concepts
xi
xii INTRODUCTION
explained in the first chapter.
This document will not cover all the available PrintSphere AutoPilot calls in details.
For this, we refer to the on-line javadoc documentation. The purpose of this tutorial
is, however, to give the necessary knowledge to modify existing Scripts and create new
Scripts using the on-line javadoc documentation.
As a prerequisite to start modifying Scripts, one should have basic scripting/programming
skills and be familiar with the JavaScript syntax and semantics. To develop Scripts from
scratch, one should be more experienced in scripting and have a good understanding of
the PrintSphere AutoPilot functionality/architecture. This document can be of great help
in this perspective.
Chapter 1
PrintSphere AutoPilot Concepts
1.1 Scripts
A PrintSphere AutoPilot Script is essentially a text file consisting of JavaScript code that
conforms to a number of specific PrintSphere AutoPilot constraints. The Scripts can be
created by a Script developer and uploaded/edited in the PrintSphere back-end called
SphereCenter.
Once the Script has been uploaded to SphereCenter, it can be activated to be run. During
this activation process, a number of parameters need to be specified:
1. the incoming folder (called the InputFolder)
2. the outputfolders (called the OutputLocations)
3. the instance parameters
4. (optional) auxiliary resources
After activation of the Scripts, the Scripts can be run if they receive specific triggers. We
currently support the following triggers:
1. incoming files: this happens when a PrintSphere user drops files in the PrintSphere
InputFolder. It should be clear this typically happens directly on the PrintSphere
Server or, indirectly, via the synchronization clients by dropping a file in a local
folder.
1
2 CHAPTER 1. PRINTSPHERE AUTOPILOT CONCEPTS
2. notification via dedicated URL’s: Script can also be triggered by means of a ded-
icated URL. It is possible to pass parameters to the Script (using either URL pa-
rameters of data forms using http POST calls).
3. timer based events
When an active Script has been triggered, it will be run after a reasonable amount of time.
How quickly the Script will be run, depends on the load of the PrintSphere AutoPilot
server. If the load is not too high, a Script should be run within several minutes after a
trigger has been received.
When exactly the triggers result in the running of a specific Script also depends on the
Execution Type of the Script. We currently support the following Execution Types:
• Immediate Execution: this is the common Execution Type. In this case, the Script
becomes runnable if either a new input file arrives or if the Script is triggered through
an URL. When the Script is run, all the files that are available at that time will be
available as Input Resources.
• Scheduled Execution: in this case, the Script will only be run at particular times
specified by the scheduling parameters in the GUI (StoreCenter). This means that
the arrival of a new file does not trigger the Script. When run, the files that have
been delivered will be made available as Input Resources.
• Delayed Execution: in this case, the Script will be run a specified time (called the
Execution Delay) after the arrival of a specific file. If a new file arrives before the
previous file has been resulted in a run of the Script, the Execution Delay will be
reset.
• Delayed Execution (Same Name): in this case, the Script will only be run a specified
time (called the Execution Delay) after the arrival of a group of files that belong
together (and have a specific naming convention).
• Delayed Execution (After the First File): in this case, the Script will be run a
specific time (called the Execution Delay) after the arrival of a file belonging to a
given group. If a new file arrives before the previous files of the same group have
resulted in the running of the Script, the Execution Delay will be reset.
1.2. A FIRST EXAMPLE 3
1.2 A First Example
In order to make things more concrete, we will start with a first demo Script. The goal of
this Script is to watch an InputFolder for incoming resources and move these to specific
output folders (OutputLocations) depending on the file type of the resources.
1 function processFile(env, resource) {2 var fileType = resource.getFileType();3 print(resource.getFileName() + ": " + fileType);4
5 if (fileType === ’PDF’) {6 resource.move(env.getOutputLocation("pdf"));7 } else if (resource.isImage()) {8 resource.move(env.getOutputLocation("image"));9 } else if (fileType === ’MJD’) {
10 resource.move(env.getOutputLocation("mjd"));11 } else if (fileType === ’XML’) {12 resource.move(env.getOutputLocation("xml"));13 } else14 resource.move(env.getOutputLocation("failure"));15 }16
17 function main(env) {18 var res = env.getInputResources();19
20 for (i = 0; i < res.length; i++) {21 processFile(env, res[i]);22 }23
24 return 0;25 }
Listing 1.1: Sorting Files
As can be seen from the source in Listing 1.1, the language of the PrintSphere AutoPilot
Scripts is JavaScript. It should be clear that we support the plain JavaScript language
constructs to manipulate the basic and built-in types. On top of these constructs, we
have created a PrintSphere AutoPilot library that can be used as well. We do not support
dedicated JavaScript libraries that are available, for instance, in browser environments.
The reason for this is simple: the PrintSphere Scripts do not run in a browser environment
but in the PrintSphere AutoPilot environment.
Although JavaScript does not have explicit typing, the variables do have underlying types.
The env parameter above is of the type Environment. As such, a whole range of meth-
ods can be invoked on this variable. The supported methods are listed exhaustively in
4 CHAPTER 1. PRINTSPHERE AUTOPILOT CONCEPTS
the PrintSphere AutoPilot javadoc documentation. When saving/updating a Script in
SphereCenter, the system will carry out a JavaScript syntax check. Type checking and
parameter checking will not be carried out at this stage; these problems will pop up as
run-time errors after activation of the Script.
The PrintSphere environment is made available as the first parameter to the main func-
tion; this function is mandatory for all PrintSphere AutoPilot Scripts and will become
the main entry point when a Script is being run.
In order to retrieve the resources that are available in the InputFolder, we call the method
getInputResources. This method returns a list of elements of type Resource. In the
Script above, we will loop through the list of resoures and call a function processFile. In
this function, we will then move the resource to a specific OutputLocation.
We first determine, however, the file type by calling the method getFileType on the
Resource variable. This method will return the fileType as a string. The getFileType
method will look at the resource and try to retrieve the fileType based on the first few
bytes of the file. It does not use the file extension. There are also calls to get the file
extension but it usually is more safe to look at the file itself.
We then use an if/then/else statement to move the resource to a specific OutputFolder
depending on the file type. To this end, we call the method getOutputLocation with
a label as parameter. During Script activation, we can associate a number of output
folders with a Script and refer to these by means of a dedicated label. The move method
on resource will then move the input resource directly to the OutputLocation with the
specified label.
1.3 Script Environment
As is shown in the previous section, the environment provides the link between the Script
and the PrintSphere AutoPilot environment.
A number of methods on Environment provide access to data that are related to that
specific Script activation:
1. getInputResources
2. getOutputLocation
3. getScriptData
1.3. SCRIPT ENVIRONMENT 5
4. getAuxiliaryResource
5. getNotificationUrl
6. getPostedResources
A second class of methods provide access to general resources on the PrintSphere AutoPi-
lot Server:
1. getMailService
2. newHttpSession
In addition, there are a number of methods available on Environment that gives access to
dedicated functionality to, for instance, manipulate ZIP files, create files of various types
(XML, text files etc.).
Chapter 2
Analyzing Resources
2.1 Dedicated Resource Classes
This chapter contains a number of sample Scripts that analyze resources and carry out spe-
cific tasks depending on the results from this analysis. On an abstract level, PrintSphere
AutoPilot can copy and move resources of any file type. PrintSphere AutoPilot has ded-
icated support for a whole range of file types.
When a Script detects a known file type, it is possible to use a specific casting method on
Resource that returns an instance of a specific type for which dedicated methods have
been implemented. The supported file types, casting methods and associated classes are:
File Type Conversion Method PrintSphere AutoPilot Class
BMP asImage Image
ICO asImage Image
JPG asImage Image
PCX asImage Image
PDF asPdf PdfDocument
PNG asImage Image
PSD asImage Image
TIFF asImage Image
XML asXml XmlDoc
As an example, it is possible to retrieve the width and height of an Image variable by
using, e.g., the getWidth and getHeight methods. For more detailed information of the
specific methods of the indiviual classes, we refer to the javadoc documentation.
7
8 CHAPTER 2. ANALYZING RESOURCES
Please note that the JDF format is not included in the above list because it is treated as
an ordinary XML format. We currently do not have a specific class to manipulate JDF
files; to get information out of JDF files, one should make use of the XmlDoc methods.
2.2 Different Resources and their Properties
All files and folders in a PrintSphere environment are in the Scripts accessible as instances
of the class Resource. In the previous chapter, we already introduced the InputResource
variables. These trigger that a specific Script can be run. As we will see later on, it
is also possible to create new resources. These will also be instances of the Resource
class. Contrary to the InputResources, these will typically be created in a temporary
folder (on the PrintSphere AutoPilot server) that will be associated with a specific Script
instantiation. When the Script is deactivated, this folder and is content will be removed.
As already explained above, an OutputLocation will be associated with a specific Print-
Sphere folder belonging to a particular PrintSphere account. It is possible to retrieve
all resources that are available in a specific OutputLocation. This can be realized by
retrieving an instance of FolderResource associated with the OutputLocation. Such
a FolderResource instance is also a Resource and it is possible to get a list of the
Resources in this folder. The following code snippet (see Listing 2.1) illustrates how this
can be done. Please note that, although the main use of OutputLocation instances is to
get a context to create new resources, it is also possible to create an OutputLocation for
the sole purpose of getting access to its resoures.
Please note that the Script in Listing 2.1 is not associated with an input folder. Therefore,
it cannot be triggered by incoming resources. Running this Script can be either done
manually by clicking the Tools→Run Script button (in the Active Scripts→General tab
in SphereCenter) or by using the URL triggering mechanism (the URL to trigger the
Script can be found in the Active Scripts→General tab).
1
2 function listResources(env, folder, indent) {3 var list = folder.getChildren();4 var i;5
6 for (i = 0; i < list.length; i++) {7 var resource = list[i];8
9 if (resource.isFolder()) {10 print(indent + "Folder: " + resource.getFileName());
2.2. DIFFERENT RESOURCES AND THEIR PROPERTIES 9
11 listResources(env, resource, indent + "-");12 } else13 print(indent + "File: " + resource.getFileName());14 }15 }16
17 function main(env) {18 var location = env.getOutputLocation("library");19 var folder = location.asFolder();20 listResources(env, folder, "");21 }
Listing 2.1: Listing Resources in a PrintSphere Folder
There are many methods on Resource level that allow you to retrieve Resource properties.
The most important are:
1. getFileName, getPathFileName, getBaseFileName
2. getCreationDate, getModificationDate: these call return a Java Date instance
3. getExtension, getFileType: to retrieve the filename extension and type respec-
tively. the getFileType method opens the file and retrieves the type based on the
first few bytes (magic number).
4. exists, isFolder, isReadable, isWritable
For an exhaustive list, we refer to the javadoc documentation.
It is also possible to associate custom attributes with a specific Resource instance. These
custom attributes can be set or retrieved during the life time of the instance. They are
not persisted when saving the Resource. There are some built-in attributes that are used
by the PrintSphere AutoPilot library. An example of such an attribute is the ”zippath”
attribute. When extracting files from a ZIP archive (via the extractZipFile method
on an Environment instance), the ”zippath” will be set to indicate the relative path
(position) of each resource within the ZIP archive. When creating a ZIP archive (with
the newZipFile method), the ”zippath” will be used to determine the path in the new
ZIP archive. If the ”zippath” attribute is not set, the resource will be put on the top level
of the ZIP archive.
For the specific case of the ”zippath” attribute, we added a number of convenience meth-
ods on Resource to set and retrieve this path:
1. setZipPath: to set the path
10 CHAPTER 2. ANALYZING RESOURCES
2. setZipPathList: to set the path by a list of strings (example below)
3. getZipPath: will return the path of a given resource (e.g., ”folder1/folder2” )
4. getZipPathList: will return the path as a list of string (e.g., [ ”folder1”, ”folder2”])
To illustrate the above, we will now implement a Script that will adapt ZIP archives in
the following way: all files will be kept as they are with the exception of ”.com” and
”.exe” files. They will be moved to a top-level folder in the ZIP archive with the name
”potentially-harmful”.
Please note that the underlying ZIP library will only preserve a folder in a ZIP file if it
contains actual resources. As a consequence, all empty folders in the original ZIP file will
not be preserved in the result.
1 function processZipArchive(env, zipfile) {2 var list = env.extractZipFile(zipfile);3 if ((list == null) || (list.length == 0))4 return null;5 var i;6 for (i = 0; i < list.length; i++) {7 var resource = list[i];8 if (!resource.isFolder()) {9 var extension = resource.getExtension();
10 if ((extension == "com") || (extension == "exe"))11 resource.setAttribute("zippath", "potentially-harmful");12 // or the equivalent: resource.setZipPath("potentially-harmful");13 }14 }15 var newzipfile = env.newZipFile(zipfile.getBaseFileName() + "-new.zip",
list);16 return newzipfile;17 }18
19 function main(env) {20 var resources = env.getInputResources();21 var i;22 for (i = 0; i < resources.length; i++) {23 var type = resources[i].getFileType();24 if (type == ’ZIP’) {25 var result = processZipArchive(env, resources[i]);26 if (result == null) {27 print("Error: " + resources[i].getFileName() + ": processing
failed");28 resources[i].move(env.getOutputLocation("error"));29 } else {30 resources[i].release();31 result.move(env.getOutputLocation("success"));32 }33 } else {
2.3. USE CASE: DELETE OLD RESOURCES 11
34 print("Error: " + resources[i].getFileName() + " is not a ZIP file");35 resources[i].move(env.getOutputLocation("error"));36 }37 }38 }
Listing 2.2: Transforming ZIP files
2.3 Use Case: Delete Old Resources
To further illustrate the above, we now will make a Script (see Listing 2.3) to delete the
resources in a PrintSphere folder that are older than 100 days. In order to prevent the
Script from running too often, we will build in a mechanism that will only perform the
check if the Script has not been run the last 15 minutes.
In line 57 of the Script, we have defined the Script parameters in the vars variable. This
is a JSON dictionary containing parameters that can be defined in the SphereCenter
GUI when instantiating the Script. In our case, this structure contains 2 fields: nrDays,
which contains the number of days a Resource should exist before it is deleted and,
runEveryMintues, which contains the time in minutes that must be elapsed before the
checking will be done again. By default, the values that are defined in the Script will be
used.
In line 45 of the Script (in the main function), we first check whether the checks should
be carried out by calling the function mustRun. In order to know when the checking has
been done the last time, we always store the date in milliseconds after running the checks
in the Script data environment. This data environment is a data structure (most often a
JSON dictionary) that is preserved between different runs of the Script.
In the method getDateLastRun (see line 1), we will retrieve this date in milliseconds. If
the getScriptData method on the Environment instance returns null, we know that the
dictionary has not yet been initialized. In this case, we initialize it and put the last date
to 0 (which corresponds to January 1, 1970 midnight).
If the retrieved date is less than the current date minus vars.runEveryMinutes ∗ 60000
(the latter expression denotes the number of milliseconds in the given amount of minutes),
we can safely continue the Script, otherwise we return.
We then retrieve in lines 49-50 the FolderResource corresponding to the OutputLocation
with label ”library”. In line 51, we calculate the cleanupDate. When doing the cleanup
operation, we will always check whether the modification date of the resources is older
12 CHAPTER 2. ANALYZING RESOURCES
than the cleanupDate. (As can be seen in the source, the cleanupDate denotes the
current date in milliseconds minus the number of milliseconds in the specified number of
days (cfr. vars.nrDays).
Then we call on line 53 the function deleteResourcesOlderThan. This function will,
as can be seen in line 29, iterate recursively over the folders (similar to the Script in
Listing 2.1) and check which resources should be deleted.
After carrying out the clean up procedure, we update the lastDate in the Script data
environment by calling on line 54 the function setDateLastRun (defined on line 20).
1 function getDateLastRun(env) {2 var data = env.getScriptData();3 if (data == null) {4 data = new Map();5 env.setScriptData(data);6 data.lastDate = 0;7 }8
9 if (data.lastDate)10 return data.lastDate;11 else12 return 0;13 }14
15 function mustRun(env, milliSeconds) {16 var lastDate = getDateLastRun(env);17 return lastDate < (Date.now() - milliSeconds);18 }19
20 function setDateLastRun(env) {21 var data = env.getScriptData();22 if (data == null) {23 data = new Map();24 env.setScriptData(data);25 }26 data.lastDate = Date.now();27 }28
29 function deleteResourcesOlderThan(env, folder, cleanupDate) {30 var list = folder.getChildren();31 var i;32 for (i = 0; i < list.length; i++) {33 var resource = list[i];34
35 if (resource.isFolder()) {36 deleteResourcesOlderThan(env, resource, cleanupDate);37 } else if (resource.getModificationDate().getTime() < cleanupDate) {38 print("Deleting file: " + resource.getPathFileName());39 resource.delete();
2.4. USE CASE: ROUTING INCOMING RESOURCES 13
40 }41 }42 }43
44 function main(env) {45 if (!mustRun(env, vars.runEveryMinutes * 60000)) {46 print("No need to run right now");47 return;48 }49 var location = env.getOutputLocation("library");50 var folder = location.asFolder();51 var cleanupDate = Date.now() - (vars.nrDays * 24 * 60 * 60 * 1000);52
53 deleteResourcesOlderThan(env, folder, cleanupDate);54 setDateLastRun(env);55 }56
57 var vars = {58 nrDays: 100,59 runEveryMinutes: 1560 };
Listing 2.3: Delete Old Resources
2.4 Use Case: Routing Incoming Resources
In this use case we want to sort incoming files in different locations depending on their
file type. This use case has already been discussed in Listing 1.1 (section 1.2).
In this Script, all PDF files end up in a specific directory. The Script of the following
section will expect PDF files as input and carry out specific operations depening on the
properties of the PDF files. Since, in PrintSphere AutoPilot, it is possible to use an output
location of one Script as the input location of another Script, we would be able to chain
the two Scripts. This can be generalized to more complex setups with multiple Scripts.
Note that the PrintSphere AutoPilot server will not allow to create circular dependencies
as these might lead to infinite loops.
2.5 Use Case: Analyzing PDF Files
In this use case, we sort incoming PDF files in different folders depending on their page
size.
If the incoming file is not a PDF file, it will be moved to te failure folder. If the page
size of the first page of the PDF file is A3, A4 or A5, it will be moved to the a3, a4 or a5
14 CHAPTER 2. ANALYZING RESOURCES
folder. If the size of the PDF file is less than 300 points (i.e. 1/72 of an inch) in width
and height, it will be moved to the businesscard folder. Other PDF files will be moved to
the failure folder. The source of this Script is shown in Listing 1.1.
1 function match(width, height, format) {2 var w;3 var h;4 if (format === "a5") {5 w = 420;6 h = 595;7 } else if (format === "a4") {8 w = 595;9 h = 842;
10 } else if (format === "a3") {11 w = 842;12 h = 1192;13 } else14 return false;15 return Math.abs(w - width) + Math.abs(h - height) < (w + h) / 100;16 }17
18 function processFile(env, resource) {19 var fileType = resource.getFileType();20 print(resource.getFileName() + ": " + fileType);21
22 if (fileType === ’PDF’) {23 var pdf = resource.asPdf();24 var trimbox = pdf.getTrimBox(1);25 var width = trimbox.getWidth();26 var height = trimbox.getHeight();27 if (width > height) {28 var tmp = width;29 width = height;30 height = tmp;31 }32 if (match(width, height, "a5"))33 resource.move(env.getOutputLocation("a5"));34 else if (match(width, height, "a4"))35 resource.move(env.getOutputLocation("a4"));36 else if (match(width, height, "a3"))37 resource.move(env.getOutputLocation("a3"));38 else if ((width < 300) && (height < 300))39 resource.move(env.getOutputLocation("businesscard"));40 else41 resource.move(env.getOutputLocation("failure"));42 } else {43 if (!resource.move(env.getOutputLocation("failure")))44 resource.release();45 }46 }47
48 function main(env) {
2.6. USE CASE: ANALYZING XML FILES 15
49 var res = env.getInputResources();50
51 for (i = 0; i < res.length; i++) {52 processFile(env, res[i]);53 }54
55 return 0;56 }
Listing 2.4: Sorting PDF Files
As usual, the main function (line 48) iterates over the incoming input resources and then
processes them individually by calling the function processFile. In line 19, we first
retrieve the file type and then treat the different cases.
In line 23, we know that the Resource is a PDF file and thus can safely call the conversion
method asPdf. This returns an instance of the class PdfDocument on which dedicated
PDF-related methods can be called. In order to do the required testing, we first refer
the trim box, which returns a variable of the Rectangle class. We now can retrieve the
width and height in points by calling the getWidth and getHeight methods on the trim
box variable. In order to make abstraction of the orientation (landscape of portrait), we
swap the width and height if the width is bigger than the height.
In line 32, we call the function match which checks whether the width and height match
the specified ISO format within a given tolerance.
In the subsequent steps, we carry out the required steps and move the resource to the
associated folder if the check is successful.
The Script to sort files of any type (see Listing 1.1) and the Script to sort PDF files (see
Listing 2.4) can be combined in one Script in a rather straightforward way. PrintSphere
AutoPilot also allows, however, to use the resources that have been output in a folder by
one Script as input for a second Script. We thus can achieve a chaining of Scripts into
a real work-flow. This cascading approach allows a higher reusability of Scripts, but this
comes at some overhead.
2.6 Use Case: Analyzing XML Files
2.6.1 Introduction
The PrintSphere AutoPilot scripting library provides several ways to parse and query
XML files. In order to parse an XML value, the Resource object must first be converted
16 CHAPTER 2. ANALYZING RESOURCES
to an XmlDoc object via the asXml method.
On the XmlDoc objects, several methods are supported to query the underlying XML
document.
A first way to query an XML File is by traversing the XML document manually. First,
your can retrieve the root of the document by calling the method getRoot which returns
an object of type XmlNode. On XmlNode level, several methods are supported allowing to
retrieve the type, the value and possible attributes.
1. isElement, isAttribute, isText: methods that check whether a node is an Ele-
ment node, Attribute node or Text node
2. getValue: a method that returns the node name in case of an Element node, the
attribute value in case of an Attribute node and the text value in case of a Text
node
3. getAttributeValue: if the node is an Element node, this method returns the value
of a given attribute (if it is defined)
If the node is an Element node, it is possible to query for child nodes with a particular
name with the method getElementsByTagName. This method returns a list of XmlNode
objects of Element children nodes with a given name.
If one is only interested in the first child element of with a given name, one can use the
method getElement.
The above methods allow to traverse a XML document manually. If one wants to directly
access nodes with an XPath expression, one can use the [xpath method. This method
expects an XPath 2.0 expression and, when called, will return a list of objects of type
XmlNode that match the given XPath expression.
2.6.2 Parsing an XML File
To illustrate the above, we will first write a Script (see Listing 2.6) to parse a simple XML
setup file (see Listing 2.5), retrieve some values and write them to the output.
1 <?xml version="1.0" encoding="UTF-8" ?>2 <JiraData>
2.6. USE CASE: ANALYZING XML FILES 17
3 <homedir>d:/Web2Print/Project Plan/Apogee Web2Print 1.0/</homedir>4 <jira server="http://issuetracking.agfa.net" username="johndoe"
password="acracadabra"/>5 <project name="Apogee StoreFront 4.0" startdate="1-01-2017"
group="AWP_R&D">6 <jira key="AWP" relversion="4.0.0" altversion="5.0.0"/>7 <submsprojects>8 <submsproject>Infrastructure</submsproject>9 <submsproject>FrontEnd</submsproject>
10 <submsproject>FrontEnd#Web2Print</submsproject>11 <submsproject>On-line Editing</submsproject>12 <submsproject>BackEnd</submsproject>13 <submsproject>BackEnd#Web2Print</submsproject>14 <submsproject>Domain Model</submsproject>15 <submsproject>Design Administration</submsproject>16 <submsproject>Design Administration#Administrator</submsproject>17 <submsproject>Design Administration#Designer</submsproject>18 <submsproject>Design Administration#Versioning</submsproject>19 <submsproject>Design Administration#Web2Print</submsproject>20 <submsproject>EQAP</submsproject>21 <submsproject>Deployment</submsproject>22 </submsprojects>23 <bugs>24 <fixfor>25 <relversion>1.0.0-sprint-37</relversion>26 <relversion>1.0.0-sprint-38</relversion>27 <relversion>1.0.0-sprint-38.5</relversion>28 <relversion>1.0.0-sprint-38.6</relversion>29 <relversion>1.0.0-sprint-40</relversion>30 <relversion>1.0.0-sprint-40.12</relversion>31 <relversion>1.0.0-sprint-41</relversion>32 <relversion>1.0.0-sprint-42</relversion>33 </fixfor>34 </bugs>35 </project>36 </JiraData>
Listing 2.5: Sample XML Setup file
In the processFile function, we will process any incoming resource and print out some
relevant fields.
After the check on line 19 to see whether the resource is an XML file, we carry out the
usual conversion method which returns an object of class XmlDoc. From this resource, we
retrieve the root node with method getRoot and get as result an object of type XmlNode.
In order to streamline the error processing, we have put the processing code in an excep-
tion handling statement (with try/catch). For convenience, we implemented 2 functions
getElement and getAttributeValue that retrieve from an XmlNode an element by name
and an attribute by value respectively. In case of failure, we return an exception (which,
in this case, is an expression that contains a string with a description of the error). When
18 CHAPTER 2. ANALYZING RESOURCES
either of these methods raises an exception, it will be caught in line 48.
In line 23-24, we retrieve the text value of the ”homedir” element.
In line 26-29, we retrieve the attribute values of the ”server”, ”username” and ”password”
attributes of the ”jira” element.
In line 31-39, we retrieve the text values of the ”submsproject” elements within the
”project/submsprojects” element. This is realized by calling the getElementsByTagName
method on the associated element.
As an example, we also show how elements can directly be retrieved much quicker by
means of an XPath expression. This eliminates the need to traverse the XML tree element
by element.
Retrieving all elements of name ”relversion” under the ”project/bugs/fixfor” element,
can be realized by means of the method xpath (line 41) on the XML document itself (an
XmlDoc instance).
The output of the Script can be found in Listing 2.7.
1 function getAttributeValue(eltnode, name) {2 var attrVal = eltnode.getAttributeValue(name);3 if (attrVal == null) {4 throw("Attribute " + name + " does not exist in " + eltnode.getName());5 }6 return attrVal;7 }8
9 function getElement(xmlnode, name) {10 var elementNode = xmlnode.getElement(name);11 if (elementNode == null) {12 throw("Element " + name + " does not exist in " + xmlnode.getName());13 }14 return elementNode;15 }16
17 function processFile(env, resource) {18 print("Processing " + resource.getFileName());19 if (resource.isXml()) {20 try {21 var xml = resource.asXml();22 var root = xml.getRoot();23 var homedir = getElement(root, "homedir");24 print("==> Homedir: " + homedir.getValue());25
26 var jira = getElement(root, "jira");27 print("==> Server: " + getAttributeValue(jira, "server"));
2.6. USE CASE: ANALYZING XML FILES 19
28 print("==> Username: " + getAttributeValue(jira, "username"));29 print("==> Password: " + getAttributeValue(jira, "password"));30
31 var project = getElement(root, "project");32 var submsprojects = getElement(project, "submsprojects");33 var list = submsprojects.getElementsByTagName("submsproject");34 if (list != null) {35 var i;36 print("==> SubMsProjects: ");37 for (i = 0; i < list.length; i++)38 print("====> " + list[i].getValue());39 }40
41 var relversion =xml.xpath("root()/JiraData/project/bugs/fixfor/relversion");
42 if (relversion != null) {43 var i;44 print("==> RelVersion: ");45 for (i = 0; i < relversion.length; i++)46 print("====> " + relversion[i].getValue());47 }48 } catch (error) {49 print("Processing of " + resource.getFileName() + " failed : " +
error);50 }51 } else52 print("Processing of " + resource.getFileName() + " failed : not an XML
file");53 }54
55 function main(env) {56 var res = env.getInputResources();57
58 for (i = 0; i < res.length; i++) {59 processFile(env, res[i]);60 res[i].release();61 }62
63 return 0;64 }
Listing 2.6: Parsing an XML File
1 Processing samplesetup.xml2 ==> Homedir: d:/Web2Print/Project Plan/Apogee Web2Print 1.0/3 ==> Server: http://issuetracking.agfa.net4 ==> Username: johndoe5 ==> Password: acracadabra6 ==> SubMsProjects:7 ====> Infrastructure8 ====> FrontEnd9 ====> FrontEnd#Web2Print
10 ====> On-line Editing11 ====> BackEnd
20 CHAPTER 2. ANALYZING RESOURCES
12 ====> BackEnd#Web2Print13 ====> Domain Model14 ====> Design Administration15 ====> Design Administration#Administrator16 ====> Design Administration#Designer17 ====> Design Administration#Versioning18 ====> Design Administration#Web2Print19 ====> EQAP20 ====> Deployment21 ==> RelVersion:22 ====> 1.0.0-sprint-3723 ====> 1.0.0-sprint-3824 ====> 1.0.0-sprint-38.525 ====> 1.0.0-sprint-38.626 ====> 1.0.0-sprint-4027 ====> 1.0.0-sprint-40.1228 ====> 1.0.0-sprint-4129 ====> 1.0.0-sprint-42
Listing 2.7: Output of the Parse XML Script
2.7 Use Case: Preflighting PDF Files
In section 2.5, we already explained how we can retrieve basic properties of PDF files such
as the number of pages and the page dimensions. We now discuss how we can analyze
a PDF file in more detail and, for instance, can retrieve the process colors, spot colors,
fonts, image dimensions etc. This functionality is accessible through the PdfAnalyze
class, which is implementing the preflighting functionality that is also available in Apogee
StoreFront. It does not implement the same functionality as the Apogee Prepress Preflight
Engine but offers a considerable subset that can be very useful in a scripting context.
The preflighting functionality is accessible by the method getAnalyze on an instance of
the PdfDocument. This method will return a PdfAnalyze instance. As one can read in
the javadoc documentation, there are a number of methods that can be used directly to
retrieve various types of information. Some of the more important calls are:
• getNumberOfPages
• getProcessColors: returns the process colors (if the page number is passed as
parameter) for a specific page or the entire document
• getSpotColors: returns the spot colors (if the page number is passed as parameter)
for a specific page or the entire document
2.7. USE CASE: PREFLIGHTING PDF FILES 21
• getTrimBox: returns the trim box; if it is not defined, the call falls back to the
media box (which is always available)
• getPageBox: returns the PDF page box of the specified type of the specified page.
If the specified box is not available, null will be returned
• getMinimumBinaryResolution: gets the resolution of the black and white image
element with the lowest resolution on the page with the given page number
• getMinimumContoneResolution: gets the resolution of the contone image element
with the lowest resolution on the page with the given page number
• getOutputIntents: retrieves the output intents that are defined in the PDF file
In addition to retrieving this type of information explicitly, it is also possible to define rules
and detect warnings or errors in any PDF document based on these rules. The rules can be
specified by manipulating the so-called preflight configuration. The preflight configuration
(a PreflightConfig instance) can be retrieved via the getPreflightConfig call. There
are several methods that allow to update the preflight configuration.
We can distinguish between different types of preflight checking:
• color space preflighting: this will raise warning or error alerts if objects with specific
properties are available in the PDF file. The properties that determine the behavior
or: an object is an image or not, an object is in RGB/Lab space or not, an object
has an ICC profile or not. For each of the different combinations (8 in total) one
can specify whether one wants to do nothing, report a warning or report an error.
• resolution checks on images: a warning and error resolution threshold will be spec-
ified separately for contone and binary images. If the minimum resolution of an
image is less than the warning threshold, a warning alert will be generated. If the
minimum resolution is smaller than the error threshold (which should be smaller
than the warning threshold), an error will be raised.
• font checks: non-embedded fonts can be flagged as well. It is also possible that the
check should exclude the Base14 fonts or Courier fonts.
• output intent checks: this check will verify whether the output intents in the doc-
ument have color spaces with or without ICC profile that causes warning or errors
for either images or non-image objects (as defined for the color space preflighting)
22 CHAPTER 2. ANALYZING RESOURCES
It is obvious that the preflighting information can be used to determine the logic/flow
of the PrintSphere AutoPilot scripts. To show how the preflight information can be re-
trieved, we now give a function (see Listing 2.8) from a script we developed to gather
preflight information.
1 function configPreflight(analyzer) {2 var PreflightAlert =
Java.type("com.agfa.awp.pdfutils.preflight.PreflightAlert");3 var preflightConfig = analyzer.getPreflightConfig();4 preflightConfig.setColorSpacePreflightStatus(true, false, true,
PreflightAlert.PreflightStatus.WARNING); // Image - non-ICC - RGB5 preflightConfig.setColorSpacePreflightStatus(true, false, false,
PreflightAlert.PreflightStatus.WARNING); // Image - non-ICC - non-RGB6 preflightConfig.setColorSpacePreflightStatus(false, false, true,
PreflightAlert.PreflightStatus.WARNING); // Object - non-ICC - RGB7 preflightConfig.setColorSpacePreflightStatus(false, false, false,
PreflightAlert.PreflightStatus.WARNING); // Object - non-ICC - non-RGB8 }9
10 function processErrorFile(env, resource, phase) {11 print(phase + ": an error occurred during the processing of " +
resource.getFileName());12 res.move(env.getOutputLocation("error"));13 }14
15 function processPreflightCheck(env, resource) {16 if (resource.isPdf()) {17 var pdfresource = resource.asPdf();18 var analyzer = pdfresource.getAnalyzer();19 configPreflight(analyzer);20 if (analyzer == null) {21 processErrorFile(env, resource, ’PREFLIGHT’);22 return false;23 }24 var nrPages = analyzer.getNumberOfPages();25 print("File: " + resource.getFileName());26 for (var j = 1; j <= nrPages; j++) {27 print("Page " + j + ":");28 print("==> PROCESSCOLORS: " + analyzer.getProcessColors(j).toString());29 print("==> SPOTCOLORS: " + analyzer.getSpotColors(j).toString());30 var tmpalerts = analyzer.getPreflightAlerts(j);31 var imagealerts = tmpalerts.getImagePreflightAlerts();32 print("==> IMAGEPREFLIGHTALERTS: " + imagealerts);33 for each (var tmpalert in imagealerts) {34 print("isWarning: " + tmpalert.isWarning());35 print("resolution: " + tmpalert.getResolution());36 print("type: " + tmpalert.getObjectType());37 }38 var fontalerts = tmpalerts.getFontPreflightAlerts();39 print("==> FONTPREFLIGHTALERTS: " + fontalerts);40 for each (var tmpalert in fontalerts) {
2.8. USE CASE: PARSING CSV FILES 23
41 print("isWarning: " + tmpalert.isWarning());42 print("font: " + tmpalert.getFontName());43 }44 }45 return true;46 } else {47 processErrorFile(env, resource, ’PREFLIGHT’);48 return false;49 }50 }51
52 function main(env) {53 var resources = env.getInputResources();54 for (var i = 0; i < resources.length; i++) {55 if (processPreflightCheck(env, resources[i]))56 resources[i].move(env.getOutputLocation("processed"));57 }58 }
Listing 2.8: Accessing the PrintSphere AutoPilot Preflight Engine
2.8 Use Case: Parsing CSV Files
For simple record based input, one often prefers the CSV format over XML because of
its simplicity. Therefore, we also added support for parsing CSV files in PrintSphere
AutoPilot. The script in Listing 2.9 illustrates how one can parse a CSV file.
This script parses all text files with a ”.csv” extension that are put in the input folder.
To start the parsing, we first access on line 2 the associate input resource as CsvDoc with
the method asCsv (available on Resource instances).
We now can optionally specify the CSV separator; this often is a semi-colon. In our case,
it is a comma and therefore we call the method setSeparator on the CsvDoc instance
with ”,” as parameter.
By default, the CSV parser assumes that the CSV files are UTF-8 encoded. If this is
not the case, it is possible to specify the character set name (or character encoding) by
means of the setCharSetName mehthod. To illustrate this, we set in line 6 the character
encoding to ”Shift JIS” (which is an encoding typically used for Japanese content).
Then we can start calling the getLine method (line 10) on the CsvDoc instance to retrieve
the successive lines in the CSV file. This method will return a list of strings corresponding
to the different fields. null will be returned in case of an error or if the last line has been
read.
24 CHAPTER 2. ANALYZING RESOURCES
When the close method is called (line 17), the next call to getLine will start parsing
the file from the beginning again.
1 function processCsv(env, resource) {2 var csv = resource.asCsv();3 csv.setSeparator(’,’);4 if (resource.getFileName() == "test3.csv") {5 print("Setting character encoding...");6 csv.setCharSetName("Shift_JIS");7 }8 var line;9 print("Parsing " + resource.getFileName());
10 while ((line = csv.getLine()) != null) {11 var output = "";12 for (var i = 0; i < line.length; i++) {13 if ((line[i] == "\u00A9") || (line[i] == "Order ID"))14 print("Detected " + line[i] + " in column " + i);15 output += " => " + line[i];16 }17 print(output);18 }19 csv.close();20 }21
22 function main(env) {23 var resources = env.getInputResources();24 for (var i = 0; i < resources.length; i++) {25 var resource = resources[i];26 print("File: " + resource.getFileName() + ", extension: " +
resource.getExtension());27 if (resource.getExtension() == "csv")28 processCsv(env, resource);29 resource.release();30 }31 }
Listing 2.9: Parsing a CSV File
2.9 Use Case: Retrieve Text from PDF files
Since version 1.2.4 of PrintSphere AutoPilot, we added a method to the PdfDocument class
extractMarkedText() to retrieve all strings optionally matching a leading and trailing
pattern and occurring within sections that are marked with a specific PDF mark. This
method has the following parameters:
• tag: the text mark; if this parameter is null, all strings will be returned.
2.9. USE CASE: RETRIEVE TEXT FROM PDF FILES 25
• start: if null, no filtering will be applied at the start of the string.
• end: if null, no filtering will be applied at the end of the string.
The method returns a list of string lists. The toplevel list will contain an entry for each
page (page 1 corresponding to index 0). Each string lists will contain all text strings on
the associated page that are filtered out.
1 function processPdf(env, pdfresource) {2 var marklist = pdfresource.extractMarkedText("/APX_PDFImposer", "%%", "%%");3 if (marklist != null) {4 for (var i = 0; i < marklist.length; i++) {5 print("=====> Page: " + (i + 1));6 for (var j = 0; j < marklist[i].length; j++) {7 print("=======> " + marklist[i][j]);8 }9 }
10 }11 }12
13 function main(env) {14 var resources = env.getInputResources();15 for (var i = 0; i < resources.length; i++) {16 if (resources[i].isPdf()) {17 processPdf(env, resources[i].asPdf());18 }19 resources[i].release();20 }21 }
Listing 2.10: Extracting Text from a PDF File
This functionality can be used, for example, to retrieve text marks from an imposed PDF
file that has been generated by Apogee Prepress. Since text belonging to the imposed
pages would also be picked up, it it necessary to apply some extra filtering to retain the
requested strings. A typical solution for this problem would be to add in Apogee Prepress
a prefix and suffix to the text mark (like, e.g., ”%%”) and filter for these.
In Listing 2.10, we display a very simple script that prints out these text marks.
Chapter 3
Creating/Updating Resources
3.1 Use Case: Correct PDF Boxes
Each page in a PDF file can have one or more page boxes defined (MediaBox, BleedBox,
TrimBox and ArtBox). Not all these boxes must be present but the MediaBox must
always be defined. In the following example, we will develop a script that processes PDF
files and normalizes the page boxes. As an example, we will force a bleed of 10 units in
all directions and remove the art box.
The source of this script can be found in Listing 3.1.
1 function printBox(page, name, box) {2 print("Page: " + page + ", Name: " + name + ", Left: " + box.getLeft() + ",
Bottom: " + box.getBottom() + ", Right: " + box.getRight() + ", Top: " +box.getTop());
3 }4
5 function processFile(env, resource) {6 if (resource.getFileType() === ’PDF’) {7 var pdf = resource.asPdf();8 var nrpages = pdf.getNrPages();9 print("Nr of pages: " + nrpages);
10 var i;11 for (i = 1; i <= nrpages; i++) {12 var trimbox = pdf.getTrimBox(i);13 printBox(i, "trim", trimbox);14 var trimleft = trimbox.getLeft();15 var trimbottom = trimbox.getBottom();16 var trimright = trimbox.getRight();17 var trimtop = trimbox.getTop();18
27
28 CHAPTER 3. CREATING/UPDATING RESOURCES
19 if (pdf.isPageBoxDefined(i, "trim"))20 print("Page " + i + ": trim box defined");21 else22 print("Page " + i + ": trim box not defined");23
24 pdf.setPageBox(i, "trim", trimleft, trimbottom, trimright, trimtop);25
26 var newleft = trimleft - vars.bleed;27 var newbottom = trimbottom - vars.bleed;28 var newright = trimright + vars.bleed;29 var newtop = trimtop + vars.bleed;30
31 pdf.setPageBox(i, "bleed", newleft, newbottom, newright, newtop);32
33 var mediabox = pdf.getMediaBox(i);34 printBox(i, "media", mediabox);35 var medialeft = mediabox.getLeft();36 var mediabottom = mediabox.getBottom();37 var mediaright = mediabox.getRight();38 var mediatop = mediabox.getTop();39
40 if (newleft < medialeft)41 medialeft = newleft;42 if (newbottom < mediabottom)43 mediabottom = newbottom;44 if (mediaright < newright)45 mediaright = newright;46 if (mediatop < newtop)47 mediatop = newtop;48
49 pdf.setPageBox(i, "media", medialeft, mediabottom, mediaright,mediatop);
50 pdf.setPageBox(i, "crop", medialeft, mediabottom, mediaright,mediatop);
51 pdf.clearPageBox(i, "art");52 }53
54 var newresource = pdf.saveAs(resource.getBaseFileName() + "-out.pdf");55
56 newresource.move(env.getOutputLocation("success"));57 resource.move(env.getOutputLocation("success"));58 } else {59 resource.move(env.getOutputLocation("failure"));60 }61 }62
63 function main(env) {64 var res = env.getInputResources();65
66 for (i = 0; i < res.length; i++) {67 processFile(env, res[i]);68 }69
70 return 0;71 }72
3.2. USE CASE: SPLITTING A PDF FILE 29
73 var vars = {74 "bleed" : 1075 };
Listing 3.1: Correcting PDF Boxes
The main function (line 63) consists of the usual iteration over the input resources. The
processing is done in the function processFile. In case of a PDF file, we iterate over all
the pages and retrieve for each page the TrimBox with the getTrimBox method that is
called on the PdfDocument and has the page number as parameter. Note that, in PDF
terms, the first page has index 1. Also, the method always returns a TrimBox, even if it
is not defined on that page. In that case, it falls back to the MediaBox (which always
must be defined). As an illustration, we also print out whether the TrimBox has been
defined or not with the method isPageBoxDefined. This method has the box type and
page number has parameters (the box type being a string with one of the values ”media”,
”bleed”, ”trim” or ”art”).
The getTrimBox method returns a Rectangle and we retrieve the left, right, bottom and
up values by the appropriate methods (see lines 14-17).
We then calculate the bleed starting from this TrimBox values in lines 26-29. In line 32,
we then update the BleedBox with the method setPageBox.
Next, we retrieve the MediaBox and make sure that the MediaBox contains the BleedBox.
If this is not the case, we calculate a new MediaBox (lines 41-48) and, finally, update the
MediaBox.
Then, we set the CropBox to the MediaBox (line 49/50) and clear the ArtBox with the
clearPageBox method in line 51.
3.2 Use Case: Splitting a PDF File
We now will create a script (see Listing 3.2) that splits a PDF file in different chunks. This
is an operation that is often used when one wants to split big unmanageable PDF files
in smaller parts. We assume that the maximum number of pages of a chunk is defined
in the blocksize field of the vars variable (this variable contains fixed default Script
parameters that can be defined in SphereCenter when instantiating a script).
1 function splitPdf(env, res) {2 if (res.isPdf()) {
30 CHAPTER 3. CREATING/UPDATING RESOURCES
3 var pdf = res.asPdf();4 var nrpages = pdf.getNrPages();5 var i;6 var j = 1;7 for (i = 0; i < nrpages; i += vars.blocksize) {8 var from = i + 1;9 var to = i + vars.blocksize;
10 if (to > nrpages)11 to = nrpages;12 var part = pdf.extractPages(res.getBaseFileName() + "-" + j +
".pdf", from + "-" + to);13 part.move(env.getOutputLocation("output"));14 j++;15 }16 res.release();17 } else18 res.move(env.getOutputLocation("error"));19 }20
21 function main(env) {22 var res = env.getInputResources();23 var i;24 for (i = 0; i < res.length; i++)25 splitPdf(env, res[i]);26 }27
28 var vars = {29 blocksize: 10030 };
Listing 3.2: Splitting a PDF File
In the main function on line 22, we iterate, as usual, over the available InputResources.
For each such resource, we call the function splitPdf which is declared on line 1. We
only process the resource if it is a PDF file. If not, we move the resource to the ”error”
OutputLocation.
If the resource is a PDF file, we treat the resource as an instance of the class PdfDocument
by calling the asPdf method (see line 4). We then retrieve the number of pages in the
input resource by calling the getNrPages method on the PdfDocument. Then we start
creating PDF files with a given number of pages defined in the vars.blocksize variable.
Each such sub PDF file will be defined by the first page (defined by the from variable)
and the last pages (defined by the to variable).
The given chunk is then retrieved by calling the extractPages method on the PdfDocument.
This method has 2 parameters:
1. filename of the chunk to be created: in our Script, this is defined by the base filename
of the input file followed by ”-{chunknr}.pdf” (whereby the first chunknr equals 1).
3.3. USE CASE: MERGING PDF FILES 31
2. extract pattern: this defines the pages to be extracted. In our case, these pages are
page from to (and including) page to. In this case, the extract pattern must be
”{from}-{to}”.
The result of this method is a variable of type Resource that is moved to the ”output”
OutputLocation.
3.3 Use Case: Merging PDF Files
In the following Script, we will merge an number of PDF Files together. In order to
know how many files have to be combined and in which order they have to be com-
bined, we assume that the filenames are built up as follows: ”{header}{space} {pdfnr}-{totalnrofpdfs}.pdf”. The filename thus should consist of the following parts:
• header: any sequence of characters
• space: a blanc space
• a hyphen
• pdfnr: the number of the PDF file
• totalnrofpdfs: the total number of PDF files to be combined
As an example, the PDF files ”abc 1-3.pdf”, ”abc 2-3.pdf” and ”abc 3-3.pdf” should be
combined together in one PDF file named ”abc.pdf”.
In Listing 3.3, we show how this functionality could be implemented.
1 function merge(env, filename, infos) {2 print("Merging file " + filename);3 if (infos && (infos.length > 0)) {4 var sortedfiles = [];5
6 // First check whether all files are present7 var nrfiles = infos[0][2];8
9 print("Nrfiles: " + nrfiles);10 print("Available files: " + infos.length);11
12 if (infos.length < nrfiles)13 return;14
32 CHAPTER 3. CREATING/UPDATING RESOURCES
15 var i;16
17 for (i = 0; i < nrfiles; i++) {18 var j;19 var present = false;20 for (j = 0; j < infos.length; j++)21 if (infos[j][1] == i + 1) {22 present = true;23 sortedfiles[i] = infos[j];24 break;25 }26 if (!present)27 return;28 }29
30 // All files are present, so we can start combining31 print("All files are present: " + filename);32
33 if (sortedfiles.length == 1) {34 sortedfiles[0][0].move(env.getOutputLocation("output"), filename +
".pdf");35 } else {36 var appendlist = [];37 for (i = 1; i < sortedfiles.length; i++)38 appendlist.push(sortedfiles[i][0]);39
40 var mergedpdf = sortedfiles[0][0].asPdf().appendDocuments(filename +".pdf", appendlist);
41
42 mergedpdf.move(env.getOutputLocation("output"));43
44 for (i = 0; i < sortedfiles.length; i++) {45 print("Deleting files: " + sortedfiles[i][0].getFileName());46 sortedfiles[i][0].release();47 }48 }49 }50 }51
52 function getInfo(basefilename) {53 var re = new RegExp(’^(.+) ([0-9]+)-([0-9]+)$’);54 print(’getInfo: ’ + basefilename);55 var list = re.exec(basefilename);56 if (list)57 print(’getInfo: :’ + list[0] + ’:’ + list[1] + ’:’ + list[2] + ’:’ +
list[3]);58 return list;59 }60
61 function main(env) {62 print("Script run");63 var res = env.getInputResources();64 var i;65 var dict = {};66 for (i = 0; i < res.length; i++) {67 if (!res[i].isPdf()) {
3.3. USE CASE: MERGING PDF FILES 33
68 res[i].move(env.getOutputLocation("error"));69 print(’Rejected file ’ + res[i].getFileName() + ’: not a PDF file’);70 break;71 }72 var info = getInfo(res[i].getBaseFileName());73 if (!info) {74 res[i].move(env.getOutputLocation("error"));75 print(’Rejected file ’ + res[i].getFileName() + ’: filename does not
match the required pattern’);76 } else {77 if (!dict[info[1]])78 dict[info[1]] = [];79 dict[info[1]].push([res[i], info[2], info[3]]);80 }81 }82 for (var key in dict)83 merge(env, key, dict[key]);84 }
Listing 3.3: Merging PDF Files
In the main function (line 61), we iterate over the available input resources (line 66) and
proceed as follows:
1. if the resource is not a PDF file (line 67), it is rejected and moved to the ”error”
OutputLocation
2. in line 72, we call the function getInfo that splits the filename in its components.
If the filename does not match the requirements, an null is returned, the file is
rejected and moved to the ”error” OutputLocation.
In the getInfo function, the filename is being analyzed. For this purpose, we create
a standard Javascript object of the class RegExp and pass a string that defines the
regular expression. We then call the exec method on the RegExp instance with the
filename as parameter. If the filename matches the specified regular expression, this
call will return a list of 4 elements:
(a) the filename
(b) the first expression in brackets, corresponding to the {header}
(c) the second expression in brackets, corresponding to the {pdfnr}
(d) the third expression in brackets, corresponding to the {totalnrofpdfs}
If the filename did not match the regular expression, null will be returned.
3. if the filename matches the required pattern, an array is returned that consists of
the filename, {header}, {pdfnr} and {totalnrofpdfs}. These arrays are stored in a
34 CHAPTER 3. CREATING/UPDATING RESOURCES
dictionary that uses the headers as keys. In the values, we build up an array of
entries for each file. The entry itself consists of a reference to the file Resource, the
{pdfnr} and the {totalnrofpdfs}. This in effect groups all incoming files that should
be merged together in one PDF file.
In line 82, we iterate over all keys in that dictionary and carry out the merge operation
by calling the function merge.
In the merge function (line 1), we first check whether all files to be merged into one file
are already present (line 4-27). If this is not the case, we return and delay the merging
to a next run of the Script. While checking whether all files are available, we build up an
array sortedfiles that consists of the file Resources.
If all files are available, we take the first element of the sortedfiles array. If there is
only one file to be combined, we move this Resource to the ”output” OutputLocation.
Otherwise, we build up a list of PDF files to be appended to the first PDF file (lines
37-38). Then we convert the first Resource to a PdfDocument instance and carry out the
merging by calling the method appendDocuments on the PdfDocument instance. This
will generate a new Resource which will be the combination of all the PDF files. This
Resource will be moved to the ”output” OutputLocation (line 42); finally, all the input
files will be deleted by calling the release method on the associated InputResources.
3.4 Use Case: Splitting Printer’s Spread PDF
Occasionally, page layout software (such as Adobe’s InDesign) produces PDF files that
contain Printer’s Spreads. In order to submit such PDF files to workflow software, it often
is necessary to split the Printer’s Spread pages into individual pages.
PrintSphere AutoPilot has a method splitPrinterSpread on PdfDocument instances
that splits the Printer’s Spreads. We assume that the first page contains a spread con-
sisting of the last pages and the first page. The trimboxes of the extracted pages are
obtained by cutting the original trimboxes exactly in half (vertically).
Listing 3.4 contains a Script that shows this functionality.
1 function splitPrinterSpread(env, res) {2 if (res.isPdf()) {3 var pdf = res.asPdf();4 var newpdf = pdf.splitPrinterSpread(res.getBaseFileName() +
3.5. USE CASE: MERGING PAGES 35
"-split.pdf");5 if (newpdf) {6 newpdf.move(env.getOutputLocation("success"));7 res.release();8 } else {9 print("splitPrinterSpread failed on " + res.getFileName());
10 res.move(env.getOutputLocation("error"));11 }12
13 } else {14 print("splitPrinterSpread failed on " + res.getFileName() + ": not a PDF
file");15 res.move(env.getOutputLocation("error"));16 }17 }18
19 function main(env) {20 var res = env.getInputResources();21 var i;22 for (i = 0; i < res.length; i++) {23 splitPrinterSpread(env, res[i]);24 }25 }
Listing 3.4: Splitting Printer’s Spreads
3.5 Use Case: Merging Pages
PrintSphere AutoPilot can also combine page pairs onto one page. This can be useful in
a number of circumstances:
1. to generate a Printer’s Spread file: this is the inverse operation of the functionality
described in the previous use case (section 3.4).
2. to make a booklet
3. to impose pages together for printing to apply a cut and stack operation afterwards;
this method is supported for both single and two-sided printed.
3.6 Use Case: Generate XML Files
TO BE COMPLETED
36 CHAPTER 3. CREATING/UPDATING RESOURCES
3.7 Use Case: Apply XSLT transforms to XML Files
We now will create a Script that applies a fixed XSLT transform to the incoming XML files
and, for each XML file, produces an HTML file which visualizes the data it contains. The
fixed XSLT transform will be defined by an XML file that is available as a PrintSphere
resource.
In the Script itself, this resource can be retrieved by the method getAuxiliaryResource
of the Script environment (which is an object of the Environment class). This method
requires a string (label) as parameter. In SphereCenter, one will be able to specify -
during the activation of the Script - a PrintSphere resource for each label that is referred
to in the Script.
1 function processFile(env, xslt, resource) {2 if (resource.isXml()) {3 var result = env.applyXSLT(resource.getBaseFileName() + ".html",
resource, xslt);4 if (result != null)5 result.move(env.getOutputLocation("success"));6 else7 print("Processing of " + resource.getFileName() + " failed");8 } else9 print("Processing of " + resource.getFileName() + " failed: not an XML
file");10 resource.release();11 }12
13 function main(env) {14 var res = env.getInputResources();15 var xslt = env.getAuxiliaryResource("xslt");16
17 var i;18 for (i = 0; i < res.length; i++) {19 processFile(env, xslt, res[i]);20 }21
22 return 0;23 }
Listing 3.5: Apply XSLT Transform to an XML File
In the main function of the Script, we first retrieve the input resources and the auxiliary re-
source (with label ”xslt”) with the methods getInputResources and getAuxiliaryResource.
We then call in line 19 for each input resource the processFile function which has the
environment, the auxiliary resource and the input resource has parameters.
3.7. USE CASE: APPLY XSLT TRANSFORMS TO XML FILES 37
In the processFile method we then first check whether the input resource is an XML
file. If this is the case, we call the method applyXSLT on the environment to transform
the XML file with the XSLT resource. If this call is succesful, it will return an object of
class Resource. This resource will then be moved to the ”success” folder. In all other
cases, we print out an error log.
As an example, we used the following XSLT Script (Listing 3.6) and applied it on the
following XML file (Listing 3.7). This produced the HTML file shown in Listing 3.8.
1
2 <?xml version="1.0" encoding="ISO-8859-1"?>3 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">4 <xsl:template match="/">5 <html>6 <body>7 <h1>Indian Languages details</h1>8 <table border="1">9 <tr>
10 <th>Language</th>11 <th>Family/Origin</th>12 <th>No. of speakers</th>13 <th>Region</th>14 </tr>15 <xsl:for-each select="languages-list/language">16 <tr>17 <td><xsl:value-of select="name"/></td>18 <td><xsl:value-of select="family"/></td>19 <td><xsl:value-of select="users"/></td>20 <td><xsl:value-of select="region"/></td>21 </tr>22 </xsl:for-each>23 </table>24 </body>25 </html>26 </xsl:template>27 </xsl:stylesheet>
Listing 3.6: XSLT Script
1 <?xml version="1.0" encoding="utf-8" ?>2 <languages-list>3 <language id = "1">4 <name>Kannada</name>5 <region>Karnataka</region>6 <users>38M</users>7 <family>Dravidian</family>8 </language>9 <language id = "2">
10 <name>Telugu</name>
38 CHAPTER 3. CREATING/UPDATING RESOURCES
11 <region>Andra Pradesh</region>12 <users>74M</users>13 <family>Dravidian</family>14 </language>15 <language id = "3">16 <name>Tamil</name>17 <region>TamilNadu</region>18 <users>61M</users>19 <family>Dravidian</family>20 </language>21 <language id = "4">22 <name>Malayalam</name>23 <region>Kerela</region>24 <users>33M</users>25 <family>Dravidian</family>26 </language>27 <language id = "5">28 <name>Hindi</name>29 <region>Andaman and Nicobar Islands, North india, Parts of North
east</region>30 <users>442M</users>31 <family>Indo Aryan</family>32 </language>33 <language id = "6">34 <name>Assamese</name>35 <region>Assam, Arunachal Pradesh</region>36 <users>13M</users>37 <family>Indo Aryan</family>38 </language>39 </languages-list>
Listing 3.7: XML Input for XSLT Transform
1 <?xml version="1.0" encoding="UTF-8" ?>2 <html>3 <body>4 <h1>Indian Languages details</h1>5 <table border="1">6 <tr>7 <th>Language</th>8 <th>Family/Origin</th>9 <th>No. of speakers</th>
10 <th>Region</th>11 </tr>12 <tr>13 <td>Kannada</td>14 <td>Dravidian</td>15 <td>38M</td>16 <td>Karnataka</td>17 </tr>18 <tr>19 <td>Telugu</td>20 <td>Dravidian</td>21 <td>74M</td>
3.8. USE CASE: GENERATE PDF REPORTS 39
22 <td>Andra Pradesh</td>23 </tr>24 <tr>25 <td>Tamil</td>26 <td>Dravidian</td>27 <td>61M</td>28 <td>TamilNadu</td>29 </tr>30 <tr>31 <td>Malayalam</td>32 <td>Dravidian</td>33 <td>33M</td>34 <td>Kerela</td>35 </tr>36 <tr>37 <td>Hindi</td>38 <td>Indo Aryan</td>39 <td>442M</td>40 <td>Andaman and Nicobar Islands, North india, Parts of North
east</td>41 </tr>42 <tr>43 <td>Assamese</td>44 <td>Indo Aryan</td>45 <td>13M</td>46 <td>Assam, Arunachal Pradesh</td>47 </tr>48 </table>49 </body>50 </html>
Listing 3.8: Output HTML of XSLT Transform
Figure 3.1 shows how this HTML is displayed in a browser.
3.8 Use Case: Generate PDF Reports
PrintSphere AutoPilot also supports PDF reporting functionality. This can be handy if
you want to generate a PDF representation from a list of data records that are defined
by means of a number of text fields. Consider, for instance, a list of travellers with their
name, phone number and destination. We now show how we can make special luggage
tags.
In order to proceed, we first need to create a PDF form template. This best can be done by
first creating a design in your favourite page editing software (like Microsoft Word, Adobe
InDesign etc.) and then generate a PDF from this software. You then have to define a
number of form fields on this template in Adobe Acrobat Professional (see Figure 3.2).
40 CHAPTER 3. CREATING/UPDATING RESOURCES
Figure 3.1: HTML visualization of Listing 3.8
In this example, we will embed form fields with the names ”name”, ”address” and ”phone”.
A you will see, we will print for each record 2 tags on one page (one in green and one in
pink).
We now need an XML file that contains the data records for which we will generate the
tags. In Listing 3.9, we give a sample XML file with 4 records.
1 <?xml version="1.0" encoding="UTF-8"?>2 <LuggageTags>3 <Item>4 <Name>Roger Rabbit</Name>5 <Address>Hotel Bellevue6 Grand Rue 277 B-4960 Ligneuville</Address>8 <Phone>+32 3 444 44 44</Phone>9 </Item>
10 <Item>11 <Name>Tazmanian Devil</Name>12 <Address>Hotel Bellevue13 Grand Rue 2714 B-4960 Ligneuville</Address>15 <Phone>+32 3 444 44 45</Phone>16 </Item>17 <Item>18 <Name>Daffy Duck</Name>19 <Address>Hotel Bellevue20 Grand Rue 2721 B-4960 Ligneuville</Address>22 <Phone>+32 3 444 44 46</Phone>23 </Item>
3.8. USE CASE: GENERATE PDF REPORTS 41
Figure 3.2: Making an Adobe Form Template PDF
24 <Item>25 <Name>Willy Wombat</Name>26 <Address>Hotel Bellevue27 Grand Rue 2728 B-4960 Ligneuville</Address>29 <Phone>+32 3 444 44 47</Phone>30 </Item>31 </LuggageTags>
Listing 3.9: XML Input for generating PDF Reports
We now are ready to develop the script (see Listing 3.10) for generating the PDF output.
In line 45, you will find the main function. There, we will iterate as usual over the
incoming files. In our case, these are XML files like the one shown in Figure 3.9. For
each such file, we call a function process (line 52) which is defined in line 38. In this
function, we read in the XML file and convert it into a list of JSON dictionaries. This list
is passed to the function createReport on line 42. This function will create and return
the requested PDF report.
42 CHAPTER 3. CREATING/UPDATING RESOURCES
In the function createReport, we first load the PDF template. This template is available
as an auxilary resource. Then we call the function fillFormPdf on line 32 which does
the actual work. This function expects 3 parameters:
1. filename: the name of the PDF file to be generated
2. templateName: the filename of the PDF template
3. formData: a list of JSON dictionaries which contain the form fields for each of the
records. The name of the dictionary entries must match with the names of the form
fields in the PDF template
The resulting PDF report is passed on to the main function in line 52. If the PDF file
has been generated successfully, it is moved to the proper location (line 55) and the job
file is moved to a location which contains the processed job files. In case there is no PDF
file generated, the job file is moved to a location that gathers the job files which could
not be processed successfully.
1 // Parsing of XML file2 function eltValue(node, eltname) {3 var elt = node.getElement(eltname);4 if (elt == null)5 return null;6 else7 return elt.getValue();8 }9
10 function retrieveXml(resource) {11 var data = [];12 if (resource.isXml()) {13 var xml = resource.asXml();14 var list = xml.xpath("root()/*:LuggageTags/*:Item");15 var i;16 for (i = 0; i < list.length; i++) {17 var jsonnode = {};18 var node = list.get(i);19 jsonnode.name = eltValue(node, "Name");20 jsonnode.address = eltValue(node, "Address");21 jsonnode.phone = eltValue(node, "Phone");22 data.push(jsonnode);23 }24 return data;25 }26 return null;27 }28
29 function createReport(env, xml, records) {30 var template = env.getAuxiliaryResource("template");
3.9. USE CASE: GENERATE NESTED PDF REPORTS 43
31
32 var pdf = env.fillFormPdf(xml.getBaseFileName() + ".pdf",33 template.getPathFileName(), records);34 return pdf;35 }36
37 // Main functions38 function process(env, xml) {39 var records = retrieveXml(xml);40 if (records == null)41 return null;42 return createReport(env, xml, records);43 }44
45 function main(env) {46 var resources = env.getInputResources();47
48 var i;49 for (i = 0; i < resources.length; i++) {50 var jobfile = resources[i];51 print("Jobfile: " + jobfile.getFileName() + ": started processing");52 var report = process(env, jobfile);53 if (report != null) {54 jobfile.move(env.getOutputLocation("processedLocation"));55 report.move(env.getOutputLocation("reportLocation"));56 print("Jobfile: " + jobfile.getFileName() + ": processed");57 } else {58 jobfile.move(env.getOutputLocation("failure"));59 print("Jobfile: " + jobfile.getFileName() + ": failed");60 }61 }62 }
Listing 3.10: Script for generating PDF Reports
The result of the above Script is a PDF file containing 4 pages, each for every record. In
Figure 3.3, we show a cropped view of the fourth page containing the results of record 4
(the tags for Willy Wombat).
3.9 Use Case: Generate Nested PDF Reports
The above functionality can be used for record based form generation. In that case, one
or more pages will be generated for each and every record. Often, however, one might
wants to output the formatted records in another PDF template. An obvious example of
this functionality might be the generation of an invoice. In this case, we typically have a
template that describes the looks of the invoice (one might even want to define a different
template for the first page and the subsequent pages) and one or more templates that
define the look of the different records (in the case of the invoice, the different invoice
44 CHAPTER 3. CREATING/UPDATING RESOURCES
Figure 3.3: A sample (cropped) view of the result (record 4)
lines).
In order to implement this functionality, we have to call the method fillNestedFormPdf
of the Script environment with the following parameters:
1. filename: the name of the PDF file to be generated
2. headerTemplateNameFirst: the filename of the PDF template for the first page
3. headerTemplateNameNext: the filename of the PDF template to be used for the
subsequent pages
4. headerData: a JSON dictionary that contains the form fields to be filled in the
headerTemplates
5. formTemplateName: a PDF file that contains a number of pages that each contain
a form template for a specific record type
6. formData: a list of JSON dictionaries which contain the form fields for each of the
records. A field with the name ”template” must be defined in each record that will
contain the page number of the form template to be used (the page number must
be passed as a string and the first page in the formTemplate file corresponds with
”1”). The names of the other dictionary entries must match with the names of the
form fields defined on the associated page in the form template file.
In order to define where the records will be put, the header template files must each
contain a form field with the name ”box”. Within these boxes, the records will be added
3.10. USE CASE: FORMS WITH VARIABLE IMAGES AND BARCODES 45
one after the other. The records will be fill the boxes top/down and when the next record
cannot be fitted in the current box anymore, a new page will be added to the final PDF
file.
In the header templates, 2 additional built-in form fields can be used:
1. page: the page number of the current page
2. nrpages: which contains the total number of pages that will be generated
3.10 Use Case: Forms with Variable Images and Bar-
codes
3.10.1 Images
In section 3.8, we explained how record data can be visualized in PDF files using PDF
form templates. Until now, the data that could be rendered were all text-based; we now
show how we can also use images or PDF content in records.
The API is quite straightforward. Whereas, for text attributes, the value of the record
was a string, for images, we have to specify a JSON dictionary with the following fields:
1. resource: a pointer to a Resource object
2. type: ’IMAGE’ or ’PDF’
3. pagenr: in case the type attritube is equal to ’PDF’; please note that the first page
is numbered 1
In the PDF form templates, one must define a form field with the name of the image
attribute. The content will be scaled proportionally to fit within the form field. When
the aspect ratio is not exactly the one of the containing form field, one can specify how
one wants to position the image/PDF within the following options:
1. one of LEFT, MIDH or RIGHT: to specify the horizontal positioning. The default value
is MIDH.
2. one of TOP, MIDV or BOTTOM: to specify the vertical positioning. The default value
is MIDV.
46 CHAPTER 3. CREATING/UPDATING RESOURCES
In case the content resource is a page of a PDF file, one can also specify what area of the
PDF file will be shown by specifying one of the PDF boxes (the region outside the box
will be clipped):
1. CROP (default value)
2. MEDIA
3. BLEED
4. TRIM
The options are part of the name field of the form field and or specified after the base
name (separated with a colon). For a PDF-based form field with name ’animal’ of which
only the trim box should be visualized in the center (both horizontally and vertically),
the name would be: ’animal:MIDH:MIDV:TRIM’.
Images and PDF content can be stacked on top of each other. In order to know the order
in which images should be drawn, we introduced the level concept. The level must be an
integer (and can be negative). The image form fields with the lowest level, will be drawn
first. The level is specified as an integer. In the above animal example, is one would like to
assign level 5 to the form field, the name would become: ’animal:MIDH:MIDV:TRIM:5’.
Note that the order of the options is arbitrary. Also note that the images fields will always
be put on top of the original content and the text fields.
3.10.2 Barcodes
For barcodes, the JSON dictionary looks as follows:
1. type: ’BARCODE’
2. subtype: one of ’QRCODE’, ’EAN-8’, ’EAN-13’, ’UPC-A’, ’UPC-E’
3. text: the content to be encoded in the bar code. The required text can depend on
the subtype. For ’EAN-8’ the text should consist out of 8 digits; for ’EAN-13’, it
should consist out of 13 digits. For ’UPC-A’ the text should consist out of 12 digits;
for ’UPC-E’, it should consist out of 6 digits.
The QR codes will be default by in black and produce a white background. The traditional
barcodes (line patterns) will be printed in black and have a transparent background. It
3.10. USE CASE: FORMS WITH VARIABLE IMAGES AND BARCODES 47
is possible to alter this behavior by specifying custom colors. These have to be passed as
a string in hexadecimal format, e.g., ’#ff00ff’ (magenta).
For QR codes you can specify:
1. color: color for the squares in the QR code
2. bgcolor: color for the background
For the traditional barcodes you can specify:
1. color: color for the lines
2. textcolor: color of the encoded content (text)
Depending on the subtype of the bar code, additional
elds are supported:
1. subtype: ’QRCODE’
(a) charset: one of the problems with QR codes is that the encoding of the text is
not specified by the standard. This parameter allows to specify the encoding.
It should be one of ’Shift JIS’, ’Cp437’, ’ISO-8859-1’, ’ISO-8859-16’. When not
specified, the default is ’ISO-8859-1’
2. subtype: ’EAN-8’, ’EAN-13’, ’UPC-A’, ’UPC-E’
(a) suppltype: one of ’SUPP2’, ’SUPP5’. When de
ned, this will add a supplementary small barcode next to the main barcode.
(b) suppltext: the supplementary barcode text. For ’SUPP2’, this text should
consist out of 2 digits; for ’SUPP5’, it should consist out of 5 digits.
3.10.3 Example
Listing 3.11 shows how the above luggage tag generation script (Listing 3.10) can be
extended to support variable images.
48 CHAPTER 3. CREATING/UPDATING RESOURCES
1 // Parsing of XML file2 function eltValue(node, eltname) {3 var elt = node.getElement(eltname);4 if (elt == null)5 return null;6 else7 return elt.getValue();8 }9
10 function eltImageValue(env, node, eltname) {11 var imagename = eltValue(node, eltname);12 if (imagename == null)13 return null;14 var filename = "./looneytunes/" + imagename;15 var location = env.getOutputLocation("ImageLocation");16 var folder = location.asFolder();17 var resource = folder.getResource(filename);18 if (resource.exists() && resource.isImage()) {19 var result = {};20 result.resource = resource;21 result.type = "IMAGE";22 return result;23 } else24 return null;25 }26
27 function retrieveXml(env, resource) {28 var data = [];29 if (resource.isXml()) {30 var xml = resource.asXml();31 var list = xml.xpath("root()/*:LuggageTags/*:Item");32 var i;33 for (i = 0; i < list.length; i++) {34 var jsonnode = {};35 var node = list.get(i);36 jsonnode.name = eltValue(node, "Name");37 jsonnode.address = eltValue(node, "Address");38 jsonnode.phone = eltValue(node, "Phone");39 jsonnode.image = eltImageValue(env, node, "Image");40 data.push(jsonnode);41 }42 return data;43 }44 return null;45 }46
47 function createReport(env, xml, records) {48 var template = env.getAuxiliaryResource("template");49
50 var pdf = env.fillFormPdf(xml.getBaseFileName() + ".pdf",51 template.getPathFileName(), records);52 return pdf;53 }54
55 // Main functions
3.10. USE CASE: FORMS WITH VARIABLE IMAGES AND BARCODES 49
56 function process(env, xml) {57 var records = retrieveXml(env, xml);58 if (records == null)59 return null;60 return createReport(env, xml, records);61 }62
63 function main(env) {64 var resources = env.getInputResources();65
66 var i;67 for (i = 0; i < resources.length; i++) {68 var jobfile = resources[i];69 print("Jobfile: " + jobfile.getFileName() + ": started processing");70 var report = process(env, jobfile);71 if (report != null) {72 jobfile.move(env.getOutputLocation("processedLocation"));73 report.move(env.getOutputLocation("reportLocation"));74 print("Jobfile: " + jobfile.getFileName() + ": processed");75 } else {76 jobfile.move(env.getOutputLocation("failure"));77 print("Jobfile: " + jobfile.getFileName() + ": failed");78 }79 }80 }
Listing 3.11: Script for generating PDF Reports with variable images
Figure 3.4 shows a PDF template of the luggage tag example above whereby 2 form fields
have been added to contain the images (the form field name is ”image”). In the source
XML file (see Listing 3.12), the image to be used is specified in the ”Image” element.
As can be seen in the Listing 3.11 on line 17, the associated image resource is put in a
specific PrintSphere folder.
In Figure 3.5, a sample record is being shown. In this case, the image was a BMP file. If
one would use PNG, it would also be possible to use transparent content.
1 <?xml version="1.0" encoding="UTF-8"?>2 <LuggageTags>3 <Item>4 <Name>Roger Rabbit</Name>5 <Address>Hotel Bellevue6 Grand Rue 277 B-4960 Ligneuville</Address>8 <Phone>+32 3 444 44 44</Phone>9 <Image>looneytunes.bmp</Image>
10 </Item>11 <Item>12 <Name>Tazmanian Devil</Name>13 <Address>Hotel Bellevue14 Grand Rue 27
50 CHAPTER 3. CREATING/UPDATING RESOURCES
15 B-4960 Ligneuville</Address>16 <Phone>+32 3 444 44 45</Phone>17 <Image>tazzy.bmp</Image>18 </Item>19 <Item>20 <Name>Sylvester</Name>21 <Address>Hotel Bellevue22 Grand Rue 2723 B-4960 Ligneuville</Address>24 <Phone>+32 3 444 44 46</Phone>25 <Image>sylvester.bmp</Image>26 </Item>27 <Item>28 <Name>Willy Wombat</Name>29 <Address>Hotel Bellevue30 Grand Rue 2731 B-4960 Ligneuville</Address>32 <Phone>+32 3 444 44 47</Phone>33 <Image>willywombat.jpg</Image>34 </Item>35 <Item>36 <Name>Tweety</Name>37 <Address>Hotel Bellevue38 Grand Rue 2739 B-4960 Ligneuville</Address>40 <Phone>+32 3 444 44 47</Phone>41 <Image>tweety.bmp</Image>42 </Item>43 </LuggageTags>
Listing 3.12: XML Input for generating PDF Reports with images
Figure 3.4: Making an Adobe Form Template PDF with image form fields
3.10. USE CASE: FORMS WITH VARIABLE IMAGES AND BARCODES 51
Figure 3.5: A sample (cropped) view of the result with images
Chapter 4
Apogee StoreFront Use Cases
4.1 Use Case: Reroute StoreFront Prepress Packages
Apogee StoreFront is an online service that enables printing organizations to setup their
own Web2Print shops. Orders that are placed with these shops typically consist of items
to be printed (and possibly other non-print items). For each order item, the system will
generate a prepress job that will be processed by the prepress system of the printing
organization.
When connected to the Apogee Prepress system, Apogee StoreFront will typically generate
so-called MJD file which, essentially, is a MIME package consisting of a JDF file and a
PDF file. In case of non-print items, the system will generate plain JDF files.
By default, these job orders will be downloaded automatically from the Apogee StoreFront
Service by the Apogee Prepress System. It is, however, also possible to export the job
orders to a PrintSphere volume. This opens the possibility to send the job orders to other
prepress systems, also forward the job orders to MIS systems or manipulate/change the
job orders before they are forwarded to either prepress or MIS systems.
In the Script we are now going to develop, we will analyze the generate job orders and to
treat them differently based on the type of job order. In order to do this, we will have to
unpack the MJD files and analyze the associated JDF job tickets.
1 function findResource(list, filetype) {2 var i;
53
54 CHAPTER 4. APOGEE STOREFRONT USE CASES
3 for (i = 0; i < list.length; i++) {4 var type = list[i].getFileType();5 if (type == filetype)6 return list[i];7 }8 return null;9 }
10
11 function findgenid(list, key) {12 var i;13 for (i = 0; i < list.length; i++) {14 if (list[i].getAttributeValue("IDUsage") == key) {15 return list[i].getAttributeValue("IDValue");16 }17 }18 return null;19 }20
21 function mailGadgetSupplier(env, store, jdfresource) {22 print("Mailing the gadget supplier " + vars.gadgetSupplierEMail);23
24 var mailService = env.getMailService();25 var message = mailService.newMessage();26
27 message.setFrom(vars.printerEMail);28 message.setTo([vars.gadgetSupplierEMail]);29 // message.setCc([ "[email protected]" ]);30 // message.setBcc([ "[email protected]" ]);31
32 message.setSubject("Order from " + vars.printerName + " via " + store);33
34 message.setBody("Dear supplier,\n\nPlease carry out the accompanying order.The details can be found in the attachment.\n\nWith kind regards,\n\n" +vars.printerName + "\n");
35
36 message.addAttachment(jdfresource, jdfresource.getFileName());37
38 if (!mailService.send(message))39 print("Mail sending failed");40 }41
42 function informPrinterAdministrator(env, store, jdfresource) {43 // Via IFTTT44
45 print("Informing Printer Administrator " + jdfresource.getFileName());46 var url = vars.iftttURLPrinterAdministrator;47 var value1 = store;48 var value2 = "A non-print product has been ordered on " + value1 + ".<br>A
mail has been sent to your gadget supplier to fulfill the order.";49 var value3 = jdfresource.getShareURL(); // + "/download?nodisposition=1";50
51 var httpSession = env.newHttpSession();52
53 var httpCommand = httpSession.newCommand("GET", url, null);54 httpCommand.setAttrNameValue("value1", value1);55 httpCommand.setAttrNameValue("value2", value2);
4.1. USE CASE: REROUTE STOREFRONT PREPRESS PACKAGES 55
56 httpCommand.setAttrNameValue("value3", value3);57
58 var httpResult = httpSession.executeCommand(httpCommand);59 print("Result: " + httpResult.getResult());60 }61
62 function analyzeJDF(env, jdfresource, pdfresource) {63 var xmldoc = jdfresource.asXml();64
65 var jdfnodes = xmldoc.getElementsByTagName("JDF");66 if ((jdfnodes == null) || (jdfnodes.length < 1))67 return false;68 var rootnode = jdfnodes[0];69 var idlist = rootnode.getElementsByTagName("GeneralID");70
71 var type = findgenid(idlist, "StoreFrontOrderItemType");72 var itemid = findgenid(idlist, "StoreFrontOrderIDItem");73 var agfatemplate = rootnode.getAttributeValue("agfa:TicketTemplateName");74 var store = findgenid(idlist, "StoreFrontStoreName");75
76 if (type == "NON_PRINTED") {77 print("==> Non Printed Product");78 var jdfcopy = jdfresource.copy(env.getOutputLocation("gadgetLocation"));79 mailGadgetSupplier(env, store, jdfresource);80 informPrinterAdministrator(env, store, jdfcopy);81 } else {82 print("==> " + type);83 if (agfatemplate == vars.posterTicketTemplateName) {84 // Send the individual files to the poster workflow system85
86 jdfresource.move(env.getOutputLocation("posterLocation"));87 if (pdfresource != null)88 pdfresource.move(env.getOutputLocation("posterLocation"));89 } else {90 // Send the MJD to Prepress91
92 var newlist = [jdfresource];93 if (pdfresource != null)94 newlist.push(pdfresource);95 var mjdresource = env.newMJDFile(newlist, itemid + ".mjd");96 mjdresource.move(env.getOutputLocation("prepressLocation"));97 }98 }99 }
100
101 function processFile(env, resource) {102 var jdfresource = null;103 var pdfresource = null;104
105 var fileType = resource.getFileType();106
107 if (fileType == ’XML’)108 jdfresource = resource.copy(resource.getFileName());109 else if (fileType === ’MJD’) {110 var list = env.extractMJDFile(resource);111 if (list != null) {
56 CHAPTER 4. APOGEE STOREFRONT USE CASES
112 jdfresource = findResource(list, ’XML’);113 pdfresource = findResource(list, ’PDF’);114 }115 } else if (fileType == ’ZIP’) {116 var list = env.extractZipFile(resource);117 var mjdresource = null;118 if (list != null) {119 jdfresource = findResource(list, ’XML’);120 pdfresource = findResource(list, ’PDF’);121 mjdresource = findResource(list, ’MJD’);122 }123 if (mjdresource != null) {124 var list2 = env.extractMJDFile(mjdresource);125 jdfresource = findResource(list2, ’XML’);126 pdfresource = findResource(list2, ’PDF’);127 }128 } else {129 print(resource.getFileName() + ": not a prepress job file");130 }131
132 if (jdfresource != null)133 analyzeJDF(env, jdfresource, pdfresource);134
135 resource.release();136 }137
138 function main(env) {139 var res = env.getInputResources();140
141 for (i = 0; i < res.length; i++) {142 processFile(env, res[i]);143 }144
145 return 0;146 }147
148 var vars = {149 "posterTicketTemplateName": "PostersAndSigns",150 "gadgetSupplierEMail": "[email protected]",151 "printerEMail": "[email protected]",152 "printerName": "Flandria Printing",153 "iftttURLPrinterAdministrator":
"https://maker.ifttt.com/trigger/printsphere_trigger/with/key/..."154 };
Listing 4.1: Rerouting StoreFront Prepress Jobs
4.1.1 Detailed Requirements
When preprocessing the Prepress jobs that have been generated by Apogee StoreFront,
the following routing must be realized:
4.1. USE CASE: REROUTE STOREFRONT PREPRESS PACKAGES 57
1. For the non-print order items, a mail must be sent to the person that will carry
out the fulfillment (this person is referred to in the Script as the GadgetSuppier).
In this case, the Printer Administrator must also be informed via an App of the
IFTTT Service. This App can be triggered by a specific URL.
2. In case the order item is a poster (we assume that these products have a dedi-
cated Prepress TicketTemplate name), the PDF file should be sent to a specific
PrintSphere folder (that will be mapped to another workflow system).
3. All the other jobs will be processed by Apogee Press. The associated job file (MJD)
should, in this case, be moved to a specific PrintSphere folder (that will be mapped
to an input folder of the Prepress system).
4.1.2 Structure of the Script
In lines 148-154, the vars variable has been declared. This variable is a JSON object with
several fields that are treated as Script parameters. In SphereCenter, these fields can be
specified by a non-technical user in the GUI for activating a Script.
The main processing loop can be found in lines 138-146. There, a function processFile
is called that does the main work. The goal of this function is to identify both the JDF
and PDF files (if applicable). In case the input file is an MJD file, the JDF and PDF
files will be extracted from the MJD archive. The same is done in case the input file is
a ZIP file. In case the MJD file itself is zipped, we first retrieve the MJD file from the
ZIP file and then extract the JDF/PDF files from the MJD file. In case of a plain JDF
submission (which typically happens in case of a non-print order item), the JDF file is
put aside.
Then the function analyzeJDF is called in which the further processing will be done. In
line 63, the JDF file is converted to an XmlDoc and a number of parameters are retrieved
from the JDF file that will allow the identification of the job. A number of StoreFront-
specific parameters are stored in the JDF in the so-called GeneralID elements with the
IDUsage/IDValue attributes; in line 11, a convenience function findgenid has been im-
plemented that retrieves these parameters. The TicketTemplate name, on the other hand,
is retrieved on line 73 (and stored in the proprietary agfa:TicketTemplateName attribute
on JDF root level.
If the StoreFront item type is ”NON PRINTED” (line 76), we copy the JDF file to the
PrintSphere location with label ”gadgetLocation”. As pointed out before, we also mail
58 CHAPTER 4. APOGEE STOREFRONT USE CASES
the GadgetSupplier and inform the Printer Administrator using the mailGadgetSupplier
and informPrinterAdministrator functions.
If the ticket template name has the value ”PostersAndSigns” (this value is stored in the
vars variable as vars.posterTicketTemplateName), the PDF resource is moved to the
PrintSphere location with label ”posterLocation”.
In all other cases, an MJD file is created with the JDF and PDF files.
4.1.3 Sending a Mail
In line 21, we find the mailGadgetSupplier function. In order to send an e-mail with
PrintSphere AutoPilot, we first have to retrieve the default mailService (an instance of
the class MailService). We then (line 25) can create a new message using the newMessage
method and set the From, To, Subject and Body of the message using the appropriate
functions (see lines 27-34). On line 36, we then attach the JDF file (available as a resource)
as an attachment of the mail; the addAttachment method also expects a second parameter
that contains the name of the attachment (as it will be shown in the mail client).
Sending the message, at the end, can be done with the method send (line 38).
4.1.4 Connecting to an HTTP Service
In this use case, we have to inform the Printer Administrator by triggering an App of the
IFTTT Service (we use the IFTTT App called WebHooks). Trigger the App can be done
by executing a http GET call on a specific URL with several parameters called value1,
value2 and value3. The semantics of these parameters depends on the actual IFTTT
Script. In our example, they contain the StoreFront Store name, the body content of the
message and the Share URL of the JDF resource. Note that producing an anonymous
share URL on a PrintSphere resource is done with the method getShareURL (see line 49).
In order to issue an HTTP call, we first have to create an HTTP session (using the
newHttpSession method on the Environment, see line 51). Then, the specific HTTP
command can be created with the newCommand method. This method has 3 parameters:
the type of HTTP call (i.e., GET or POST), the URL and the name of the resulting
Resource. If the last parameter is null, the result will be available in memory as a String
with the getResult method. The URL parameters can be set with the setAttrNameValue
method (lines 54-56). The method executeCommand (method of the HttpSession object)
will then execute the HTTP command. The result can be, as already said, retrieved with
4.2. USE CASE: MANIPULATE STOREFRONT PREPRESS PACKAGES 59
the getResult method.
When the IFTTT App has been triggered, it will be executed with the passed parameters.
In our case, we connected the WebHooks App to the ”Send E-mail” IFTTT App to send
an e-mail based on the passed parameters. Note that it would also be possible to connect
with any other IFTTT App that would notify the Printer Administrator.
4.2 Use Case: Manipulate StoreFront Prepress Pack-
ages
When one has different workflow systems connected to the Apogee StoreFront account,
het system to be used will often be determined by the type of the order item. This
functionality has already been shown in the previous use case (see Section 4.1).
In case we would have to interface with a non-JDF compliant workflow, we might be
forced to create a non-standard job ticket using the information in the original JDF file.
The functionality to create XML, JSON or text files in PrintSphere AutoPilot could be
used to realize this.
Another typical use case might consist of interfacing with a third party MIS system. In
this case, the PrintSphere AutoPilot Script would analyze the incoming jobs and inform
the MIS (via, e.g., HTTP) on the created jobs. Afterwards, the PrintSphere AutoPilot
Script will then typically copy the incoming job tickets to the Prepress input folder.
TO DO: implement a sample Script to create a dedicated job ticket in XML
Chapter 5
Apogee Prepress Use Cases
5.1 Convert APOXML files to JDF
If one wants to generate orders for an Apogee Prepress system, it is best to create a JDF
file with the job definition. The JDF job ticket format is a standard introduced by CIP4
and is very flexible and powerful. Generating and maintaining JDF files can, however, be
quite involving. Therefore, Agfa has introduced the APOXML format. This is a simple
XML-based format that allows the definition of relatively simple prepress jobs and can
be converted into a JDF file. A sample APOXML file is shown in Listing 5.1.
1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>2 <ns0:ApoXML xmlns:ns0="http://www.agfa.com/apoxml"
OrderNumber="ApoXML-BusinessCard" JobName="BusinessCard" Comments="ApoXMLfor single Business card. v3" Amount="5000" ProductType="Flatwork"AgentName="ApoXML SDK" AgentVersion="1.0" Unit="mm" DecimalSeparator=",">
3 <CustomerContact>4 <Company Company="Agfa Graphics NV" Phone="Cust_Agfa_Pers_Fix"
Street="Septestraat 27" ZIP="B-2640" StateProvince="Vlaanderen"City="Mortsel" Country="Belgie" JDFProductID="PI_CompAgfa"/>
5 <Person Prefix="Dhr." Title="Tech. Manager" FirstName="John"LastName="Doe" FixPhone="Cust_Agfa_Pers_Fix"MobilePhone="Cust_Agfa_Pers_Mobile" Email="[email protected]"JDFProductID="PI_Cust1234"/>
6 </CustomerContact>7 <Binding Method="Unbound"/>8 <Part PartType="Plain" PartName="BusinessCards" Press="SM74"
WorkStyle="Duplex" Comments="business cards">9 <!-- adapt the URL to the content location on your server or remove it
-->
61
62 CHAPTER 5. APOGEE PREPRESS USE CASES
10 <Pages URL="file://be.local/.../Content/BusinessCardCMYK_DS.pdf"PageCount="2" PageWidth="85" PageHeight="55"/>
11 <Color NrColors="4"/>12 <PaperStock StockName="businesscardbrand" Weight="200" Grade="2"
Thickness="0,2" SheetWidth="707" SheetHeight="500"/>13 </Part>14 </ns0:ApoXML>
Listing 5.1: Sample APOXML file
PrintSphere AutoPilot has built-in support to convert APOXML files to the JDF for-
mat. In Listing 5.2, we show how this can be done. To this end, we use the method
apoXML2JDF on the Environment object (see line 5) that converts an XML resource into
a JDF Resource file. The first parameter to this method is the filename of the JDF
Resource that will be created and the second parameter is the XML Resource (Resource
object and not the XmlDoc). The method returns the generated Resource.
1 function processFile(env, resource) {2 print("Processing " + resource.getFileName());3 var xml = resource.asXml();4 if (xml) {5 var jdf = env.apoXML2JDF(resource.getBaseFileName() + ".jdf", resource);6
7 if (jdf != null) {8 jdf.move(env.getOutputLocation("success"));9 resource.release();
10 } else11 resource.move(env.getOutputLocation("failure"));12 } else13 resource.move(env.getOutputLocation("failure"));14 }15
16 function main(env) {17 var res = env.getInputResources();18
19 for (i = 0; i < res.length; i++) {20 processFile(env, res[i]);21 }22
23 return 0;24 }
Listing 5.2: Convert APOXML files to JDF
5.2. SUBMIT JOBS TO PREPRESS IN THE CLOUD 63
5.2 Submit Jobs to Prepress in the Cloud
5.2.1 General
We now write a Script that shows how a submitted PS/PDF file can be normalized in
Apogee Prepress and how, in addition, specific preflight actions can be executed. We
therefore use built-in PrintSphere AutoPilot functions that assume that a Prepress sys-
tems is available in the PrintSphere AutoPilot Cloud. On this Apogee Prepress system,
the same resources have been installed as those used in Apogee Bridge.
The required processing can be selected by choosing the right Ticket Template. In our
Script, this must be specified in vars.TicketTemplateName. Depending on the selected
Ticket Template, different processing will take place and different resources will be gen-
erated.
Apogee Bridge currently supports 3 different Ticket Templates:
1. ApogeeBridge Normalizer
2. ApogeeBridge Preflight
3. ApogeeBridge Render
4. In the future, Ticket Templates to access the InkSave and ContraVision functionality
will be added as well.
In Listing 5.3, you will find the Script that implements this functionality.
1 function initJobId(env) {2 var data = env.getScriptData();3 if (data == null) {4 print("data not initialized");5 data = new Map();6 env.setScriptData(data);7 }8 if (!data.containsKey("jobIdCounter"))9 data.jobIdCounter = vars.jobIdCounter;
10 }11
12 function newJobId(env) {13 var data = env.getScriptData();14 var newId = vars.jobIdPrefix + data.jobIdCounter;
64 CHAPTER 5. APOGEE PREPRESS USE CASES
15 data.jobIdCounter++;16 return newId;17 }18
19 function submitjob(env, resource, jobid, vars) {20 var eqid = env.submitPrepressJob(jobid, resource, vars);21 print("EntityQueueID: " + eqid + ", JobID: " + jobid);22 }23
24 function retrieveresources(env) {25 var outputresources = env.prepressCallback(vars, "resource");26 if (outputresources != null) {27 var i;28 for (i = 0; i < outputresources.length; i++) {29 var id = outputresources[i].getAttribute("id");30 var type = outputresources[i].getAttribute("type");31 var jobid = outputresources[i].getAttribute("jobid");32 outputresources[i].rename(jobid + "-" + type + "-" + id + "." +
outputresources[i].getExtension());33 outputresources[i].move(env.getOutputLocation("success"));34 }35 }36 }37
38 function main(env) {39 var notificationType = env.getNotificationType();40 print("Notification: " + notificationType);41 print("Notification URL: " + env.getNotificationUrl());42
43 initJobId(env);44
45 if (notificationType == "PrintSphereFile") {46 // Incoming files to be processed by Apogee Prepress47 var inputresources = env.getInputResources();48 var i;49 for (i = 0; i < inputresources.length; i++) {50 submitjob(env, inputresources[i], newJobId(env), vars);51 inputresources[i].release();52 }53
54 } else if (notificationType == "httpPost") {55 // Replies from Apogee Prepress with resource URL’s...56 retrieveresources(env);57 }58 }59
60 var vars = {61 "jobIdPrefix": "JobID",62 "jobIdCounter": "3010",63 "TicketTemplateName": "ApogeeBridge_Normalizer",64 "ApogeeURL": "http://vmeqap-t0d.be.local/AXJMF"65 // , "NamedFeaturesTag": ""66 };
Listing 5.3: Submit a Job to Apogee Prepress
5.2. SUBMIT JOBS TO PREPRESS IN THE CLOUD 65
5.2.2 Setup of the Script and Main Function
As already indicated before, the vars variable contains a number of parameters that will
determine the behavior of the Script. The vars variable is a read-only JSON structure
that remains unchanged in successive runs of the Script. Each time an incoming PS/PDF
file arrives, we will create a new Prepress jobs with a unique JobID. Since we want to
base this JobID on a sequence number, we need to keep track of the calls we have already
executed. Therefore, we need a data context that can be updated and the updates of
which will be available during successive calls of the Script.
For each activated Script, PrintSphere AutoPilot has created such a data context. This
data context (which is a JSON structure) can be accessed via the getScriptData method
on the Environment object.
In the function initJobId, we will retrieve the data context and check whether the
sequence counter of the next Prepress job has already been set. If this is not the case,
this means the Script is running for the first time and we initialize the data.jobIdCounter
tot the initial value specified in vars.jobIdCounter.
Contrary to the other scripts we have described so far, we will also check in the main func-
tion what triggered the execution of the Script. It could be the arrival of a new incoming
PS/PDF file or it could be a trigger that is generated by the underlying Apogee Pre-
press system to inform us that a previous job has been completed and that the resources
are ready. The notification type can be retrieved by calling the getNotificationType
method of the Environment object. If the notification type is equal to ”PrintSphereFile”,
incoming files were the trigger. In our Script, we then call the function submitjob to
submit a job to the Prepress system.
If the notification type is equal to ”httpPost”, an HTTP POST call to the Script’s noti-
fication URL has been the trigger. In our case, the underlying Apogee Prepress system
informed the Script a specific task has been completed and that the resources are ready
to be downloaded. In that case, we will call the retrieveresources function.
5.2.3 Submitting the Job
In the function newJobId, we first will calculate the JobID we will use (which will be based
on the data.jobIdCounter. We then increment the data.jobIdCounter and return the
calculated JobID.
66 CHAPTER 5. APOGEE PREPRESS USE CASES
To submit a job to Prepress, we use the built-in submitPrepressJob method of the
Environment object. This function has three parameters:
1. jobid: the JobId we will give to the newly created Prepress job
2. resource: the submitted resource (typically a PS/PDF file)
3. vars: a JSON structure that contains a number of important variables (such as the
TicketTemplateName and optionally the NamedFeatureTag)
This method submits the job to the Prepress system (via JMF) and returns immediately.
The Prepress system will notify the PrintSphere AutoPilot server later on the progress of
the submitted job. The call to submitPrepressJob will return a JMF ID (known as the
QueueEntryID); this ID will be available in the callback of the Prepress system and will
allow to relate the created job to future responses.
5.2.4 Retrieving the Resources
When the Prepress system informed us that the job has been finished, we can call the built-
in prepressCallback function of the Environment to retrieve the generated resources.
This method will return a list of generated resources (of type Resource). These resources
will have certain metadata that will allow to identify what they really are. For the details,
we refer to the source example. The metadata can be retrieved using the getAttribute
method on the Resource objects.
5.3 Distribute Resources generated by Prepress
5.3.1 General
We now develop a Script that can notify users that specific Resources have been generated
by a Prepress job and share these Resources by PrintSphere. Although this Script can
be used in non-Prepress environments as well, we include it in this chapter because it
shows how PrintSphere AutoPilot can complement Apogee Prepress to realize further
automation.
The idea is that, in Prepress, Resources (typically PDF files or rendered images) gen-
erated by a specific job are exported at a specific location that is synchronized with
5.3. DISTRIBUTE RESOURCES GENERATED BY PREPRESS 67
the PrintSphere server. This synchronization will typically be realized by a PrintSphere
stand-alone client (available on PC and Mac). The Script expects that the Resources are
put in folders of which the name maps to the e-mail address of a specific person. When
resources are dropped in such a folder, the Script will pick-up these resources and move
them to an output location (that reflects the same structure as the input folder). The
first time a Resource is created for a specific user, a mail will be sent to this user that
contains a PrintSphere Share URL of this user’s folder.
5.3.2 The Script
1 function mustSendEmail(env, email) {2 var data = env.getScriptData();3 if (data == null) {4 data = new Map();5 env.setScriptData(data);6 }7
8 if (data[email]) {9 data[email] = data[email] + 1;
10 return false;11 } else {12 data[email] = 1;13 return true;14 }15 }16
17 function sendEmail(env, addressee, location) {18 print("Sending email to " + addressee);19
20 var mailService = env.getMailService();21 var mailMessage = mailService.newMessage();22 mailMessage.setFrom(vars.mailSender);23 mailMessage.setTo([addressee]);24 mailMessage.setSubject(vars.mailSubject);25 mailMessage.setBody(vars.mailBody + getShareURL(env, addressee, location));26 mailService.send(mailMessage);27 }28
29 function getChild(folder, resourcename) {30 var children = folder.getChildren();31 var i;32 for (i = 0; i < children.length; i++) {33 if (children[i].getFileName() == resourcename)34 return children[i];35 }36 return null;37 }38
39 function getShareURL(env, emailaddress, location) {40 var parent = location.asFolder();
68 CHAPTER 5. APOGEE PREPRESS USE CASES
41 if (parent) {42 var subfolder = getChild(parent, emailaddress);43 if (subfolder != null) {44 var url = subfolder.getShareURL();45 if (url != null)46 return url;47 }48 }49 return "http://www.wrongurl.com/";50 }51
52
53 function main(env) {54 var resources = env.getInputResources();55
56 var i;57 for (i = 0; i < resources.length; i++) {58 var resource = resources[i];59 print("Processing " + resource.getPathName());60
61 var pathlist = resource.getRelativePathList();62
63 resource.movesubdirList(env.getOutputLocation("success"), pathlist);64
65 if (mustSendEmail(env, pathlist[0]))66 sendEmail(env, pathlist[0], env.getOutputLocation("success"));67 }68 }69
70 var vars = {71 mailSubject: ’Apogee Prepress created a folder for you’,72 mailBody: ’New files are available at ’,73 mailSender: ’[email protected]’74 };
Listing 5.4: Distribute Resources generated by Prepress
In the main function of our Script (Listing 5.4), we iterate, as usual, over all input resources
and start processing them. For each resource, we first look where they are put relative to
the input folder. This can be done easily by means of the getRelativePathList method
on the InputResource instance (line 61) which returns a list of strings that consists of
the subfolders. The first element of this list should be a string that contains the e-mail
address of the person to be notified.
Then, we move the resource to the OuputLocation but with the same folder hierarchy.
This can be done with the method movesubdirList on the Resource instance (line 63).
This method will create (if necessary) the necessary subfolders and then move the input
resource to the newly created folder. As a result, the input resource will disappear from
the input folder.
5.3. DISTRIBUTE RESOURCES GENERATED BY PREPRESS 69
Then, on line 65, we call our own function mustSendEmail that checks whether we need
to send an e-mail to the person with this address. Such an element should only be sent if
this person has not yet received such an e-mail (in other words, when it is the first time
that a Resource appears in this person’s folder).
The function mustSendEmail is defined in line 1. In order for the Script to recall whether
a mail has already been sent to a specific user, we keep track of the number of messages
sent out to the different users by putting them in a JSON dictionary that survives between
different runs of the same Script. This data structure is put, exactly as how it is done in
the Script of Listing 2.3, in the Script data environment. Note that it is possible to clear
this data structure for a particular Script instance in the SphereCenter GUI by clicking
on the ’Reset Script Data’ item of the Tools menu (available under the cogwheel icon
of the ’Active Scripts’→’General’ screen). As a result of this action, all users will start
receiving one e-mail as soon as there appears a new resource in their folder. The logic of
this function is quite simple. If the data structure has not yet been set (which will always
be the case if the Script is first instantiated or whether the Script data have been reset),
it creates a map and puts in the Script data environment. For each e-mail address, we
will keep track of the number of resources that already have been processed by keeping
a dictionary item of which the key is the e-mail address and the value is an integer. If
an entry with this e-mail address is not yet available (which is checked in line 8), we will
add an entry with the value 1 in the dictionary and return true; in the other case (line
9), we will increase the value associated by the e-mail address by 1 and return false.
On line 66, we then send, if necessary, an e-mail to the person which contains a share URL
pointing to this person’s PrintSphere folder (under the OutputLocation) by calling our
own function sendEmail. In this function (line 17), we retrieve the MailService (method
on the Environment instance). Then, we create a new MailMessage instance by calling
the newMessage method on the MailService instance. we then set both the sender and
receiver’s e-mail address via the methods setFrom and setTo. On line 24, we set the mail
subject (in this Script, we assume that this subject is set as a Script parameter).
On line 25, we then set the body of the mail with the method setBody. This method
expects a string (containing the text lines of the mail body) as parameter. In our case,
this body text consists of a fixed string defined as Script parameter followed by the share
URL of the person’s resource folder. This share URL is built in the function getShareURL
on line 39. To calculate the share URL, we first retrieve the FolderResource instance
from the OutputLocation. This can be done by calling the method asFolder on the
OutputLocation instance. On line 42, we retrieve the subfolder of which the name is
equal to the person’s e-mail address (through the function getChild which is defined on
70 CHAPTER 5. APOGEE PREPRESS USE CASES
line 29). Once the ResourceFolder instance corresponding to this subfolder has been
retrieved, we call the method getShareURL to calculate a share URL from this subfolder.
On line 45, we then return the share URL.
In the function sendEmail, we now have set all relevant fields of the MailMessage instance
and can send out the mail by calling the send method on the MailService instance which
has the MailMessage instance as parameter.
5.3.3 Possible improvements
One way to proceed would be to define an additional dictionary level per job. In fact, our
Script already supports additional subfolder levels as it is right now. Of course, we will
have to let Prepress know where to put the resources (this can be done in the Prepress
Client by editing the settings of the job’s Export TP). We could set up Prepress such that
the name of the additional subfolder would correspond to the Prepress job’s name. The
Script could then be adapted to create a share URL for each job folder and to send out
a e-mail each time the first resource of a new job arrives.
Chapter 6
More Advanced Use Cases
Until now, most scripts have been triggered by incoming files. There can be cases, how-
ever, in which another triggering mechanism is needed. To tackle these situations, each
activated Script will have a unique HTTP URL associated with it. When this URL is
submitted to the PrintSphere AutoPilot server (with either GET or POST method), the
associated Script will be called. The caller can add attribute/value pairs to the URL
which will be accessible in the Script. It is also possible to upload data to the Script
environment if one submits a POST call with form data.
A first example where this alternate triggering can be useful is if an external system wants
to trigger a specific action. This can be an external server such as the IFTTT server that
calls a Script if certain conditions are met. Another use case where triggering a Script
with an URL will be useful is to interface with a web servers that provide asynchronous
feedback after a specific web service has been called. This type of servers will typically
acknowledge the call (with status 200) and will, later on, call you back with the results.
6.1 Triggering PrintSphere AutoPilot Scripts
As a first use case to trigger AutoPilot scripts with notification URL’s, we are considering
a system that sends an e-mail to a customer with a document in attachment that has to
be approved or rejected. In the e-mail, we embed 2 URL’s: an approve URL and a reject
URL. If the user clicks on either of these URL’s, the underlying Script will be triggered
with an approve or reject action.
It is important to realize that when the URL is called, the AutoPilot server will just
71
72 CHAPTER 6. MORE ADVANCED USE CASES
register the action. If there is a communication or encoding problem with the URL, it
will report an error. If not, the associated script will be scheduled to run. This will be
done at a later stage.
It is then up to the script to implement the logic that is expected.
In the current version, calling the notification URL will always result in a JSON reply. In
future versions, we consider to reply with HTML that can be displayed properly in the
browser and can be determined by the calling URL.
1 function replaceAll(source, dict) {2 if (dict == null)3 return source;4 for (var key in dict) {5 source = source.replace(new RegExp(’\\{\\$’ + key + ’\\}’, ’g’),
dict[key]);6 }7 return source;8 }9
10 function sendMessage(env, from, to, cc, subject, body, resource) {11 var mailService = env.getMailService();12 var message = mailService.newMessage();13 print("From: " + from);14 print("To: " + to[0]);15 print("Cc: " + cc[0]);16
17 if (from == null)18 return false;19 message.setFrom(from);20 if (to == null)21 return false;22
23 message.setTo(to);24
25 if ((cc != null) && (cc.length > 0))26 message.setCc(cc);27 message.setSubject(subject);28 if (body != null)29 message.setBodyHtml(body);30 if (resource != null)31 message.addAttachment(resource, resource.getFileName());32 return mailService.send(message);33 }34
35 function processFile(env, httpSession, resource) {36 print("Mailing " + resource.getFileName());37
38 var dict = {};39 dict.url = env.getNotificationUrl();40 dict.docname = resource.getFileName();41 dict.docid = encodeURI(’1234’); // hardcoded for now
6.1. TRIGGERING PRINTSPHERE AUTOPILOT SCRIPTS 73
42 dict.projectname = ’Test Project’;43
44 var subjecttemplate = "AutoPilot: approval of {$docname} (project:{$projectname})";
45 var subject = replaceAll(subjecttemplate, dict);46 var body = null;47
48 var templateresource = env.getAuxiliaryResource("template");49 if (templateresource != null) {50 var bodytemplate = templateresource.getText();51 if (bodytemplate != null) {52 body = replaceAll(bodytemplate, dict);53 }54 }55 sendMessage(env, "[email protected]", ["[email protected]"], [],
subject, body, resource);56 }57
58 function main(env) {59 var notificationType = env.getNotificationType();60 if (notificationType == "httpGet") {61 var map = env.getFormParameters();62 if (map == null) {63 return;64 }65 for (var entry in map) {;66 print(entry + " -> " + map[entry]);67 }68 } else {69 var res = env.getInputResources();70 var session = env.newHttpSession();71 mailService = env.getMailService();72
73 for (var i = 0; i < res.length; i++) {74 if (res[i].isPdf() || res[i].isImage()) {75 processFile(env, session, res[i]);76 } else {77 res[i].move(env.getOutputLocation("failure"));78 }79 res[i].release();80 }81 }82 return 0;83 }
Listing 6.1: Trigger AutoPilot Scripts from an e-mail
In the main routine of Listing 6.1 on line 58, we first check what triggered the running of
the Script. In case the script is triggered by a HTTP GET, we process the arguments of
the URL and print them out. It is here that that the logic must be put that is triggered
by clicking either APPROVE or REJECT in the example above. By analyzing the URL
parameters, we can see whether it was an APPROVE or REJECT and for what document.
74 CHAPTER 6. MORE ADVANCED USE CASES
In case the Script was triggered by an incoming file, we will send out a mail (with ap-
proval/reject links embedded) to the customer. Most of the time, additional processing
will take place before we submit the document for approval. To keep this example simple,
we mail the file directly (at least if it is a PDF file or image).
In the function on line 35, we will first generate the body of our e-mail. Since the body
can be quite complex, we chose to supply if as a text file in PrintSphere. This resource
will become accessible as an Auxiliary Resource (which is retrieved on line 48).
In Listing /refbodytemplate, we show the body template of the e-mail.
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
2 <html lang="en">3 <head>4 </head>5 <body>6 <h1>Document Approval</h1>7 <h4>Project Name: {$projectname}</h4>8 <h4>Document Name: {$docname}</h4>9 <p>If you agree with the attached document, please click
10 <a href="{$url}?action=APPROVE&docid={$docid}">APPROVE</a>.11 </p>12 <p>If you want to reject the attached document, please click13 <a href="{$url}?action=REJECT&docid={$docid}">REJECT</a>.14 </p>15 <p>Pleas note that after clicking, you will be directed to the browser16 and will receive a rather technical message. This is normal; after17 clicking, the AutoPilot engine will trigger your Script and execute the18 required logic. In this case, you will also receive a confirmation19 e-mail.20 </p>21 </body>
Listing 6.2: Template for e-mail boddy
Both subject line template and body template contain variables that can be replaced by
calling the method replaceAll (see lines 45 and 52). The variables to be replaced or put
in a dictionary which is passed to the replaceAll call. For the example here, we use the
following variables:
• docname
• docid: the document Id. In our case, we use a fixed id. Since this variable will be
used to build the URL, we have to encode it (see line 41). If we use only digits, this
is, of course, not necessary.
6.2. INTERFACING WITH ASYNCHRONOUS WEB SERVICES 75
• projectname
• url: the notification URL
When all variable replacements have been carried out, we finally send the e-mail with
attachment to the customer.
In Figure 6.1, we display an e-mail sent out by Script 6.1.
Figure 6.1: E-mail sent to the User to Approve/Reject a Document
6.2 Interfacing with Asynchronous Web Services
We already encountered such an example in section 5.2 where we submitted jobs to Apogee
Prepress and were notified when the resources had been generated. The example in that
section, however, used built-in functionality to interface with an Apogee Prepress in the
PrintSphere AutoPilot Cloud.
It is perfectly possible to use PrintSphere AutoPilot to interface with asynchronous web
servers yourself.
In order to allow an external server to trigger our Script, we must now the Script URL.
This URL is visible in the SphereCenter GUI, but, often, we need the URL while run-
76 CHAPTER 6. MORE ADVANCED USE CASES
ning the Script. The URL can be retrieved by the getNotificationUrl method on the
Environment object.
TO BE COMPLETED
6.3 Retrieving Mail from a Mail Server
In previous chapters, we already showed that scripts can be triggered in different ways.
The most straightforward way is the arrival of resources in PrintSphere, but it is also
possible to trigger scripts explicitly by calling a URL or by calling scripts at regular times
using the so-called Scheduled Execution functionality.
As another variation on the same theme, one might want to run a script when a mail has
arrived in the inbox of a particular user. Although there currently is no direct support
to trigger scripts in this way, it is possible to realizing this functionality by directly
connecting to external mail servers using the ClientMailService functionality.
6.3.1 Retrieving Mail Messages
We first will implement a function retrievemailscript (see line 12 of Listing 6.3)
that retrieves, analyzes and extracts mail messages that have arrived in a specific In-
box. All available functionality related to the retrieval of e-mails is accessible via a
ClientMailService instance which can be created with the newClientMailService
method on the Environment. This method has the following parameters:
1. host: the hostname of the mail server; for Google Mail, for instance, the hostname
is ”imap.google.mail”.
2. user: the user name of the mail account
3. password: the password of the mail account
4. protocol: the protocol that will be used. One of:
(a) ”pop3”: for a mail server using the POP3 protocol
(b) ”pop3s”: for a mail server using the secure version of the POP3 protocol
(c) ”imap” : for a mail server using the IMAP protocol
(d) ”imaps”: for a mail server using the secure version of the IMAP protocol
6.3. RETRIEVING MAIL FROM A MAIL SERVER 77
In the main function of Script 6.3 (line 32), we first create a ClientMailService instance.
In this example, we retrieve mails from the Google Mail server with the secure IMAP
protocol. The retrievemails function is called in line 42 and defined in line 12.
We first retrieve all e-mail from the Inbox with the getEmailsFromInbox method. We
do this here for simplicity but one should note that retrieving all messages from an Inbox
might be a heavy operation if we have a lot of mails in the Inbox; it therefore is (as we
will see shortly) better to reduce the messages to be retrieved by specifying a number of
conditions (such as the sender of the message and the mail address of the recipient).
We then (line 17) iterate over the retrieved list of messages and do the following:
• retrieve the sender with the getFrom method (line 18). Please note that the for-
mat can be in the so-called display format, which represents the addresses as,
e.g., ”John Doe <[email protected]>”. In order to convert these strings to the
plain address (”[email protected]”), one can use one of the convenience methods
normalizeAddress or normalizeAddresses which work on a single address or an
array of addresses.
• retrieve the mail message subject (or title) with the getSubject method
• retrieve the body text of the mail message with the getBody method. Please note
that it is not always trivial to determine what the body text of a mail message is
(in case there are multiple attachments). We always return the first object of the
mail message which has the ”text/plain” mime type.
• retrieve the date on which the message was sent with the getDate method (line 20)
• retrieve the list of attachments with the getAttachments method (line 22). Each
attachment will be saved as a Resource in the temporary PrintSphere folder. In
order to preserve these attachments, they can be modified and/or moved to output
locations. Please note that the getFileName method will return the file name of
the resource in the temporary file system. This file name can differ from the actual
attachment. The attachment name can be retrieved with the getOriginalFileName
method.
In line 49, we finally call the close method on the ClientMailService. This will close
the connection to the mail server.
1 function deletemails(env, service, from, to, folder) {
78 CHAPTER 6. MORE ADVANCED USE CASES
2 var messages = service.getEmailsFromInbox(from, to);3 var i;4 for (i = 0; i < messages.length; i++) {5 var message = messages[i];6 service.copyToFolder(message, folder);7 message.markForDelete();8 }9 service.expunge();
10 }11
12 function retrievemails(env, service) {13 var messages = service.getEmailsFromInbox();14 var i;15 for (i = 0; i < messages.length; i++) {16 var message = messages[i];17 var from = message.getFrom();18 print("From: " + message.getFrom() + ", Title: " + message.getSubject());19 print("Body: " + message.getBody());20 var date = message.getDate();21 print("Date: " + date);22 var attachments = message.getAttachments();23 var j;24 for (j = 0; j < attachments.length; j++) {25 var attachment = attachments[j];26 print("Attachment: " + attachment.getPathFileName());27 attachment.move(env.getOutputLocation("attachments"),
attachment.getOriginalFileName());28 }29 }30 }31
32 function main(env) {33 var service = env.newClientMailService("imap.gmail.com",34 "[email protected]", "????",35 "imaps");36
37 var selection = 1;38
39 switch(selection) {40 case 1:41 default:42 retrievemails(env, service);43 break;44 case 2:45 deletemails(env, service,46 "[email protected]", "[email protected]", "Trash");47 break;48 }49
50 service.close();51 }
Listing 6.3: Retrieve Mail Messages from a Mail Server
6.3. RETRIEVING MAIL FROM A MAIL SERVER 79
6.3.2 Copy and Remove Mail Messages
We now make a function deletemails (line 1 of the script in Listing 6.3) that will retrieve
all messages that came from a specific user and move these messages to a specific folder
on the mail server. Please note that this functionality is only available for mail servers
that use the IMAP protocol.
In line 2, we first retrieve the mail messages that are sent from a particular user to a partic-
ular e-mail address. Sometimes, mails of different recipients can be managed by the same
mail server account. In Google Mail, for instance, all mails sent to ”[email protected]”
and ”[email protected]” will end up in the same Inbox. By filtering on the recip-
ient address, we can distinguish between the mails sent to either e-mail address.
We then iterate over the retrieved messages (line 4) and copy each mail message to a
separate folder on the e-mail server by using the copyToFolder method which is called
on the MailClientService and has 2 parameters:
1. message: an instance of ClientMailMessage
2. folder: a String that contains the folder name
Then, we mark the message as deleted (in line 7) with the markForDelete method.
Eventually, we call in line 9 the expunge method on the ClientMailService instance;
this operation will effectively delete the messages that are marked as deleted.
6.3.3 Script to Process Incoming Mail Messages
We now will develop a Script that will watch the incoming mail messages that are subject
to a number of constraints. For each such message, we will create a separate folder with
the attachments and generate an XML file that contains the metadata, subject, body and
references to the attachments.
These XML files (together with the attachments) can then be used to trigger other scripts.