3.37. XMP-Daten

Überblick

XMP steht für Extensible Metadata Platform und ist ein von Adobe initiierter offener Standard, Metadaten in beliebige Dateitypen einzubetten. Nicht nur PDF-Dokumente, auch Bilder können mittels XMP Informationen über Ort, Format und andere Daten einbinden.

Die Metadaten in PDF sind für die Weiterverarbeitung durch andere Programme wichtig und sollten daher richtig sein. Zum Testen bietet PDFUnit die gleichen Methoden an, wie für XFA-Daten:

// Methods to test XMP data:
.hasXMPData()
.hasXMPData().matchingXPath(..) 
.hasXMPData().withNode(..)

.hasNoXMPData()

Existenz und Abwesenheit von XMP

Die nächsten Beispiele zeigen die Prüfung der An- bzw. Abwesenheit von XMP-Daten:

@Test
public void hasXMPData() throws Exception {
  String filename = "documentUnderTest.pdf";

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

Einzelne XML-Tags validieren

Die Existenz und auch die Werte einzelner XML-Knoten können überprüft werden. Das nächsten Beispiele basiert auf dem folgenden XML-Ausschnitt:

<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    ...
    <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
      <xmp:CreateDate>2011-02-08T15:04:19+01:00</xmp:CreateDate>
      <xmp:ModifyDate>2011-02-08T15:04:19+01:00</xmp:ModifyDate>
      <xmp:CreatorTool>My program using iText</xmp:CreatorTool>
    </rdf:Description>
    ...
  </rdf:RDF>
</x:xmpmeta>

Diese XMP-Daten wurden mit dem Hilfsprogramm ExtractXMPData aus einem PDF-Dokumente exportiert. Kapitel 9.14: „XMP-Daten nach XML extrahieren“ beschreibt die Verwendung dieses Hilfprogramms.

Im folgenden Beispiel wird die Existenz zweier XML-Knoten geprüft. Für jeden zu testenden Knoten muss eine Instanz von com.pdfunit.XMLNode erzeugt werden:

@Test
public void hasXMPData_WithNode_ValidateExistence() throws Exception {
  String filename = "documentUnderTest.pdf";
  XMLNode nodeCreateDate = new XMLNode("xmp:CreateDate");
  XMLNode nodeModifyDate = new XMLNode("xmp:ModifyDate");
  
  AssertThat.document(filename)
            .hasXMPData()
            .withNode(nodeCreateDate)
            .withNode(nodeModifyDate)
  ;
}

Soll auch der Wert eines Knotens überprüft werden, muss der erwartete Wert als zweiter Parameter an den Konstruktor von XMLNode übergeben werden:

@Test
public void hasXMPData_WithNodeAndValue() throws Exception {
  String filename = "documentUnderTest.pdf";
  XMLNode nodeCreateDate = new XMLNode("xmp:CreateDate", "2011-02-08T15:04:19+01:00");
  XMLNode nodeModifyDate = new XMLNode("xmp:ModifyDate", "2011-02-08T15:04:19+01:00");

  AssertThat.document(filename)
            .hasXMPData()
            .withNode(nodeCreateDate)
            .withNode(nodeModifyDate)
  ;
}

Der XPath-Ausdruck für den Knoten darf die Wurzel (document root) nicht enthalten, weil PDFUnit intern // ergänzt.

Existiert ein gesuchter Knoten mehrfach innerhalb der XMP-Daten, wird der erste Treffer verwendet.

Der Knoten darf selbstverständlich auch ein Attribut-Knoten sein.

XPath-basierte XMP-Tests

In PDFUnit gibt es noch die Methode matchingXPath(..), um das ganze Potential von XPath nutzen zu können. Die nächsten beiden Beispiele zeigen, was damit machbar ist:

@Test
public void hasXMPData_MatchingXPath() throws Exception {
  String filename = "documentUnderTest.pdf";
  String xpathString = "//xmp:CreateDate[node() = '2011-02-08T15:04:19+01:00']";
  XPathExpression expression = new XPathExpression(xpathString);
  
  AssertThat.document(filename)
            .hasXMPData()
            .matchingXPath(expression)
  ;
}
@Test
public void hasXMPData_MatchingXPath_MultipleInvocation() throws Exception {
  String filename = "documentUnderTest.pdf";

  String xpathDateExists = "count(//xmp:CreateDate) = 1";
  String xpathDateValue = "//xmp:CreateDate[node()='2011-02-08T15:04:19+01:00']";
  
  XPathExpression exprDateExists = new XPathExpression(xpathDateExists);
  XPathExpression exprDateValue = new XPathExpression(xpathDateValue);
  
  AssertThat.document(filename)
            .hasXMPData()
            .matchingXPath(exprDateValue)
            .matchingXPath(exprDateExists)
  ;
  
  // The same test in a different style:
  AssertThat.document(filename)
            .hasXMPData().matchingXPath(exprDateValue)
            .hasXMPData().matchingXPath(exprDateExists)
  ;
}

Der Funktionsumfang der Verarbeitung der XPath-Ausdrücke hängt vom verwendeten XML-Parser bzw. der XPath-Engine ab. PDFUnit verwendet die des JDK/JRE.

Im Kapitel 13.12: „JAXP-Konfiguration“ wird erläutert, wie ein beliebiger XML-Parser genutzt werden kann.

Default-Namensraum in XPath

XML-Namensräume werden automatisch ermittelt. Wenn aber ein Default-Namensraum verwendet wird, muss dieser als Instanz von DefaultNamespace angegeben werden. Und es muss für den Default-Namensraum ein Prefix verwendet werden. Der Wert für das Prefix kann beliebig sein.

@Test
public void hasXMPData_MatchingXPath_WithDefaultNamespace() throws Exception {
  String filename = "documentUnderTest.pdf";

  String xpathAsString = "//foo:format = 'application/pdf'";
  String stringDefaultNS = "http://purl.org/dc/elements/1.1/";
  DefaultNamespace defaultNS = new DefaultNamespace(stringDefaultNS);        
  XPathExpression expression = new XPathExpression(xpathAsString, defaultNS); 

  AssertThat.document(filename)
            .hasXMPData()
            .matchingXPath(expression)
  ;
}

Auch bei der Verwendung der Klasse XMLNode muss der Default-Namensraum mitgegeben werden:

@Test
public void hasXMPData_WithDefaultNamespace_SpecialNode() throws Exception {
  String filename = "documentUnderTest.pdf";

  String stringDefaultNS = "http://ns.adobe.com/xap/1.0/";
  DefaultNamespace defaultNS = new DefaultNamespace(stringDefaultNS);
  String nodeName  = "foo:ModifyDate";
  String nodeValue = "2011-02-08T15:04:19+01:00";
  XMLNode nodeModifyDate = new XMLNode(nodeName, nodeValue, defaultNS);

  AssertThat.document(filename)
            .hasXMPData()
            .withNode(nodeModifyDate)
  ;
}