Reading and writing xml metadata in PDF/A document

2.5k views Asked by At

I need to read from and write to the XMP metadata of a PDF/A file.

I am using itextsharp 7 and have tried several ways to achieve my goal, without much success. The fields like control:Anzahl_Zeichen_Titel are my target. The following code should do the work, but I can't figure out how exactly.

PdfADocument pdfADocument = new PdfADocument(new PdfReader(Vorlage), new PdfWriter(Ausgabe), new StampingProperties());
XMPMeta xmpMeta = XMPMetaFactory.ParseFromBuffer(pdfADocument.GetXmpMetadata());
XMPProperty test1 = xmpMeta.GetProperty("ftx:ControlData", "control:Anzahl_Zeichen_Vorname");
XMPProperty test2 = xmpMeta.GetProperty("http://www.aiim.org/pdfa/ns/schema#", "ControlData");

When I use the test1 version it shows me an XMPException "Unregistered schema namespace URI". The second one seems to work but the test2 variable is null.

<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c015 84.159810, 2016/09/10-02:41:30        ">
   <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/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:pdf="http://ns.adobe.com/pdf/1.3/"
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
            xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
            xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#"
            xmlns:pdfaType="http://www.aiim.org/pdfa/ns/type#"
            xmlns:pdfaField="http://www.aiim.org/pdfa/ns/field#"
            xmlns:ftx="http://ns.ftx.com/forms/1.0/"
            xmlns:control="http://ns.ftx.com/forms/1.0/controldata/">
         <xmp:CreatorTool>QuarkXPress(R) 8.12</xmp:CreatorTool>
         <xmp:CreateDate>2017-03-14T08:56:49+01:00</xmp:CreateDate>
         <xmp:ModifyDate>2017-04-11T14:35:21+02:00</xmp:ModifyDate>
         <xmp:MetadataDate>2017-04-11T14:35:21+02:00</xmp:MetadataDate>
         <dc:format>application/pdf</dc:format>
         <!-- snip -->
         <ftx:ControlData rdf:parseType="Resource">
            <control:Anzahl_Zeichen_Titel>0</control:Anzahl_Zeichen_Titel>
            <control:Anzahl_Zeichen_Vorname>0</control:Anzahl_Zeichen_Vorname>
            <control:Anzahl_Zeichen_Namenszusatz>0</control:Anzahl_Zeichen_Namenszusatz>
            <control:Anzahl_Zeichen_Hausnummer>0</control:Anzahl_Zeichen_Hausnummer>
            <control:Anzahl_Zeichen_Postleitzahl>0</control:Anzahl_Zeichen_Postleitzahl>
            <control:Anzahl_Zeichen_Wohnsitzlaendercode>0</control:Anzahl_Zeichen_Wohnsitzlaendercode>
            <control:Auftragsnummer_Einsender>0</control:Auftragsnummer_Einsender>
            <control:Formularnummer>10</control:Formularnummer>
            <control:Formularversion>07.2017</control:Formularversion>
         </ftx:ControlData>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>

How must I use the methods to create and read valid data?

2

There are 2 answers

0
Booser On BEST ANSWER

With the help of mkl's answer I found how to read and write the data needed.

private const string NsControlData = "http://ns.ftx.com/forms/1.0/";
private const string NsControl = "http://ns.ftx.com/forms/1.0/controldata/";

// Opens the template file as PDF/A document.
PdfADocument pdfADocument = new PdfADocument(new iText.Kernel.Pdf.PdfReader("input.pdf"), new PdfWriter("output.pdf"), new StampingProperties());

// Reading the metadata from input file.
byte[] xmpMetadata = pdfADocument.GetXmpMetadata(); 

// Parse the metadata
XMPMeta parser = XMPMetaParser.Parse(xmpMetadata, new ParseOptions());

// Read a value
XMPProperty anzahlZeichenTitel = parser.GetStructField(NsControlData, "ControlData", NsControl, "Anzahl_Zeichen_Titel");
// Write a value
parser.SetStructField(NsControlData, "ControlData", NsControl, "Anzahl_Zeichen_Titel", "333");

// writing new file with new metadata.
pdfADocument.SetXmpMetadata(parser);
pdfADocument.GetWriter().Flush();
pdfADocument.Close();
2
mkl On

XMPMeta.getProperty is documented as:

/**
 * The property value getter-methods all take a property specification: the first two parameters
 * are always the top level namespace URI (the &quot;schema&quot; namespace) and the basic name
 * of the property being referenced. See the introductory discussion of path expression usage
 * for more information.
 * <p>
 * All of the functions return an object inherited from <code>PropertyBase</code> or
 * <code>null</code> if the property does not exists. The result object contains the value of
 * the property and option flags describing the property. Arrays and the non-leaf levels of
 * nodes do not have values.
 * <p>
 * See {@link PropertyOptions} for detailed information about the options.
 * <p>
 * This is the simplest property getter, mainly for top level simple properties or after using
 * the path composition functions in XMPPathFactory.
 * 
 * @param schemaNS The namespace URI for the property. May be <code>null</code> or the empty
 *        string if the first component of the propName path contains a namespace prefix. The
 *        URI must be for a registered namespace.
 * @param propName The name of the property. May be a general path expression, must not be
 *        <code>null</code> or the empty string. Using a namespace prefix on the first
 *        component is optional. If present without a schemaNS value then the prefix specifies
 *        the namespace. The prefix must be for a registered namespace. If both a schemaNS URI
 *        and propName prefix are present, they must be corresponding parts of a registered
 *        namespace.
 * @return Returns a <code>XMPProperty</code> containing the value and the options or
 *         <code>null</code> if the property does not exist.
 * @throws XMPException Wraps all errors and exceptions that may occur.
 */
XMPProperty getProperty(String schemaNS, String propName) throws XMPException;

In particular the first parameter has to be a namespace URI, so

XMPProperty test1 = xmpMeta.GetProperty("ftx:ControlData", "control:Anzahl_Zeichen_Vorname");

obviously is wrong.

Your second alternative

XMPProperty test2 = xmpMeta.GetProperty("http://www.aiim.org/pdfa/ns/schema#", "ControlData");

properly has a namespace URI as the first parameter. Unfortunately, though, it's not the namespace URI for the property in question which is http://ns.ftx.com/forms/1.0/.

Thus, you should try

XMPProperty test2 = xmpMeta.GetProperty("http://ns.ftx.com/forms/1.0/", "ControlData");

or (because schemaNS as documented may be null or the empty string if the first component of the propName path contains a namespace prefix)

XMPProperty test2 = xmpMeta.GetProperty(null, "ftx:ControlData");