3.23. Texte

Überblick

Der häufigste Testfall für PDF-Dokumente ist vermutlich, die Existenz erwarteter Texte zu überprüfen. Dafür steht das Tag <hasText /> zur Verfügung, das weitere Tags und Attribute für den eigentlichen Textvergleich bereitstellt:

<!-- Tags to verify content: -->

<hasText />

<!-- Nested tags of <hasText />:  -->

<hasText >
  <inClippingArea />     (optional)

  <!-- Comparing content: -->
  <containing />         (optional)
  <endingWith />         (optional)
  <matchingComplete />   (optional)
  <matchingRegex />      (optional)
  <startingWith />       (optional)
  
  <!-- Prove the absence of text: -->
  <notContaining />      (optional)
  <notEndingWith />      (optional)
  <!-- <notMatchingRegex /> is itentionally not provided -->
  <notMatchingRegex />   (optional)
  <notStartingWith />    (optional)
<hasText />

<!-- Attributes of <hasText /> to select pages.  -->
<!-- One of these attributes has to be used:  -->

<hasText on=".."                />
<hasText onPage=".."            />
<hasText onEveryPageAfter=".."  />
<hasText onEveryPageBefore=".." />
<hasText onAnyPageAfter=".."    />
<hasText onAnyPageBefore=".."   />

<!-- 
Whitespace processing, see:  13.4: „Behandlung von Whitespaces“  
--> 

Text auf bestimmten Seiten

Wenn Sie einen bestimmten Text auf der ersten Seite eines Anschreibens suchen, vielleicht die Adresse in der Anschrift, sieht ein Test folgendermaßen aus:

<testcase name="hasText_OnFirstPage_Containing">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="FIRST_PAGE">
      <containing>Content on first page.</containing>
    </hasText>
  </assertThat>
</testcase>

Die Selektion bestimmter Seiten geschieht über das Attribut on="..", für das mehrere Konstanten zur Verfügung stehen, beispielsweise on="FIRST_PAGE", on="EVERY_PAGE", on="ODD_PAGES" etc. Das Kapitel 13.2: „Seitenauswahl“ beschreibt die Seitenauswahl ausführlich.

Ein Text auf der letzten Seite wird folgendermaßen überprüft:

<testcase name="hasText_OnLastPage">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="LAST_PAGE">
      <containing>Content on last page.</containing>
    </hasText>
  </assertThat>
</testcase>

Auch Tests mit beliebigen individuellen Seiten sind möglich, dazu wird das Attribut onPage=".." zu Verfügung gestellt:

<testcase name="hasText_OnIndividualPages">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText onPage="2, 3">
      <containing>Content on</containing>
    </hasText>
  </assertThat>
</testcase>

Die Seitenzahlen im Attribut onPage=".." müssen durch Kommata getrennt werden.

Das Kapitel 13.2: „Seitenauswahl“ beschreibt die Seitenauswahl ausführlich.

Text auf allen Seiten

Um Text auf allen Seiten zu suchen, stehen im Attribut on=".." drei Konstanten zur Verfügung: on="EACH_PAGE", on="EVERY_PAGE" und on="ANY_PAGE".

<testcase name="hasText_OnEveryPage">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="EVERY_PAGE" >
      <startingWith>PDFUnit</startingWith>
    </hasText>
  </assertThat>
</testcase>
<testcase name="hasText_OnAnyPage">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="ANY_PAGE">
      <containing>Page # 3</containing>
    </hasText>
  </assertThat>
</testcase>

Die Konstanten on="EVERY_PAGE" und on="EACH_PAGE" fordern, dass der zu suchende Text wirklich auf jeder Seite existiert. Mit der Konstanten on="ANY_PAGE" reicht es, wenn der erwartete Text auf irgendeiner Seite des Dokumentes vorkommt.

Verneinte Suche

Die Logik der beiden vorhergehenden Beispiele ist klar. Unklar wird die Logik aber bei der Verneinung beider Aussagen. In der Umgangssprache ist der Unterschied zwischen Jede Seite enthält den Suchbegriff nicht und Irgendeine Seite enthält den Suchbegriff nicht nicht klar.

Um Fehler zu vermeiden, erlaubt PDFUnit keine verneinten Testmethoden in Kombination mit der Konstanten ON_ANY_PAGE. Der folgende Test ist daher nicht zulässig:

<testcase name="hasText_NotMatchingRegex">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="ANY_PAGE">
      <notEndingWith>wrongValueIntended</notEndingWith>
    </hasText>
  </assertThat>
</testcase>

Die Fehlermeldung lautet:

Searching text 'ON_ANY_PAGE' in combination with negated methods is not supported.

Anstatt zu testen, ob irgendeine Seite einen Suchbegriff NICHT enthält prüfen Sie lieber, dass Jede Seite den Suchbegriff enthält und fangen dann die Exception ab.

Zeilenumbrüche im Text

Zeilenumbrüche im Text werden beim Vergleich ignoriert, sowohl Zeilenumbrüche im Text der PDF-Seite, als auch die im Suchstring. Im folgenden Beispiel stammt der zu suchende Text aus dem Dokument Digital Signatures for PDF Documents von Bruno Lowagie (iText Software). Der erste Absatz sieht optisch so aus:

Tests auf den markierten Text ohne Berücksichtigung auf Zeilenumbrüche sehen folgendermaßen aus. Beide laufen erfolgreich durch:

<!-- 
  The PDF document has a (visible) line break after the word "The".
  The search string does not contain a line break. 
-->

<testcase name="hasText_ContainingLineBreaks_LineBreakInPDF">
  <assertThat testDocument="digitalsignatures20121017.pdf">
    <hasText on="FIRST_PAGE">
      <containing>The technology was conceived</containing>
    </hasText>
  </assertThat>
</testcase>
<!-- 
  The expected search string intentionally contains other line breaks.
-->

<testcase name="hasText_ContainingLineBreaks_LineBreakInExpectedString">
  <assertThat testDocument="digitalsignatures20121017.pdf">
    <hasText on="FIRST_PAGE">
      <containing>
        The 
        technology 
        was 
        conceived
      </containing>
    </hasText>
  </assertThat>
</testcase>

Text in Seitenausschnitten

Text kann aber nicht nur auf vollständigen Seite gesucht werden, sondern auch auf Seitenausschnitten. Das Kapitel 13.6: „Seitenausschnitt definieren“ beschreibt diesen Aspekt ausführlich.

Seiten ohne Text

Sie können auch testen, ob Ihr PDF-Dokument leere Seiten enthält:

<testcase name="hasText_AnyPageEmpty">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="EVERY_PAGE" />
  </assertThat>
</testcase>

Wenn Sie explizit prüfen wollen, ob eine Seite oder ein Ausschnitt einer Seite leer ist, geht das mit dem Tag hasNoText:

<testcase name="hasNoTextInClippingArea" >
  <assertThat testDocument="&pdfdir;/emptyPages/pagesPartiallyEmpty.pdf">
    <hasNoText on="FIRST_PAGE" >
      <inClippingArea upperLeftX="70" upperLeftY="80" width="90" height="60" />
    </hasNoText>
  </assertThat>
</testcase>

Mehrere Suchbegriffe gleichzeitig

Wenn auf einer Seite mehrere Texte gesucht werden, ist es lästig, für jeden Suchbegriff einen eigenen Test zu schreiben. Deshalb können manche Tags mehrfach benutzt werden:

<testcase name="hasText_Containing_MultipleTokens">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="ODD_PAGES">
      <containing>on</containing>
      <containing>page</containing>
      <containing>odd pagenumber</containing>
    </hasText>
  </assertThat>
</testcase>
<testcase name="hasText_NotContaining_MultipleTokens">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="FIRST_PAGE">
      <notContaining>even pagenumber</notContaining>
      <notContaining>Page #2</notContaining>
    </hasText>
  </assertThat>
</testcase>

Die Tests sind erfolgreich, wenn im ersten Beispiel alle Suchbegriffe gefunden werden, oder eben alle nicht im zweiten Beispiel.

Die Tags <startingWith /> und <endingWith /> dürfen nicht mehrfach auftreten.

Mehrfach verwendete Textvergleiche beziehen sich alle auf die spezifizierten Seiten des umschließenden Tags <hasText />:

<testcase name="hasText_MultipleInvocation">
<assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
  <hasText on="ANY_PAGE">
    <startingWith>PDFUnit</startingWith>
    <containing>Content on last page.</containing>
    <matchingRegex>.*[Cc]ontent.*</matchingRegex>
    <endingWith>of 4</endingWith>
  </hasText>
</assertThat>
</testcase>

Für Textvergleiche, die sich auf unterschiedliche Seiten beziehen, muss das Tag <hasText /> wiederholt werden:

<!-- 
  Different pages and different comparisons in one concatenated statement.
  This test works, but it is not recommended.
  When the test fails, the error analysis is more complicated than
  if you had 3 individual tests.
-->
<testcase name="hasText_ComplexSearchOverDifferentPages">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText on="ANY_PAGE">
      <startingWith>PDFUnit - Automated PDF Tests</startingWith>
    </hasText>
    <hasText on="EVEN_PAGES">
      <containing>Content</containing>
      <containing>even pagenumber</containing>
    </hasText>
    <hasText on="ODD_PAGES">
      <containing>odd pagenumber</containing>
    </hasText>
  </assertThat>
</testcase>

Eine solcher Test ist aber nicht sinnvoll, weil der Name des Tests nicht klar genug gewählt werden kann.

Fließende Seitenangaben mit Unter- und Obergrenze

Es kann den Wunsch geben, Texte auf jeder Seite zu überprüfen, aber nicht auf der ersten Seite. Ein solcher Test sieht folgendermaßen aus:

<testcase name="hasText_OnAnyPageAfter">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText onAnyPageAfter="1">
      <containing>Content on</containing>
    </hasText>
  </assertThat>
</testcase>

Die Zählung der Seitenzahlen beginnt mit 1.

Ungültige Seitenobergrenzen sind nicht unbedingt ein Fehler. Im folgenden Beispiel wird Text auf irgendeiner Seite zwischen 1 und 99 gesucht. Obwohl das Dokument nur 4 Seiten hat, endet der Test erfolgreich, weil die gesuchte Zeichenkette auf Seite 1 gefunden wird:

<!--
  Attention: the document has the search token on page 1. 
  And '1' is before '99'. So this test ends successfully.
-->
<testcase name="hasText_OnAllPagesBefore_WrongPageNumber">
  <assertThat testDocument="content/diverseContentOnMultiplePages.pdf">
    <hasText onAnyPageBefore="99">
      <containing>Content on</containing>
    </hasText>
  </assertThat>
</testcase>

Potentielles Problem mit Fließtext

Die sichtbare Reihenfolge des Textes einer PDF-Seite entspricht nicht zwingend der Textreihenfolge innerhalb des PDF-Dokumentes. Das kann zur Folge haben, dass PDFUnit scheinbaren Fließtext nicht als solchen erkennt, aber PDFUnit nutzt intern die Fähigkeiten von iText, Textobjekte über ihre Positionen auf der Seite zusammenzubauen.

Obwohl der Text im Rahmen ein eigenes Textobjekt ist, funktioniert ein Test über den fortlaufenden Text "the beginning. This is content":

<!-- 
  The PDF document does not store the text in the visible order.
-->  
<testcase name="hasText_TextNotInVisibleOrder">
  <assertThat testDocument="content/contentNotInVisibleOrder.pdf">
    <hasText on="FIRST_PAGE">
      <containing>
        Content at the beginning.
        This is content, placed in a frame by OpenOffice.
        Content at the end.
      </containing>
    </hasText>
  </assertThat>
</testcase>