1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 #include <sal/config.h>
13 #include <string_view>
15 #include <pdf/XmpMetadata.hxx>
16 #include <tools/XmlWriter.hxx>
22 constexpr const char* constPadding
= " "
30 XmpMetadata::XmpMetadata() = default;
32 void XmpMetadata::write()
34 mpMemoryStream
= std::make_unique
<SvMemoryStream
>(4096 /*Initial*/, 64 /*Resize*/);
37 mpMemoryStream
->WriteOString(std::string_view(reinterpret_cast<char const*>(
38 u8
"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n")));
41 tools::XmlWriter
aXmlWriter(mpMemoryStream
.get());
42 aXmlWriter
.startDocument(2, false);
43 aXmlWriter
.startElement("x"_ostr
, "xmpmeta"_ostr
, "adobe:ns:meta/"_ostr
);
44 aXmlWriter
.startElement("rdf"_ostr
, "RDF"_ostr
,
45 "http://www.w3.org/1999/02/22-rdf-syntax-ns#"_ostr
);
47 // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
50 OString sPdfVersion
= OString::number(mnPDF_A
);
52 aXmlWriter
.startElement("rdf:Description");
53 aXmlWriter
.attribute("rdf:about", ""_ostr
);
54 aXmlWriter
.attribute("xmlns:pdfaid", "http://www.aiim.org/pdfa/ns/id/"_ostr
);
56 aXmlWriter
.startElement("pdfaid:part");
57 aXmlWriter
.content(sPdfVersion
);
58 aXmlWriter
.endElement();
62 aXmlWriter
.startElement("pdfaid:rev");
63 aXmlWriter
.content("2020"_ostr
);
64 aXmlWriter
.endElement();
67 if (!msConformance
.isEmpty())
69 aXmlWriter
.startElement("pdfaid:conformance");
70 aXmlWriter
.content(msConformance
);
71 aXmlWriter
.endElement();
73 aXmlWriter
.endElement();
76 // Dublin Core properties
77 if (!msTitle
.isEmpty() || !msAuthor
.isEmpty() || !msSubject
.isEmpty()
78 || !maContributor
.empty() || !msCoverage
.isEmpty() || !msIdentifier
.isEmpty()
79 || !maPublisher
.empty() || !maRelation
.empty() || !msRights
.isEmpty()
80 || !msSource
.isEmpty() || !msType
.isEmpty())
82 aXmlWriter
.startElement("rdf:Description");
83 aXmlWriter
.attribute("rdf:about", ""_ostr
);
84 aXmlWriter
.attribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"_ostr
);
86 aXmlWriter
.startElement("dc:format");
87 aXmlWriter
.content("application/pdf"_ostr
);
88 aXmlWriter
.endElement();
90 aXmlWriter
.startElement("dc:date");
91 aXmlWriter
.startElement("rdf:Seq");
92 aXmlWriter
.startElement("rdf:li");
93 aXmlWriter
.content(m_sCreateDate
);
94 aXmlWriter
.endElement();
95 aXmlWriter
.endElement();
96 aXmlWriter
.endElement();
98 if (!msTitle
.isEmpty())
100 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
101 aXmlWriter
.startElement("dc:title");
102 aXmlWriter
.startElement("rdf:Alt");
103 aXmlWriter
.startElement("rdf:li");
104 aXmlWriter
.attribute("xml:lang", "x-default"_ostr
);
105 aXmlWriter
.content(msTitle
);
106 aXmlWriter
.endElement();
107 aXmlWriter
.endElement();
108 aXmlWriter
.endElement();
110 if (!msAuthor
.isEmpty())
112 aXmlWriter
.startElement("dc:creator");
113 aXmlWriter
.startElement("rdf:Seq");
114 aXmlWriter
.startElement("rdf:li");
115 aXmlWriter
.content(msAuthor
);
116 aXmlWriter
.endElement();
117 aXmlWriter
.endElement();
118 aXmlWriter
.endElement();
120 if (!msSubject
.isEmpty())
122 aXmlWriter
.startElement("dc:description");
123 aXmlWriter
.startElement("rdf:Alt");
124 aXmlWriter
.startElement("rdf:li");
125 aXmlWriter
.attribute("xml:lang", "x-default"_ostr
);
126 aXmlWriter
.content(msSubject
);
127 aXmlWriter
.endElement();
128 aXmlWriter
.endElement();
129 aXmlWriter
.endElement();
131 if (!maContributor
.empty())
133 aXmlWriter
.startElement("dc:contributor");
134 aXmlWriter
.startElement("rdf:Bag");
135 for (const OString
& rContributor
: maContributor
)
137 aXmlWriter
.startElement("rdf:li");
138 aXmlWriter
.content(rContributor
);
139 aXmlWriter
.endElement();
141 aXmlWriter
.endElement();
142 aXmlWriter
.endElement();
144 if (!msCoverage
.isEmpty())
146 aXmlWriter
.startElement("dc:coverage");
147 aXmlWriter
.content(msCoverage
);
148 aXmlWriter
.endElement();
150 if (!msIdentifier
.isEmpty())
152 aXmlWriter
.startElement("dc:identifier");
153 aXmlWriter
.content(msIdentifier
);
154 aXmlWriter
.endElement();
156 if (!maPublisher
.empty())
158 aXmlWriter
.startElement("dc:publisher");
159 aXmlWriter
.startElement("rdf:Bag");
160 for (const OString
& rPublisher
: maPublisher
)
162 aXmlWriter
.startElement("rdf:li");
163 aXmlWriter
.content(rPublisher
);
164 aXmlWriter
.endElement();
166 aXmlWriter
.endElement();
167 aXmlWriter
.endElement();
169 if (!maRelation
.empty())
171 aXmlWriter
.startElement("dc:relation");
172 aXmlWriter
.startElement("rdf:Bag");
173 for (const OString
& rRelation
: maRelation
)
175 aXmlWriter
.startElement("rdf:li");
176 aXmlWriter
.content(rRelation
);
177 aXmlWriter
.endElement();
179 aXmlWriter
.endElement();
180 aXmlWriter
.endElement();
182 if (!msRights
.isEmpty())
184 aXmlWriter
.startElement("dc:rights");
185 aXmlWriter
.startElement("rdf:Alt");
186 aXmlWriter
.startElement("rdf:li");
187 aXmlWriter
.attribute("xml:lang", "x-default"_ostr
);
188 aXmlWriter
.content(msRights
);
189 aXmlWriter
.endElement();
190 aXmlWriter
.endElement();
191 aXmlWriter
.endElement();
193 if (!msSource
.isEmpty())
195 aXmlWriter
.startElement("dc:source");
196 aXmlWriter
.content(msSource
);
197 aXmlWriter
.endElement();
199 if (!msType
.isEmpty())
201 aXmlWriter
.startElement("dc:type");
202 aXmlWriter
.content(msType
);
203 aXmlWriter
.endElement();
205 aXmlWriter
.endElement();
212 { // tdf#157517 PDF/A extension schema is required
213 aXmlWriter
.startElement("rdf:Description");
214 aXmlWriter
.attribute("rdf:about", ""_ostr
);
215 aXmlWriter
.attribute("xmlns:pdfaExtension",
216 "http://www.aiim.org/pdfa/ns/extension/"_ostr
);
217 aXmlWriter
.attribute("xmlns:pdfaSchema",
218 "http://www.aiim.org/pdfa/ns/schema#"_ostr
);
219 aXmlWriter
.attribute("xmlns:pdfaProperty",
220 "http://www.aiim.org/pdfa/ns/property#"_ostr
);
221 aXmlWriter
.startElement("pdfaExtension:schemas");
222 aXmlWriter
.startElement("rdf:Bag");
223 aXmlWriter
.startElement("rdf:li");
224 aXmlWriter
.attribute("rdf:parseType", "Resource"_ostr
);
225 aXmlWriter
.startElement("pdfaSchema:namespaceURI");
226 aXmlWriter
.content("http://www.aiim.org/pdfua/ns/id/"_ostr
);
227 aXmlWriter
.endElement();
228 aXmlWriter
.startElement("pdfaSchema:prefix");
229 aXmlWriter
.content("pdfuaid"_ostr
);
230 aXmlWriter
.endElement();
231 aXmlWriter
.startElement("pdfaSchema:schema");
232 aXmlWriter
.content("PDF/UA identification schema"_ostr
);
233 aXmlWriter
.endElement();
234 aXmlWriter
.startElement("pdfaSchema:property");
235 aXmlWriter
.startElement("rdf:Seq");
237 aXmlWriter
.startElement("rdf:li");
238 aXmlWriter
.attribute("rdf:parseType", "Resource"_ostr
);
239 aXmlWriter
.startElement("pdfaProperty:category");
240 aXmlWriter
.content("internal"_ostr
);
241 aXmlWriter
.endElement();
242 aXmlWriter
.startElement("pdfaProperty:description");
243 aXmlWriter
.content("PDF/UA version identifier"_ostr
);
244 aXmlWriter
.endElement();
245 aXmlWriter
.startElement("pdfaProperty:name");
246 aXmlWriter
.content("part"_ostr
);
247 aXmlWriter
.endElement();
248 aXmlWriter
.startElement("pdfaProperty:valueType");
249 aXmlWriter
.content("Integer"_ostr
);
250 aXmlWriter
.endElement();
251 aXmlWriter
.endElement(); // rdf:li
253 aXmlWriter
.startElement("rdf:li");
254 aXmlWriter
.attribute("rdf:parseType", "Resource"_ostr
);
255 aXmlWriter
.startElement("pdfaProperty:category");
256 aXmlWriter
.content("internal"_ostr
);
257 aXmlWriter
.endElement();
258 aXmlWriter
.startElement("pdfaProperty:description");
259 aXmlWriter
.content("PDF/UA amendment identifier"_ostr
);
260 aXmlWriter
.endElement();
261 aXmlWriter
.startElement("pdfaProperty:name");
262 aXmlWriter
.content("amd"_ostr
);
263 aXmlWriter
.endElement();
264 aXmlWriter
.startElement("pdfaProperty:valueType");
265 aXmlWriter
.content("Text"_ostr
);
266 aXmlWriter
.endElement();
267 aXmlWriter
.endElement(); // rdf:li
269 aXmlWriter
.startElement("rdf:li");
270 aXmlWriter
.attribute("rdf:parseType", "Resource"_ostr
);
271 aXmlWriter
.startElement("pdfaProperty:category");
272 aXmlWriter
.content("internal"_ostr
);
273 aXmlWriter
.endElement();
274 aXmlWriter
.startElement("pdfaProperty:description");
275 aXmlWriter
.content("PDF/UA corrigenda identifier"_ostr
);
276 aXmlWriter
.endElement();
277 aXmlWriter
.startElement("pdfaProperty:name");
278 aXmlWriter
.content("corr"_ostr
);
279 aXmlWriter
.endElement();
280 aXmlWriter
.startElement("pdfaProperty:valueType");
281 aXmlWriter
.content("Text"_ostr
);
282 aXmlWriter
.endElement();
283 aXmlWriter
.endElement(); // rdf:li
285 aXmlWriter
.endElement(); // rdf:Seq
286 aXmlWriter
.endElement(); // pdfaSchema:property
287 aXmlWriter
.endElement(); // rdf:li
288 aXmlWriter
.endElement(); // rdf:Bag
289 aXmlWriter
.endElement(); // pdfaExtension:schemas
290 aXmlWriter
.endElement(); // rdf:Description
292 OString sPdfUaVersion
= OString::number(mnPDF_UA
);
293 aXmlWriter
.startElement("rdf:Description");
294 aXmlWriter
.attribute("rdf:about", ""_ostr
);
295 aXmlWriter
.attribute("xmlns:pdfuaid", "http://www.aiim.org/pdfua/ns/id/"_ostr
);
297 aXmlWriter
.startElement("pdfuaid:part");
298 aXmlWriter
.content(sPdfUaVersion
);
299 aXmlWriter
.endElement();
303 aXmlWriter
.startElement("pdfuaid:rev");
304 aXmlWriter
.content("2024"_ostr
);
305 aXmlWriter
.endElement();
307 aXmlWriter
.endElement();
311 if (!msProducer
.isEmpty() || !msKeywords
.isEmpty() || !msPDFVersion
.isEmpty())
313 aXmlWriter
.startElement("rdf:Description");
314 aXmlWriter
.attribute("rdf:about", ""_ostr
);
315 aXmlWriter
.attribute("xmlns:pdf", "http://ns.adobe.com/pdf/1.3/"_ostr
);
316 if (!msProducer
.isEmpty())
318 aXmlWriter
.startElement("pdf:Producer");
319 aXmlWriter
.content(msProducer
);
320 aXmlWriter
.endElement();
322 if (!msKeywords
.isEmpty())
324 aXmlWriter
.startElement("pdf:Keywords");
325 aXmlWriter
.content(msKeywords
);
326 aXmlWriter
.endElement();
328 if (!msPDFVersion
.isEmpty())
330 aXmlWriter
.startElement("pdf:PDFVersion");
331 aXmlWriter
.content(msPDFVersion
);
332 aXmlWriter
.endElement();
334 aXmlWriter
.endElement();
338 aXmlWriter
.startElement("rdf:Description");
339 aXmlWriter
.attribute("rdf:about", ""_ostr
);
340 aXmlWriter
.attribute("xmlns:xmp", "http://ns.adobe.com/xap/1.0/"_ostr
);
341 if (!m_sCreatorTool
.isEmpty())
343 aXmlWriter
.startElement("xmp:CreatorTool");
344 aXmlWriter
.content(m_sCreatorTool
);
345 aXmlWriter
.endElement();
347 aXmlWriter
.startElement("xmp:CreateDate");
348 aXmlWriter
.content(m_sCreateDate
);
349 aXmlWriter
.endElement();
351 aXmlWriter
.startElement("xmp:ModifyDate");
352 aXmlWriter
.content(m_sCreateDate
);
353 aXmlWriter
.endElement();
355 aXmlWriter
.startElement("xmp:MetadataDate");
356 aXmlWriter
.content(m_sCreateDate
);
357 aXmlWriter
.endElement();
359 aXmlWriter
.endElement();
360 aXmlWriter
.endElement();
361 aXmlWriter
.endElement();
362 aXmlWriter
.endDocument();
365 // add padding (needed so the metadata can be changed in-place"
366 for (sal_Int32 nSpaces
= 1; nSpaces
<= 21; nSpaces
++)
367 mpMemoryStream
->WriteOString(constPadding
);
369 mpMemoryStream
->WriteOString("<?xpacket end=\"w\"?>\n");
373 sal_uInt64
XmpMetadata::getSize()
377 return mpMemoryStream
->GetSize();
380 const void* XmpMetadata::getData()
384 return mpMemoryStream
->GetData();
389 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */