Freitag, 25. Januar 2013

Befüllen dynamischer PDF-Formulare mit iText 2.1.7

Zur Bearbeitung von PDF bietet sich unter Java insbesondere die iText-Bibliothek an. Bis Version 2.1.7 konnte iText unter LGPL frei genutzt werden, anschließend wurde das Lizenzmodell geändert. Für die kommerzielle Nutzung späterer Versionen werden Gebühren fällig. Für kleine und mittlere Projekte wird Version 2.1.7 daher noch gern genutzt. Die Schwächen liegen hier vor allem in der Bearbeitung von dynamischen PDF nach XFA-Standard (XML Forms Architecture) ab PDF-Version 1.5.
Zumindest das Befüllen dynamischer Formulare ist jedoch auch mit der angestaubten Bibliothek ohne weiteres möglich. Im Beispiel wurde aus einem XML-Schema ein einfaches Formular generiert:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="modelData" type="modelDataDef"/>

  <xs:complexType name="modelDataDef">
    <xs:sequence>
      <xs:element name="author" type="xs:string"/>
      <xs:element name="cars" type="carsDef"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="carsDef">
    <xs:sequence>
      <xs:element name="car" minOccurs="0" maxOccurs="unbounded">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="manufacturer" type="xs:string"/>
            <xs:element name="type" type="xs:string"/>
            <xs:element name="color" type="xs:string"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:schema>



Mit iText soll das Formular mit schema-konformen Daten befüllt werden:
         

  T. Kröniger
  
    
      Audi
      A4
      Blau
    
    
      VW
      Golf IV
      Rot
    
    
      VW
      Polo
      Gelb
    
    
      BMW
      Z3
      Silber
    
  





Im ersten Schritt kann mit iText der XFA-Content des PDF ausgelesen werden:
        PdfReader reader = new PdfReader(PDF_FORM);
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(PDF_FORM_FILLED));

        Document xfaDomDocument = stamper.getAcroFields().getXfa().getDomDocument();

Nach XFA-Spezifikation müssten die Daten folgendermaßen integriert werden:
   

  
    
      T. Kröniger
      
        
          Audi
          A4
          Blau
        
        
          VW
          Golf IV
          Rot
        
        
          VW
          Polo
          Gelb
        
        
          BMW
          Z3
          Silber
        
      
    
  
  
    
      
      
        
          
          
          
        
      
    
  


Im XFA-Document wird zunächst die "data"-Node ausfindig gemacht und, sofern nicht vorhanden, angelegt.
        NodeList dataNode = xfaDocument.getElementsByTagNameNS(XFA_DATA_SCHEMA, "data");
        if (dataNode.getLength() == 0) {
            return createXfaDataNode();
        }
        return (Element) dataNode.item(0);

    private Element createXfaDataNode() {
        Node parentNode = xfaDocument.getFirstChild();
        while (xfaDocument.getChildNodes().getLength() == 0) {
            parentNode = parentNode.getNextSibling();
        }
        if (parentNode == null) {
            throw new RuntimeException("unable to create datasetNode");
        }

        Element xfaDatasetNode = parentNode.getOwnerDocument().createElement("xfa:datasets");
        xfaDatasetNode.setAttribute("xmlns:xfa", XFA_DATA_SCHEMA);

        Node dataNode = xfaDatasetNode.getOwnerDocument().createElementNS(XFA_DATA_SCHEMA, "xfa:data");
        xfaDatasetNode.appendChild(dataNode);

        parentNode.appendChild(xfaDatasetNode);

        return xfaDatasetNode;
    }

Jetzt können die zu befüllenden Daten im XFA-Stream untergebracht werden:
        Document xmlData = retreiveMyData();
        Node xmlDataNode = xfaDocument.importNode(xmlData.getDocumentElement(), true);
        xfaDataNode.appendChild(xmlDataNode);

Wichtig ist das Setzen der Changed-Flag:
        stamper.getAcroFields().getXfa().setChanged(true);

Über z.B. JAXB-Modellklassen kann somit eine saubere Datenbindung an dynamische PDF-Formulare geschaffen werden. Erfahrungsaustausch zum Thema ist stets willkommen.