Get XMP Metadata
The XMP packet lives on the document catalog as a stream, and reading it takes
three steps: fetch the object, unwrap the stream, then parse it into an
xmputil.Document. From there you ask for a namespace model. GetPdfInfo returns
the pdf: model, the one that mirrors the document information dictionary.
Doing it
metadata, ok := reader.GetCatalogMetadata()
if !ok {
return errors.New("no XMP metadata in this document")
}
stream, ok := core.GetStream(metadata)
if !ok {
return fmt.Errorf("catalog metadata is not a stream: %T", metadata)
}
xmpDoc, err := xmputil.LoadDocument(stream.Stream)
if err != nil {
return err
}
pdfInfo, ok := xmpDoc.GetPdfInfo()
if !ok {
return errors.New("no pdf: namespace in the XMP document")
}
fmt.Println(pdfInfo.PdfVersion, pdfInfo.Copyright, pdfInfo.Marked)Both ok results matter, and they mean different things. GetCatalogMetadata
returns false when the catalog has no /Metadata entry at all. GetPdfInfo
returns false when the packet parsed but carries neither a pdf: nor an xmp:
model.
core.GetStream resolves an indirect reference and asserts the stream type; the
decoded XMP bytes are in its Stream field.
Reading the info fields
xmputil.PdfInfo is not model.PdfInfo. It has PdfVersion, Copyright and
Marked as plain Go values, and everything that maps onto a standard info key is
bundled into a single InfoDict of type core.PdfObject. Convert it to get named
fields:
if pdfInfo.InfoDict != nil {
infoDict, err := model.NewPdfInfoFromObject(pdfInfo.InfoDict)
if err != nil {
return err
}
fmt.Printf("%#v\n", infoDict)
}InfoDict is nil when none of the mapped properties were present, so check before
converting.
Limitations
GetPdfInfo falls back to the xmp: base model when there is no pdf: model,
and that fallback only yields three keys: Creator from xmp:CreatorTool,
CreationDate from xmp:CreateDate and ModDate from xmp:ModifyDate. Title,
author, subject and keywords are not read from dc: here. If you need Dublin
Core, go through Document.GetGoXmpDocument and query that model directly.
The values need not match the document’s /Info dictionary. Nothing keeps the two
in sync, and plenty of tools write one and not the other.
LoadDocument parses in lenient mode, so one bad property gets skipped instead of
failing the packet. That also means a field reading as empty may mean malformed
rather than absent.
Run the example
The example prints the pdf: model of the file you pass it, converting InfoDict
back to a model.PdfInfo for display, and reports how long parsing took.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/metadata
go run pdf_get_xmp_pdf_metadata.go input.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.