3.14. Formularfelder

Überblick

Wenn Inhalte eines PDF-Dokumentes weiterverarbeitet werden sollen, spielen Formularfelder eine entscheidende Rolle. Diese sollten korrekt erstellt sein. Dafür sind vor allem korrekte und eindeutige Feldnamen wichtig, aber auch manche Eigenschaft eines Feldes.

Mit dem Hilfsprogramm ExtractFieldInfo können Informationen zu Formularfeldern in eine XML-Datei extrahiert. Dadurch werden Feldeigenschaften sichtbar, die ansonsten nur schwer zu ermitteln sind. Alle in der XML-Datei dargestellten Eigenschaften können in Tests überprüft werden.

In den folgenden Abschnitten werden viele Tests auf Feldeigenschaften, Größe und natürlich auch die Inhalte von Feldern beschrieben. Je nach Anwendungkontext kann der eine oder andere Tests für Sie wichtig sein:

// Simple tests:
.hasField(..) 
.hasField(..).ofType(..)
.hasField(..).withHeight()
.hasField(..).withWidth()
.hasFields()                    
.hasFields(..)                   
.hasNumberOfFields(..) 
.hasSignatureField(..)
.hasSignatureFields()            1

// Tests belonging to all fields:
.hasFields().withoutDuplicateNames()
.hasFields().allWithoutTextOverflow()   2

// Content of a field:
.hasField(..).withText().containing()
.hasField(..).withText().endingWith()
.hasField(..).withText().equalsTo()
.hasField(..).withText().matchingRegex()
.hasField(..).withText().notContaining()
.hasField(..).withText().notMatchintRegex()
.hasField(..).withText().startingWith()

// JavaScript associated to a field:
.hasField(..).withJavaScript().containing(...)

// Field properties:
.hasField(..).withProperty().checked()
.hasField(..).withProperty().editable()
.hasField(..).withProperty().exportable()
.hasField(..).withProperty().multiLine()
.hasField(..).withProperty().multiSelect()
.hasField(..).withProperty().notExportable()
.hasField(..).withProperty().notSigned()
.hasField(..).withProperty().notVisibleInPrint()
.hasField(..).withProperty().notVisibleOnScreen()
.hasField(..).withProperty().optional()
.hasField(..).withProperty().passwordProtected()
.hasField(..).withProperty().readOnly()
.hasField(..).withProperty().required()
.hasField(..).withProperty().signed()
.hasField(..).withProperty().singleLine()
.hasField(..).withProperty().singleSelect()
.hasField(..).withProperty().unchecked()
.hasField(..).withProperty().visibleInPrint()
.hasField(..).withProperty().visibleOnScreen()
.hasField(..).withProperty().visibleOnScreenAndInPrint()

1

Tests werden in Kapitel 3.26: „Signaturen - Unterschriebenes PDF“ beschrieben.

2

Tests werden in Kapitel 3.15: „Formularfelder, Textüberlauf“ beschrieben.

Existenz

Mit dem folgenden Test können Sie prüfen, ob es überhaupt Felder gibt:

@Test
public void hasFields() throws Exception {
  String filename = "documentUnderTest.pdf";
  
  AssertThat.document(filename)
            .hasFields()  // throws an exception when no fields exist
  ;
}

Feldnamen

Da bei der Verarbeitung von PDF-Dokumenten über die Feldnamen auf deren Inhalte zugegriffen wird, muss sichergestellt sein, dass es die erwarteten Felder auch gibt:

@Test
public void hasField_MultipleInvocation() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname1 = "name";
  String fieldname2 = "address";
  String fieldname3 = "postal_code";
  String fieldname4 = "email";
  
  AssertThat.document(filename)
            .hasField(fieldname1)
            .hasField(fieldname2)
            .hasField(fieldname3)
            .hasField(fieldname4)
  ;
}

Das gleiche Ziel kann auch mit einer anderen Syntax erreicht werden, indem ein Array mit Feldnamen an die Methode hasFields(..) übergeben wird:

@Test
public void hasFields() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname1 = "name";
  String fieldname2 = "address";
  String fieldname3 = "postal_code";
  String fieldname4 = "email";
  
  AssertThat.document(filename)
            .hasFields(fieldname1, fieldname2, fieldname3, fieldname4)
  ;
}

Doppelte Feldnamen sind zwar nach der PDF-Spezifikation erlaubt, bereiten bei der Weiterverarbeitung von PDF-Dokumenten höchstwahrscheinlich aber Überraschungen. PDFUnit stellt deshalb eine Methode zur Verfügung, um die Abwesenheit doppelter Namen zu prüfen:

@Test
public void hasFields_WithoutDuplicateNames() throws Exception {
  String filename = "documentUnderTest.pdf";
  
  AssertThat.document(filename)
            .hasFields()
            .withoutDuplicateNames()
  ;
}

Anzahl

Wenn es lediglich wichtig ist, wieviele Formularfelder ein PDF-Dokument enthält, nutzen Sie die Funktion hasNumberOfFields(..):

@Test
public void hasNumberOfFields() throws Exception {
  String filename = "documentUnderTest.pdf";
  
  AssertThat.document(filename)
            .hasNumberOfFields(4)
  ;
}

Möglicherweise ist es auch interessant, sicherzustellen, dass ein PDF-Dokument keine Felder (mehr) besitzt:

@Test
public void hasNumberOfFields_NoFieldsAvailable() throws Exception {
  String filename = "documentUnderTest.pdf";
  int zeroExpected = 0;
  
  AssertThat.document(filename)
            .hasNumberOfFields(zeroExpected)
  ;
}

Inhalte von Feldern

Am einfachsten ist ein Test, der prüft, ob ein bestimmtes Feld überhaupt Daten enthält:

@Test
public void hasField_WithAnyValue() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "ageField";
  
  AssertThat.document(filename)
            .hasField(fieldname)
            .withText()
  ;
}

Zur Überprüfung der Inhalte von Feldern stehen ähnliche Vergleichsmethoden zur Verfügung, wie für die Überprüfung der Inhalte von Dokumenteneigenschaften:

.containing(..)
.endingWith(..)
.equalsTo(..)
.matchingRegex(..)
.notContaining(..)
.notMatchingRegex(..)
.startingWith(..)

Die nachfolgenden Beispiele sollen Ihnen ein paar Anregungen für die Verwendung dieser Methoden geben:

@Test
public void hasField_EqualsTo() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "Text 1";
  String expectedValue = "Single Line Text";
  
  AssertThat.document(filename)
            .hasField(fieldname)
            .equalsTo(expectedValue)
  ;
}
/**
 * This is a small test to protect fields against SQL-Injection.
 */
@Test
public void hasField_NotContaining_SQLComment() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "Text 1";
  String sqlCommentSequence = "--";
  
  AssertThat.document(filename)
            .hasField(fieldname) 
            .notContaining(sqlCommentSequence)
  ;
}

Whitespaces werden vor dem Vergleich des tatsächlichen mit dem erwarteten Text normalisiert.

Feldtypen

Formularfelder haben einen bestimmten Typ. Auch wenn die Bedeutung des Typs wohl nicht so groß ist, wie die des Namens, gibt es trotzdem eine Testmethode für Typen:

@Test
public void hasField_WithType_MultipleInvocation() throws Exception {
  String filename = "documentUnderTest.pdf";
  
  AssertThat.document(filename)
            .hasField("Text 25")       .ofType(TEXT)
            .hasField("Check Box 7")   .ofType(CHECKBOX)
            .hasField("Radio Button 4").ofType(RADIOBUTTON)
            .hasField("Button 19")     .ofType(PUSHBUTTON)
            .hasField("List Box 1")    .ofType(LIST)
            .hasField("List Box 1")    .ofType(CHOICE)
            .hasField("Combo Box 5")   .ofType(CHOICE)
            .hasField("Combo Box 5")   .ofType(COMBO)
  ;
}

Das vorhergehende Code-Listing enthält bis auf das Signaturfeld alle Feldtypen, die überprüft werden können. Mit dem nächsten Beispiel wird auf Signaturfelder geprüft:

@Test
public void hasField_WithType_Signature() throws Exception {
  String filename = "documentUnderTest.pdf";
  
  AssertThat.document(filename)
            .hasField("Signature2").withType(SIGNATURE)
  ;
}

Ausführliche Tests zu Signaturen werden in Kapitel 3.26: „Signaturen - Unterschriebenes PDF“ beschrieben.

Die möglichen Feldtypen sind als Konstanten in com.pdfunit.Constants definiert. Die Namen der Konstanten entsprechen den gängigen, sichtbaren Elementen einer graphischen Anwendung. Innerhalb von PDF gibt es aber andere Typen. Weil deren Namen in einer Fehlermeldungen auftauchen können, gibt die folgende Liste die Zuordnung wider:

// Mapping between PDFUnit-Constants and PDF-internal types.

PDFUnit-Constant    PDF-intern
-------------------------------
CHOICE          ->  "Ch"
COMBO           ->  "Ch"
LIST            ->  "Ch"
CHECKBOX        ->  "Btn"
PUSHBUTTON      ->  "Btn"
RADIOBUTTON     ->  "Btn"
SIGNATURE       ->  "Sig"
TEXT            ->  "Tx"

Größe von Feldern

Falls die Größe von Formularfeldern wichtig ist, stehen für die Überprüfung von Länge und Breite zwei Methoden zur Verfügung:

@Test
public void hasField_WidthAndHeight() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "Title of 'someField'"; 
  int allowedDeltaForMillis = 2;
  AssertThat.document(filename)
            .hasField(fieldname)
            .withWidth(159, MILLIMETERS, allowedDeltaForMillis)
            .withHeight(11, MILLIMETERS, allowedDeltaForMillis)
  ;
}

Beide Methoden können mit verschiedenen Maßeinheiten aufgerufen werden, die als Konstanten vorgegeben sind. Und da es beim Umgang mit den Größen zu Rundungsfehlern kommen kann, muss als dritter Parameter noch eine erlaubte Abweichung des tatsächlichen vom erwarteten Wert mitgegeben werden. Millimeter und Points können aus Einheit verwendet werden. Points und eine Abweichung von '0' gelten als Default.

@Test
public void hasField_Width() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "Title of 'someField'"; 
  int allowedDeltaForPoints = 0;
  int allowedDeltaForMillis = 2;
  AssertThat.document(filename)
            .hasField(fieldname)
            .withWidth(450, POINTS, allowedDeltaForPoints)
            .withWidth(159, MILLIMETERS, allowedDeltaForMillis)
            .withWidth(450) // default is POINTS
  ;
}

Sie werden beim Erstellen eines Testes wahrscheinlich nicht die Maße eines Feldes kennen. Kein Problem, nehmen Sie eine beliebige Zahl für die Höhe und Breite und starten den Test. Die dann auftretende Fehlermeldung enthält die richtigen Werte in Millimetern.

Ob ein Text tatsächlich in ein Formularfeld passt, lässt sich durch die Größenbestimmung alleine nicht sicherstellen. Neben der Schriftgröße bestimmen auch die Worte am Zeilenende zusammen mit der Silbentrennung die Anzahl der benötigten Zeilen und damit die benötigte Höhe. Das Kapitel 3.15: „Formularfelder, Textüberlauf“ beschäftigt sich ausführlich mit diesem Thema.

Weitere Eigenschaften von Feldern

Formularfelder haben neben ihrer Größe noch weitere Eigenschaften, wie z.B. editable und required. Viele dieser Eigenschaften können manuell gar nicht getestet werden. Deshalb gehören passende Tests in jedes PDF-Testwerkzeug. Das folgende Beispiele stellt das Prinzip dar:

@Test
public void hasField_Editable() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldnameEditable = "Combo Box 4"; 
  
  AssertThat.document(filename)
            .hasField(fieldnameEditable) 
            .withProperty()
            .editable()
  ;
}

Insgesamt stehen folgende Methoden zur Überprüfung von Feldeigenschaften zur Verfügung:

// Check field properties

// All methods following .withProperty():
.checked()                .unchecked()
.editable(),              .readOnly()
.exportable(),            .notExportable()
.multiLine(),             .singleLine()
.multiSelect(),           .singleSelect()
.optional(),              .required()
.signed(),                .notSigned()
.visibleInPrint(),        .notVisibleInPrint()
.visibleOnScreen(),       .notVisibleOnScreen()

.visibleOnScreenAndInPrint()
.passwordProtected()

JavaScript Aktionen zur Validierung von Feldern

Wenn PDF-Dokumente Teil eines Workflows sind, unterliegen Formularfelder normalerweise bestimmten Plausibilitäten. Diese Plausibilitäten werden häufig durch eingebettetes JavaScript umgesetzt, um die Prüfungen schon zum Zeitpunkt der Eingabe auszuführen.

Mit PDFUnit kann geprüft werden, ob einem Formularfeld JavaScript zugeordnet ist. Erwartete Inhalte werden mit 'containing()' gegen die tatsächlichen Inhalte geprüft. Dabei werden Whitespace normalisiert:

@Test
public void hasFieldWithJavaScript() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldname = "Calc1_A"; 
  
  String scriptText = "AFNumber_Keystroke";
  AssertThat.document(filename)
            .hasField(fieldname)
            .withJavaScript()
            .containing(scriptText)
  ;
}

Unicode-Feldnamen

Wenn PDF-erstellende Werkzeuge Unicode-Sequenzen nicht richtig verarbeiten, wird es schwierig, diese Sequenzen in PDFUnit-Tests zu verwenden. Schwierig heißt aber nicht unmöglich. Das folgende Bild zeigt, dass der Name eines Feldes PDF-intern unglücklicherweise als UTF-16BE mit Byte-Order-Mark (BOM) gespeichert wird:

Auch wenn es schwierig ist, dieser Feldname kann als Java-Unicode-Sequenz getestet werden:

@Test
public void hasField_nameContainingUnicode_UTF16() throws Exception {
  String filename = "documentUnderTest.pdf";
  String fieldName = 
  //                     F           o           r           m           R
     "\u00fe\u00ff\u0000\u0046\u0000\u006f\u0000\u0072\u0000\u006d\u0000\u0052" +
  //         o           o           t           [           0           ]
     "\u0000\u006f\u0000\u006f\u0000\u0074\u0000\u005b\u0000\u0030\u0000\u005d" +
  //    
     "\u002e"                                                                   +
  //                     P           a           g           e           1
     "\u00fe\u00ff\u0000\u0050\u0000\u0061\u0000\u0067\u0000\u0065\u0000\u0031" +
  //         [           0           ] 
     "\u0000\u005b\u0000\u0030\u0000\u005d"                                     +
  //    
     "\u002e"                                                                   +
  //                     P           r           i           n           t
     "\u00fe\u00ff\u0000\u0050\u0000\u0072\u0000\u0069\u0000\u006e\u0000\u0074" +
  //         F           o           r           m           B           u
     "\u0000\u0046\u0000\u006f\u0000\u0072\u0000\u006d\u0000\u0042\u0000\u0075" +
  //         t           t           o           n           [           0
     "\u0000\u0074\u0000\u0074\u0000\u006f\u0000\u006e\u0000\u005b\u0000\u0030" +
  //         ]
     "\u0000\u005d";
  
  AssertThat.document(fileName)
            .hasField(fieldName)
  ; 
}

Mehr Informationen zu Unicode und Byte-Order-Mark liefern gute Artikel auf Wikipedia.