Die „XML Forms Architecture, (XFA)“ ist eine Erweiterung der PDF-Strukturen um XML-Informationen, mit dem Ziel, PDF-Formulare in den Prozessen eines Workflow's besser verarbeiten zu können.
XFA Formulare sind nicht kompatibel zu „AcroForms“. Deshalb sind die PDFUnit-Tests für Acroforms auch nicht verwendbar. Test auf XFA-Daten basieren überwiegend auf XPath:
// Methods around XFA data:
.hasXFAData()
.hasXFAData().matchingXPath(..)
.hasXFAData().withNode(..)
.hasNoXFAData()
Der erste Test zielt auf die reine Existenz von XFA-Daten:
@Test public void hasXFAData() throws Exception { String filename = "documentUnderTest.pdf"; AssertThat.document(filename) .hasXFAData() ; }
Es ist auch möglich, explizit zu testen, dass ein PDF-Dokument keine XFA-Daten enthält:
@Test public void hasNoXFAData() throws Exception { String filename = "documentUnderTest.pdf"; AssertThat.document(filename) .hasNoXFAData() ; }
Das im nächsten Beispiel verwendete PDF-Dokument enthält folgende XFA-Daten (Ausschnitt):
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/"> ... <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c041 52.337767, 2008/04/13-15:41:00" > <config xmlns="http://www.xfa.org/schema/xci/2.6/"> ... <log xmlns="http://www.xfa.org/schema/xci/2.6/"> <to>memory</to> <mode>overwrite</mode> </log> ... </config> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> ... <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" > <xmp:MetadataDate>2009-12-03T17:50:52Z</xmp:MetadataDate> </rdf:Description> ... </rdf:RDF> </x:xmpmeta> ... </xdp:xdp>
Um auf einen bestimmten Knoten zu testen, muss eine Instanz von com.pdfunit.XMLNode
mit
dem XPath-Ausdruck für den Knoten erzeugt werden. Als zweiter Parameter wird der erwartete Wert
übergeben:
@Test public void hasXFAData_WithNode() throws Exception { String filename = "documentUnderTest.pdf"; XMLNode xmpNode = new XMLNode("xmp:MetadataDate", "2009-12-03T17:50:52Z"); AssertThat.document(filename) .hasXFAData() .withNode(xmpNode) ; }
PDFUnit analysiert die XFA-Daten des aktuellen PDF-Dokumentes und ermittelt die Namensräume selbständig, lediglich der Default-Namespace muss angegeben werden. |
Für die internen Verarbeitung ergänzt PDFUnit vor dem Knoten den Pfad-Bestandteil "//"
. Aus
diesem Grund darf der Knoten im Test kein Pfad sein, der die Wurzel "/"
enthält.
Sollte der XPath-Ausdruck für den Knoten zu mehreren Treffern führen, wird der erste Treffer verwendet.
Wenn das erwartete Ergebnis für den XPath-Ausdruck bei der Erzeugung der XMLNode-Instanz angegeben wird, dann wird dieses auch mit dem tatsächlichen Ergebnis verglichen. Andernfalls wird nur die Existenz des Knotens festgestellt.
Prüfungen auf Attribut-Knoten sind selbstverständlich auch möglich:
@Test public void hasXFAData_WithNode_NamespaceDD() throws Exception { String filename = "documentUnderTest.pdf"; XMLNode ddNode = new XMLNode("dd:dataDescription/@dd:name", "movie"); AssertThat.document(filename) .hasXFAData() .withNode(ddNode) ; }
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 hasXFAData_MatchingXPath_FunctionStartsWith() throws Exception { String filename = "documentUnderTest.pdf"; String xpathString = "starts-with(//dd:dataDescription/@dd:name, 'mov')"; XPathExpression expressionWithFunction = new XPathExpression(xpathString); AssertThat.document(filename) .hasXFAData() .matchingXPath(expressionWithFunction) ; }
@Test public void hasXFAData_MatchingXPath_FunctionCount_MultipleInvocation() throws Exception { String filename = "documentUnderTest.pdf"; String xpathProducer = "//pdf:Producer[. ='Adobe LiveCycle Designer ES 8.2']"; String xpathPI = "count(//processing-instruction()) = 30"; XPathExpression exprPI = new XPathExpression(xpathPI); XPathExpression exprProducer = new XPathExpression(xpathProducer); AssertThat.document(filename) .hasXFAData() .matchingXPath(exprProducer) .matchingXPath(exprPI) ; // The same test in a different style: AssertThat.document(filename) .hasXFAData().matchingXPath(exprProducer) .hasXFAData().matchingXPath(exprPI) ; }
Eine kleine Einschränkung muss genannt werden. Die XPath-Ausdrücke können nur mit den Möglichkeiten ausgewertet werden, die die verwendete XPath-Implementierung bietet. PDFUnit nutzt normalerweise die JAXP-Implementierung des verwendeten JDK. Damit ist die XPath-Kompatibilität aber vom JDK/JRE abhängig und unterliegt dem Wandel der Zeit.
Das Kapitel 13.12: „JAXP-Konfiguration“ erläutert am Beispiel von Xerces, wie ein beliebiger XML-Parser genutzt werden kann.
XML-Namensräume werden automatisch ermittelt. Wenn aber ein Default-Namensraum verwendet
wird, muss dieser als Instanz von DefaultNamespace
angegeben werden.
Für diese Instanz muss in Java ein Prefix verwendet werden.
Der Wert für das Prefix kann beliebig sein:
@Test public void hasXFAData_WithDefaultNamespace_XPathExpression() throws Exception { String filename = "documentUnderTest.pdf"; String namespaceURI = "http://www.xfa.org/schema/xfa-template/2.6/"; String xpathSubform = "count(//default:subform[@name ='movie']//default:field) = 5"; DefaultNamespace defaultNS = new DefaultNamespace(namespaceURI); XPathExpression exprSubform = new XPathExpression(xpathSubform, defaultNS); AssertThat.document(filename) .hasXFAData() .matchingXPath(exprSubform) ; }
Auch bei der Verwendung der Klasse XMLNode
muss der Default-Namensraum
mitgegeben werden:
/** * The default namespace has to be declared, * but any alias can be used for it. */ @Test public void hasXFAData_WithDefaultNamespace_XMLNode() throws Exception { String filename = "documentUnderTest.pdf"; String namespaceXCI = "http://www.xfa.org/schema/xci/2.6/"; DefaultNamespace defaultNS = new DefaultNamespace(namespaceXCI); XMLNode aliasFoo = new XMLNode("foo:log/foo:to", "memory", defaultNS); AssertThat.document(filename) .hasXFAData() .withNode(aliasFoo) ; }