Set Custom XMP Metadata
xmputil has typed setters for pdf:, xmpMM:, pdfaid: and pdfuaid:. For any
other namespace, take the underlying document with GetGoXmpDocument, get the
model from the matching go-xmp package, set its fields, and marshal as usual. The
example does this for xmpRights:, the rights management namespace.
FindModel and MakeModel are the pair to know. FindModel returns nil if the
packet has no node for that namespace; MakeModel returns the existing model or
creates and attaches an empty one. Use MakeModel when writing.
Doing it
goXmpDoc := xmpDoc.GetGoXmpDocument()
rights, err := xmprights.MakeModel(goXmpDoc)
if err != nil {
return err
}
rights.Certificate = "56c69b3eaf10cff5c3bd8932f0169b"
rights.UsageTerms = xmp.NewAltString("My Custom Usage Terms")
rights.Owner = xmp.NewStringArray("Custom Owner")
rights.WebStatement = "My Custom Web Statement"
if err := rights.SyncToXMP(goXmpDoc); err != nil {
return err
}
data, err := xmpDoc.MarshalIndent("", "\t")
if err != nil {
return err
}
metadataStream, err := core.MakeStream(data, nil)
if err != nil {
return err
}
return pdfWriter.SetCatalogMetadata(metadataStream)The xmputil.Document and the go-xmp document are the same object, so edits made
through the unwrapped handle are picked up by xmpDoc.Marshal and
MarshalIndent. There is no write-back step.
Field types come from the xmp package rather than being plain strings. Multi-value
properties are built with constructors: xmp.NewAltString for a language
alternative such as UsageTerms, xmp.NewStringArray for an unordered bag such as
Owner, xmp.NewStringList for an ordered sequence. Assigning a bare Go string to
one of those fields will not compile.
SyncToXMP is what mirrors a model’s properties into related namespaces. It is a
no-op for xmpRights, but meaningful elsewhere: pdf.PDFInfo.SyncToXMP copies
title, author, subject and copyright into dc: and the creation and modification
dates into xmp:. Calling it after you set fields keeps the packet internally
consistent, and costs nothing when the model does not need it.
Limitations
Cross-namespace sync only fills blanks. pdf.PDFInfo.SyncToXMP copies into dc:
and xmp: only where the target property is empty, so an existing dc:title is
never overwritten from pdf:Title. If those have to agree, set the dc: model
yourself.
Marshaling a document loaded from a file rewrites the whole packet. Namespaces go-xmp does not model are still preserved as nodes, but formatting and property order will not match the original bytes, which matters if something downstream is comparing packets.
core.MakeStream with a nil encoder writes the XML uncompressed, which is what you
want for a metadata stream. SetCatalogMetadata rejects anything that is not a
stream, and clears /Metadata if you pass nil.
Setting XMP does not update the /Info dictionary, and the rights namespace has no
/Info equivalent at all, so there is nothing to mirror in that direction here.
Run the example
The example copies the input into a writer, loads the existing XMP packet or starts
a new one, fills in the four xmpRights properties, and writes the packet to the
output file. Read it back with the
get custom XMP metadata example.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/metadata
go run pdf_set_custom_xmp_metadata.go input.pdf output.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.