3.2.  Actions

Overview

Actions make PDF documents interactive and more complex. Complex means that they should be tested, especially when interactive documents are part of a workflow. Those actions need to work correctly.

An action is a dictionary object inside PDF containing the keys /S and /Type. The key /Type always maps to the value Action. And the the key /S (Subtype) has different values:

// Types of actions:

GoTo:        Set the focus to a destination in the current PDF document
GoToR:       Set the focus to a destination in another PDF document
GoToE:       Go to a destination inside an embedded file
GoTo3DView:  Set the view to a 3D annotation
Hide:        Set the hidden flag of the specified annotation
ImportData:  Import data from a file to the current document
JavaScript:  Execute JavaScript code
Movie:       Play a specified movie
Named:       Execute an action, which is predefined by the PDF viewer
Rendition:   Control the playing of multimedia content
ResetForm:   Set the values of form fields to default
SetOCGState: Set the state of an OCG
Sound:       Play a specified sound
SubmitForm:  Send the form data to an URL
Launch:      Execute an application
Thread:      Set the viewer to the beginning of a specified article
Trans:       Update the display of a document, using a transition dictionary
URI:         Go to the remote URI

PDFUnit provides tags for some of these actions:

<!-- Tags to test actions: -->

<hasNumberOfActions />
<hasNumberOfJavaScriptActions />

<hasAnyAction> 
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasAnyAction>

...  continued
... continuation:

<hasChainedAction>
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasChainedAction>

<hasCloseAction> 
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasCloseAction>

<hasImportDataAction> 
  <matchingComplete    filename=".." />  (tag optional, attribute required)
</hasImportDataAction>

<hasJavaScriptAction> 
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasJavaScriptAction>

<hasLaunchAction> 
  <toLaunch />                           (optional)
</hasLaunchAction>

...  continue
... continuation

<hasLocalGotoAction> 
  <toDestination />                      (optional)
</hasLocalGotoAction>

<hasNamedAction>
  <withName />                           (optional)
</hasNamedAction>

<hasOpenAction>
  <containing        />                  (all nested tags ...
  <matchingComplete  />                  ...
  <matchingRegex     />                  ...
  <withDestinationTo />                  ... are optional) 
</hasOpenAction>

<hasPrintAction>
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasPrintAction>

...  continued
... continuation:

<hasRemoteGotoActionTo file=".."         (required)
                       destination=".."  (optional) 
                       page=".."         (optional)
/>

<hasResetFormAction /> (no attributes and no nested tags!)

<hasSaveAction>
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasSaveAction>

<hasSubmitFormAction> 
  <withDestination />                    (optional)
</hasSubmitFormAction>

<hasURIAction>
  <containing       />                   (all nested tags ...
  <matchingComplete />                   ...
  <matchingRegex    />                   ... are optional) 
</hasURIAction>

...  (end of list)

The following sections show examples for different types of actions.

Close-Actions

Close-Actions are executed when the document is being closed:

<!-- The fundamental way to compare actions with expected values: -->

<testcase name="Principle_ComparingActionValues">
  <assertThat testDocument="actions/documentCloseAction.pdf">
    <hasCloseAction>
      <containing>app.alert('A sample for a DOCUMENT_CLOSE-action');</containing>
    </hasCloseAction>
  </assertThat>
</testcase>

The content of a Close-Action can also be compared with the content of a file:

<testcase name="hasCloseAction_MatchingComplete_ContentFromFile">
  <assertThat testDocument="actions/documentCloseAction.pdf">
    <hasCloseAction>
      <matchingComplete filename="actions/documentCloseAction.js" 
                        whitespaces="IGNORE"
      />
    </hasCloseAction>
  </assertThat>
</testcase>

The attribute whitespaces=".." is optional. The default whitespace processing is NORMALIZE.

ImportData-Actions

ImportData-Actions import data from a file. They need the filename as a parameter:

<testcase name="hasImportDataAction_MatchingFilename">
  <assertThat testDocument="actions/chainedActions.pdf">
    <hasImportDataAction>
      <matchingComplete filename="build.xml" />
    </hasImportDataAction>
  </assertThat>
</testcase>

PDFUnit checks whether the action contains the expected filename. The file's existence is not checked.

JavaScript-Actions

Since JavaScript code is generally quite long, it makes sense to read the expected text for a JavaScript-Action from a file:

<testcase name="hasJavaScriptAction_MatchingComplete_ContentFromFile">
  <assertThat testDocument="javascript/bookmarkWithJavaScriptAction_OneSimpleAlert.pdf">
    <hasJavaScriptAction>
      <matchingComplete filename="javascript/javascriptAction_OneSimpleAlert.js" />
    </hasJavaScriptAction>
  </assertThat>
</testcase>

The content of the JavaScript file is completely compared with the content of the JavaScript action. White spaces are normalized.

Launch-Actions

Launch-Actions are launching applications or scripts. This can be tested like this:

<testcase name="hasLaunchAction_Notepad_Print">
  <assertThat testDocument="actions/launchActionToFile.pdf">
    <hasLaunchAction>
      <toLaunch application="c:/windows/notepad.exe" operation="print" />
    </hasLaunchAction>
  </assertThat>
</testcase>

PDFUnit compares the content of the attributes application=".." and operation=".." with the actual values of the Launch-Action. It is not checked whether the application can be started.

Named-Actions

The name of Named-Actions should be verified:

<testcase name="hasNamedAction_WithName_NextPage">
  <assertThat testDocument="actions/namedActionsNextPages.pdf">
    <hasNamedAction>
      <withName>
        <matchingComplete>/NextPage</matchingComplete>
      </withName>
    </hasNamedAction>
  </assertThat>
</testcase>

Goto-Actions

Goto-Actions need a destination in the same PDF document:

<testcase name="hasGotoAction_ToNamedDestination">
  <assertThat testDocument="actions/bookmarksWithPdfOutline.pdf">
    <hasLocalGotoAction>
      <toDestination name="destination2.1" />
    </hasLocalGotoAction>
  </assertThat>
</testcase>

The test is successful, when the current test PDF contains the expecte destination destination2.1.

GotoRemote-Actions

GotoRemote-Actions need a destination in another PDF file.

<testcase name="hasGotoRemoteActionTo_NamedDestination">
  <assertThat testDocument="actions/gotoRemotePageAction.pdf">
    <hasRemoteGotoActionTo file="destination.pdf"
                           destination="destination-3"
    />
  </assertThat>
</testcase>

<testcase name="hasGotoRemoteAction_ToPage">
  <assertThat testDocument="actions/gotoRemotePageAction.pdf">
    <hasRemoteGotoActionTo file="destination.pdf" 
                           page="4" 
    />
  </assertThat>
</testcase>

PDFUnit checks that the action in the PDF document under test contains an action with the expected destination. PDFUnit does not check whether the remote file or the destination in the remote file exist.

Open-Actions

Open-Actions are executed when the PDF document is loaded. Often they are JavaScript- or Goto-Actions.

<testcase name="hasOpenAction_MultipleInvocation">
  <assertThat testDocument="actions/documentOpenAction_Print.pdf">
    <hasOpenAction>
      <matchingRegex>(?ms).*print(.*)</matchingRegex>
      <matchingComplete>this.print(true);</matchingComplete>
    </hasOpenAction>
  </assertThat>
</testcase>

In addition to the tags for comparing text, Open-Actions can be tested using the tag <withDestinationTo />:

<testcase name="hasOpenAction_GotoPage2">
  <assertThat testDocument="actions/documentOpenAction_Goto.pdf">
    <hasOpenAction>
      <withDestinationTo page="2" />
    </hasOpenAction>
  </assertThat>
</testcase>

Print-Actions

Print-Actions are JavaScript-Actions which are processed immediately before or after printing a PDF document. They are associated internally with the events WILL_PRINT or DID_PRINT.

<testcase name="hasPrintAction_WillPrint">
  <assertThat testDocument="actions/documentPrintActions.pdf">
    <hasPrintAction>
      <matchingComplete>app.alert('A sample for a WILL_PRINT-action');</matchingComplete>
    </hasPrintAction>
  </assertThat>
</testcase>

As for JavaScript-Actions the content of the Print-Action is compared with the expected string. The whitespaces are normalized before.

ResetForm-Actions

ResetForm-Actions have no parameters and it is unnecessary to compare text. Only the existence will be verified:

<testcase name="hasResetFormAction">
  <assertThat testDocument="acrofields/javaScriptForFields.pdf">
    <hasResetFormAction />
  </assertThat>
</testcase>

Save-Actions

Save-Actions are JavaScript-Actions which are processed immediately before or after saving a PDF document. The actions are associated with the PDF-Events WILL_SAVE or DID_SAVE.

<testcase name="hasSaveAction_MultipleInvocation">
  <assertThat testDocument="actions/documentSaveActions.pdf">
    <hasSaveAction>
      <matchingComplete>app.alert('A sample for a DID_SAVE-action');</matchingComplete>
    </hasSaveAction>
  </assertThat>
</testcase>

Again, the comparison is performed only after a normalization of whitespace characters. All known tags can be used to compare the texts.

SubmitForm-Actions

SubmitForm-Actions need a destination to which forms can be sent:

<testcase name="hasSubmitFormAction_ToUri">
  <assertThat testDocument="acrofields/javaScriptForFields.pdf">
    <hasSubmitFormAction>
      <withDestination toURI="http://www.geek-tutorials.com/java/itext/submit.php" /> 
    </hasSubmitFormAction>
  </assertThat>
</testcase>

PDFUnit does not check whether the destination exists. It only checks that the currently tested action contains a destination with the expected value.

URI-Actions

URI-Actions need a target URI:

<testcase name="hasURIAction">
  <assertThat testDocument="actions/noBookmarks-manyActions.pdf">
    <hasURIAction>
      <matchingComplete>http://www.imdb.com/</matchingComplete>
    </hasURIAction>
  </assertThat>
</testcase>

PDFUnit does not access the internet. So this test merely checks that the PDF under test contains a URI with the expected value.

Any Action

The following example tests 4 different types of actions which should all exist in one PDF document:

<testcase name="hasAnyAction_DifferentKindOfActions">
  <assertThat testDocument="actions/chainedActions.pdf">
    <hasAnyAction>
      <matchingComplete>app.alert('Demo: the first action of five.');</matchingComplete>
      <matchingComplete>http://www.google.de</matchingComplete>
      <matchingComplete>c:/windows/notepad.exe</matchingComplete>
      <matchingComplete>this.print(true);</matchingComplete>
    </hasAnyAction>
  </assertThat>
</testcase>

Whitespace Processing in Comparisons

You can control how whitespaces are processed when comparing text. In the following example line breaks and blank lines are ignored:

<testcase name="hasCloseAction_Containing_ContentFromReader">
  <assertThat testDocument="actions/documentCloseAction.pdf">
    <hasCloseAction>
      <containing filename="actions/documentCloseAction.js"  whitespaces="IGNORE" />
    </hasCloseAction>
  </assertThat>
</testcase>

The chapter 13.4: “Whitespace Processing” explains the flexible handling of whitespaces.