Bump version to 24.04.3.4
[LibreOffice.git] / sfx2 / source / doc / SfxDocumentMetaData.cxx
blob35f769dd14a5bb289d7965e776c2c8230e5b87f6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <cppuhelper/compbase.hxx>
24 #include <cppuhelper/exc_hlp.hxx>
25 #include <com/sun/star/lang/XServiceInfo.hpp>
26 #include <com/sun/star/document/XDocumentProperties.hpp>
27 #include <com/sun/star/document/XDocumentProperties2.hpp>
28 #include <com/sun/star/lang/XInitialization.hpp>
29 #include <com/sun/star/util/XCloneable.hpp>
30 #include <com/sun/star/util/XModifiable.hpp>
31 #include <com/sun/star/xml/sax/SAXException.hpp>
32 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
34 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
35 #include <com/sun/star/lang/EventObject.hpp>
36 #include <com/sun/star/beans/IllegalTypeException.hpp>
37 #include <com/sun/star/beans/PropertyExistException.hpp>
38 #include <com/sun/star/beans/XPropertySet.hpp>
39 #include <com/sun/star/beans/XPropertySetInfo.hpp>
40 #include <com/sun/star/beans/PropertyAttribute.hpp>
41 #include <com/sun/star/task/ErrorCodeIOException.hpp>
42 #include <com/sun/star/embed/XStorage.hpp>
43 #include <com/sun/star/embed/XTransactedObject.hpp>
44 #include <com/sun/star/embed/ElementModes.hpp>
45 #include <com/sun/star/io/WrongFormatException.hpp>
46 #include <com/sun/star/io/XStream.hpp>
47 #include <com/sun/star/document/XImporter.hpp>
48 #include <com/sun/star/document/XExporter.hpp>
49 #include <com/sun/star/document/XFilter.hpp>
50 #include <com/sun/star/xml/sax/Writer.hpp>
51 #include <com/sun/star/xml/sax/Parser.hpp>
52 #include <com/sun/star/xml/sax/XFastParser.hpp>
53 #include <com/sun/star/xml/dom/DOMException.hpp>
54 #include <com/sun/star/xml/dom/XDocument.hpp>
55 #include <com/sun/star/xml/dom/XElement.hpp>
56 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
57 #include <com/sun/star/xml/dom/NodeType.hpp>
58 #include <com/sun/star/xml/xpath/XPathAPI.hpp>
59 #include <com/sun/star/util/Date.hpp>
60 #include <com/sun/star/util/Time.hpp>
61 #include <com/sun/star/util/DateWithTimezone.hpp>
62 #include <com/sun/star/util/DateTimeWithTimezone.hpp>
63 #include <com/sun/star/util/Duration.hpp>
65 #include <rtl/ustrbuf.hxx>
66 #include <tools/datetime.hxx>
67 #include <comphelper/diagnose_ex.hxx>
68 #include <osl/mutex.hxx>
69 #include <comphelper/fileformat.h>
70 #include <cppuhelper/basemutex.hxx>
71 #include <comphelper/interfacecontainer3.hxx>
72 #include <comphelper/storagehelper.hxx>
73 #include <unotools/mediadescriptor.hxx>
74 #include <comphelper/sequence.hxx>
75 #include <sot/storage.hxx>
76 #include <sfx2/docfile.hxx>
77 #include <sax/tools/converter.hxx>
78 #include <i18nlangtag/languagetag.hxx>
79 #include <optional>
81 #include <algorithm>
82 #include <utility>
83 #include <vector>
84 #include <map>
85 #include <cstring>
86 #include <limits>
89 #include <cppuhelper/implbase.hxx>
90 #include <cppuhelper/supportsservice.hxx>
91 #include <com/sun/star/document/XCompatWriterDocProperties.hpp>
92 #include <com/sun/star/beans/PropertyBag.hpp>
94 /**
95 * This file contains the implementation of the service
96 * com.sun.star.document.DocumentProperties.
97 * This service enables access to the meta-data stored in documents.
98 * Currently, this service only handles documents in ODF format.
100 * The implementation uses an XML DOM to store the properties.
101 * This approach was taken because it allows for preserving arbitrary XML data
102 * in loaded documents, which will be stored unmodified when saving the
103 * document again.
105 * Upon access, some properties are directly read from and updated in the DOM.
106 * Exception: it seems impossible to get notified upon addition of a property
107 * to a com.sun.star.beans.PropertyBag, which is used for storing user-defined
108 * properties; because of this, user-defined properties are updated in the
109 * XML DOM only when storing the document.
110 * Exception 2: when setting certain properties which correspond to attributes
111 * in the XML DOM, we want to remove the corresponding XML element. Detecting
112 * this condition can get messy, so we store all such properties as members,
113 * and update the DOM tree only when storing the document (in
114 * <method>updateUserDefinedAndAttributes</method>).
118 /// anonymous implementation namespace
119 namespace {
121 /// a list of attribute-lists, where attribute means name and content
122 typedef std::vector<std::vector<std::pair<OUString, OUString> > >
123 AttrVector;
125 typedef ::cppu::WeakComponentImplHelper<
126 css::lang::XServiceInfo,
127 css::document::XDocumentProperties2,
128 css::lang::XInitialization,
129 css::util::XCloneable,
130 css::util::XModifiable,
131 css::xml::sax::XSAXSerializable>
132 SfxDocumentMetaData_Base;
134 class SfxDocumentMetaData:
135 private ::cppu::BaseMutex,
136 public SfxDocumentMetaData_Base
138 public:
139 explicit SfxDocumentMetaData(
140 css::uno::Reference< css::uno::XComponentContext > const & context);
141 SfxDocumentMetaData(const SfxDocumentMetaData&) = delete;
142 SfxDocumentMetaData& operator=(const SfxDocumentMetaData&) = delete;
144 // css::lang::XServiceInfo:
145 virtual OUString SAL_CALL getImplementationName() override;
146 virtual sal_Bool SAL_CALL supportsService(
147 const OUString & ServiceName) override;
148 virtual css::uno::Sequence< OUString > SAL_CALL
149 getSupportedServiceNames() override;
151 // css::lang::XComponent:
152 virtual void SAL_CALL dispose() override;
154 // css::document::XDocumentProperties:
155 virtual OUString SAL_CALL getAuthor() override;
156 virtual void SAL_CALL setAuthor(const OUString & the_value) override;
157 virtual OUString SAL_CALL getGenerator() override;
158 virtual void SAL_CALL setGenerator(const OUString & the_value) override;
159 virtual css::util::DateTime SAL_CALL getCreationDate() override;
160 virtual void SAL_CALL setCreationDate(const css::util::DateTime & the_value) override;
161 virtual OUString SAL_CALL getTitle() override;
162 virtual void SAL_CALL setTitle(const OUString & the_value) override;
163 virtual OUString SAL_CALL getSubject() override;
164 virtual void SAL_CALL setSubject(const OUString & the_value) override;
165 virtual OUString SAL_CALL getDescription() override;
166 virtual void SAL_CALL setDescription(const OUString & the_value) override;
167 virtual css::uno::Sequence< OUString > SAL_CALL getKeywords() override;
168 virtual void SAL_CALL setKeywords(
169 const css::uno::Sequence< OUString > & the_value) override;
170 virtual css::lang::Locale SAL_CALL getLanguage() override;
171 virtual void SAL_CALL setLanguage(const css::lang::Locale & the_value) override;
172 virtual OUString SAL_CALL getModifiedBy() override;
173 virtual void SAL_CALL setModifiedBy(const OUString & the_value) override;
174 virtual css::util::DateTime SAL_CALL getModificationDate() override;
175 virtual void SAL_CALL setModificationDate(
176 const css::util::DateTime & the_value) override;
177 virtual OUString SAL_CALL getPrintedBy() override;
178 virtual void SAL_CALL setPrintedBy(const OUString & the_value) override;
179 virtual css::util::DateTime SAL_CALL getPrintDate() override;
180 virtual void SAL_CALL setPrintDate(const css::util::DateTime & the_value) override;
181 virtual OUString SAL_CALL getTemplateName() override;
182 virtual void SAL_CALL setTemplateName(const OUString & the_value) override;
183 virtual OUString SAL_CALL getTemplateURL() override;
184 virtual void SAL_CALL setTemplateURL(const OUString & the_value) override;
185 virtual css::util::DateTime SAL_CALL getTemplateDate() override;
186 virtual void SAL_CALL setTemplateDate(const css::util::DateTime & the_value) override;
187 virtual OUString SAL_CALL getAutoloadURL() override;
188 virtual void SAL_CALL setAutoloadURL(const OUString & the_value) override;
189 virtual ::sal_Int32 SAL_CALL getAutoloadSecs() override;
190 virtual void SAL_CALL setAutoloadSecs(::sal_Int32 the_value) override;
191 virtual OUString SAL_CALL getDefaultTarget() override;
192 virtual void SAL_CALL setDefaultTarget(const OUString & the_value) override;
193 virtual css::uno::Sequence< css::beans::NamedValue > SAL_CALL
194 getDocumentStatistics() override;
195 virtual void SAL_CALL setDocumentStatistics(
196 const css::uno::Sequence< css::beans::NamedValue > & the_value) override;
197 virtual ::sal_Int16 SAL_CALL getEditingCycles() override;
198 virtual void SAL_CALL setEditingCycles(::sal_Int16 the_value) override;
199 virtual ::sal_Int32 SAL_CALL getEditingDuration() override;
200 virtual void SAL_CALL setEditingDuration(::sal_Int32 the_value) override;
201 virtual void SAL_CALL resetUserData(const OUString & the_value) override;
202 virtual css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
203 getUserDefinedProperties() override;
204 virtual void SAL_CALL loadFromStorage(
205 const css::uno::Reference< css::embed::XStorage > & Storage,
206 const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
207 virtual void SAL_CALL loadFromMedium(const OUString & URL,
208 const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
209 virtual void SAL_CALL storeToStorage(
210 const css::uno::Reference< css::embed::XStorage > & Storage,
211 const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
212 virtual void SAL_CALL storeToMedium(const OUString & URL,
213 const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
214 virtual css::uno::Sequence< OUString > SAL_CALL getContributor() override;
215 virtual void SAL_CALL setContributor(const css::uno::Sequence< OUString >& the_value) override;
216 virtual OUString SAL_CALL getCoverage() override;
217 virtual void SAL_CALL setCoverage(const OUString & the_value) override;
218 virtual OUString SAL_CALL getIdentifier() override;
219 virtual void SAL_CALL setIdentifier(const OUString & the_value) override;
220 virtual css::uno::Sequence< OUString > SAL_CALL getPublisher() override;
221 virtual void SAL_CALL setPublisher(const css::uno::Sequence< OUString > & the_value) override;
222 virtual css::uno::Sequence< OUString > SAL_CALL getRelation() override;
223 virtual void SAL_CALL setRelation(const css::uno::Sequence< OUString > & the_value) override;
224 virtual OUString SAL_CALL getRights() override;
225 virtual void SAL_CALL setRights(const OUString & the_value) override;
226 virtual OUString SAL_CALL getSource() override;
227 virtual void SAL_CALL setSource(const OUString& the_value) override;
228 virtual OUString SAL_CALL getType() override;
229 virtual void SAL_CALL setType(const OUString& the_value) override;
232 // css::lang::XInitialization:
233 virtual void SAL_CALL initialize(
234 const css::uno::Sequence< css::uno::Any > & aArguments) override;
236 // css::util::XCloneable:
237 virtual css::uno::Reference<css::util::XCloneable> SAL_CALL createClone() override;
239 // css::util::XModifiable:
240 virtual sal_Bool SAL_CALL isModified( ) override;
241 virtual void SAL_CALL setModified( sal_Bool bModified ) override;
243 // css::util::XModifyBroadcaster:
244 virtual void SAL_CALL addModifyListener(
245 const css::uno::Reference< css::util::XModifyListener > & xListener) override;
246 virtual void SAL_CALL removeModifyListener(
247 const css::uno::Reference< css::util::XModifyListener > & xListener) override;
249 // css::xml::sax::XSAXSerializable
250 virtual void SAL_CALL serialize(
251 const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
252 const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces) override;
254 protected:
255 virtual ~SfxDocumentMetaData() override {}
256 virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) { return new SfxDocumentMetaData( context ); };
257 const css::uno::Reference< css::uno::XComponentContext > m_xContext;
259 /// for notification
260 ::comphelper::OInterfaceContainerHelper3<css::util::XModifyListener> m_NotifyListeners;
261 /// flag: false means not initialized yet, or disposed
262 bool m_isInitialized;
263 /// flag
264 bool m_isModified;
265 /// meta-data DOM tree
266 css::uno::Reference< css::xml::dom::XDocument > m_xDoc;
267 /// meta-data super node in the meta-data DOM tree
268 css::uno::Reference< css::xml::dom::XNode> m_xParent;
269 /// standard meta data (single occurrence)
270 std::map< OUString, css::uno::Reference<css::xml::dom::XNode> >
271 m_meta;
272 /// standard meta data (multiple occurrences)
273 std::map< OUString,
274 std::vector<css::uno::Reference<css::xml::dom::XNode> > > m_metaList;
275 /// user-defined meta data (meta:user-defined) @ATTENTION may be null!
276 css::uno::Reference<css::beans::XPropertyContainer> m_xUserDefined;
277 // now for some meta-data attributes; these are not updated directly in the
278 // DOM because updates (detecting "empty" elements) would be quite messy
279 OUString m_TemplateName;
280 OUString m_TemplateURL;
281 css::util::DateTime m_TemplateDate;
282 OUString m_AutoloadURL;
283 sal_Int32 m_AutoloadSecs;
284 OUString m_DefaultTarget;
286 /// check if we are initialized properly
287 void checkInit() const;
288 /// initialize state from given DOM tree
289 void init(const css::uno::Reference<css::xml::dom::XDocument>& i_xDom);
290 /// update element in DOM tree
291 void updateElement(const OUString & i_name,
292 std::vector<std::pair<OUString, OUString> >* i_pAttrs = nullptr);
293 /// update user-defined meta data and attributes in DOM tree
294 void updateUserDefinedAndAttributes();
295 /// create empty DOM tree (XDocument)
296 css::uno::Reference<css::xml::dom::XDocument> createDOM() const;
297 /// extract base URL (necessary for converting relative links)
298 css::uno::Reference<css::beans::XPropertySet> getURLProperties(
299 const css::uno::Sequence<css::beans::PropertyValue> & i_rMedium) const;
300 /// get text of standard meta data element
301 OUString getMetaText(const char* i_name) const;
302 /// set text of standard meta data element iff not equal to existing text
303 bool setMetaText(const OUString& i_name,
304 const OUString & i_rValue);
305 /// set text of standard meta data element iff not equal to existing text
306 void setMetaTextAndNotify(const OUString& i_name,
307 const OUString & i_rValue);
308 /// get text of standard meta data element's attribute
309 OUString getMetaAttr(const OUString& i_name,
310 const OUString& i_attr) const;
311 /// get text of a list of standard meta data elements (multiple occ.)
312 css::uno::Sequence< OUString > getMetaList(
313 const char* i_name) const;
314 /// set text of a list of standard meta data elements (multiple occ.)
315 bool setMetaList(const OUString& i_name,
316 const css::uno::Sequence< OUString > & i_rValue,
317 AttrVector const*);
318 void createUserDefined();
321 typedef ::cppu::ImplInheritanceHelper< SfxDocumentMetaData, css::document::XCompatWriterDocProperties > CompatWriterDocPropsImpl_BASE;
323 class CompatWriterDocPropsImpl : public CompatWriterDocPropsImpl_BASE
325 OUString msManager;
326 OUString msCategory;
327 OUString msCompany;
328 protected:
329 virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) override { return new CompatWriterDocPropsImpl( context ); };
330 public:
331 explicit CompatWriterDocPropsImpl( css::uno::Reference< css::uno::XComponentContext > const & context) : CompatWriterDocPropsImpl_BASE( context ) {}
333 // XCompatWriterDocPropsImpl
334 virtual OUString SAL_CALL getManager() override { return msManager; }
335 virtual void SAL_CALL setManager( const OUString& _manager ) override { msManager = _manager; }
336 virtual OUString SAL_CALL getCategory() override { return msCategory; }
337 virtual void SAL_CALL setCategory( const OUString& _category ) override { msCategory = _category; }
338 virtual OUString SAL_CALL getCompany() override { return msCompany; }
339 virtual void SAL_CALL setCompany( const OUString& _company ) override { msCompany = _company; }
341 // XServiceInfo
342 virtual OUString SAL_CALL getImplementationName( ) override
344 return "CompatWriterDocPropsImpl";
347 virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override
349 return cppu::supportsService(this, ServiceName);
352 virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override
354 css::uno::Sequence<OUString> aServiceNames { "com.sun.star.writer.DocumentProperties" };
355 return aServiceNames;
359 constexpr OUString sMetaPageCount = u"meta:page-count"_ustr;
360 constexpr OUString sMetaTableCount = u"meta:table-count"_ustr;
361 constexpr OUString sMetaDrawCount = u"meta:draw-count"_ustr;
362 constexpr OUString sMetaImageCount = u"meta:image-count"_ustr;
363 constexpr OUString sMetaObjectCount = u"meta:object-count"_ustr;
364 constexpr OUString sMetaOleObjectCount = u"meta:ole-object-count"_ustr;
365 constexpr OUString sMetaParagraphCount = u"meta:paragraph-count"_ustr;
366 constexpr OUString sMetaWordCount = u"meta:word-count"_ustr;
367 constexpr OUString sMetaCharacterCount = u"meta:character-count"_ustr;
368 constexpr OUString sMetaRowCount = u"meta:row-count"_ustr;
369 constexpr OUString sMetaFrameCount = u"meta:frame-count"_ustr;
370 constexpr OUString sMetaSentenceCount = u"meta:sentence-count"_ustr;
371 constexpr OUString sMetaSyllableCount = u"meta:syllable-count"_ustr;
372 constexpr OUString sMetaNonWhitespaceCharacterCount = u"meta:non-whitespace-character-count"_ustr;
373 constexpr OUString sMetaCellCount = u"meta:cell-count"_ustr;
375 // NB: keep these two arrays in sync!
376 constexpr OUString s_stdStatAttrs[] = {
377 sMetaPageCount,
378 sMetaTableCount,
379 sMetaDrawCount,
380 sMetaImageCount,
381 sMetaObjectCount,
382 sMetaOleObjectCount,
383 sMetaParagraphCount,
384 sMetaWordCount,
385 sMetaCharacterCount,
386 sMetaRowCount,
387 sMetaFrameCount,
388 sMetaSentenceCount,
389 sMetaSyllableCount,
390 sMetaNonWhitespaceCharacterCount,
391 sMetaCellCount
394 // NB: keep these two arrays in sync!
395 const char* s_stdStats[] = {
396 "PageCount",
397 "TableCount",
398 "DrawCount",
399 "ImageCount",
400 "ObjectCount",
401 "OLEObjectCount",
402 "ParagraphCount",
403 "WordCount",
404 "CharacterCount",
405 "RowCount",
406 "FrameCount",
407 "SentenceCount",
408 "SyllableCount",
409 "NonWhitespaceCharacterCount",
410 "CellCount",
411 nullptr
414 const char* s_stdMeta[] = {
415 "meta:generator", // string
416 "dc:title", // string
417 "dc:description", // string
418 "dc:subject", // string
419 "meta:initial-creator", // string
420 "dc:creator", // string
421 "meta:printed-by", // string
422 "meta:creation-date", // dateTime
423 "dc:date", // dateTime
424 "meta:print-date", // dateTime
425 "meta:template", // XLink
426 "meta:auto-reload",
427 "meta:hyperlink-behaviour",
428 "dc:language", // language
429 "meta:editing-cycles", // nonNegativeInteger
430 "meta:editing-duration", // duration
431 "meta:document-statistic", // ... // note: statistic is singular, no s!
432 "dc:coverage",
433 "dc:identifier",
434 "dc:rights",
435 "dc:source",
436 "dc:type",
437 nullptr
440 constexpr OUString sMetaKeyword = u"meta:keyword"_ustr;
441 constexpr OUString sMetaUserDefined = u"meta:user-defined"_ustr;
442 constexpr OUString sDCContributor = u"dc:contributor"_ustr;
443 constexpr OUString sDCPublisher = u"dc:publisher"_ustr;
444 constexpr OUString sDCRelation = u"dc:relation"_ustr;
445 constexpr OUString s_stdMetaList[] {
446 sMetaKeyword, // string*
447 sMetaUserDefined, // ...*
448 sDCContributor, // string*
449 sDCPublisher, // string*
450 sDCRelation, // string*
453 constexpr OUStringLiteral s_nsXLink = u"http://www.w3.org/1999/xlink";
454 constexpr OUString s_nsDC = u"http://purl.org/dc/elements/1.1/"_ustr;
455 constexpr OUString s_nsODF = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"_ustr;
456 constexpr OUString s_nsODFMeta = u"urn:oasis:names:tc:opendocument:xmlns:meta:1.0"_ustr;
457 // constexpr OUStringLiteral s_nsOOo = "http://openoffice.org/2004/office"; // not used (yet?)
459 constexpr OUString s_meta = u"meta.xml"_ustr;
461 bool isValidDate(const css::util::Date & i_rDate)
463 return i_rDate.Month > 0;
466 bool isValidDateTime(const css::util::DateTime & i_rDateTime)
468 return i_rDateTime.Month > 0;
471 std::pair< OUString, OUString >
472 getQualifier(const OUString& nm) {
473 sal_Int32 ix = nm.indexOf(u':');
474 if (ix == -1) {
475 return std::make_pair(OUString(), nm);
476 } else {
477 return std::make_pair(nm.copy(0,ix), nm.copy(ix+1));
481 // get namespace for standard qualified names
482 // NB: only call this with statically known strings!
483 OUString getNameSpace(const OUString& i_qname) noexcept
485 OUString ns;
486 OUString n = getQualifier(i_qname).first;
487 if ( n == "xlink" ) ns = s_nsXLink;
488 if ( n == "dc" ) ns = s_nsDC;
489 if ( n == "office" ) ns = s_nsODF;
490 if ( n == "meta" ) ns = s_nsODFMeta;
491 assert(!ns.isEmpty());
492 return ns;
495 bool
496 textToDateOrDateTime(css::util::Date & io_rd, css::util::DateTime & io_rdt,
497 bool & o_rIsDateTime, std::optional<sal_Int16> & o_rTimeZone,
498 const OUString& i_text) noexcept
500 if (::sax::Converter::parseDateOrDateTime(
501 &io_rd, io_rdt, o_rIsDateTime, &o_rTimeZone, i_text)) {
502 return true;
503 } else {
504 SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
505 return false;
509 // convert string to date/time
510 bool
511 textToDateTime(css::util::DateTime & io_rdt, const OUString& i_text) noexcept
513 if (::sax::Converter::parseDateTime(io_rdt, i_text)) {
514 return true;
515 } else {
516 SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
517 return false;
521 // convert string to date/time with default return value
522 css::util::DateTime
523 textToDateTimeDefault(const OUString& i_text) noexcept
525 css::util::DateTime dt;
526 static_cast<void> (textToDateTime(dt, i_text));
527 // on conversion error: return default value (unchanged)
528 return dt;
531 // convert date to string
532 OUString
533 dateToText(css::util::Date const& i_rd,
534 sal_Int16 const*const pTimeZone) noexcept
536 if (isValidDate(i_rd)) {
537 OUStringBuffer buf;
538 ::sax::Converter::convertDate(buf, i_rd, pTimeZone);
539 return buf.makeStringAndClear();
540 } else {
541 return OUString();
546 // convert date/time to string
547 OUString
548 dateTimeToText(css::util::DateTime const& i_rdt,
549 sal_Int16 const*const pTimeZone = nullptr) noexcept
551 if (isValidDateTime(i_rdt)) {
552 OUStringBuffer buf(32);
553 ::sax::Converter::convertDateTime(buf, i_rdt, pTimeZone, true);
554 return buf.makeStringAndClear();
555 } else {
556 return OUString();
560 // convert string to duration
561 bool
562 textToDuration(css::util::Duration& io_rDur, OUString const& i_rText)
563 noexcept
565 if (::sax::Converter::convertDuration(io_rDur, i_rText)) {
566 return true;
567 } else {
568 SAL_WARN_IF(!i_rText.isEmpty(), "sfx.doc", "Invalid duration: " << i_rText);
569 return false;
573 sal_Int32 textToDuration(OUString const& i_rText) noexcept
575 css::util::Duration d;
576 if (textToDuration(d, i_rText)) {
577 // #i107372#: approximate years/months
578 const sal_Int32 days( (d.Years * 365) + (d.Months * 30) + d.Days );
579 return (days * (24*3600))
580 + (d.Hours * 3600) + (d.Minutes * 60) + d.Seconds;
581 } else {
582 return 0; // default
586 // convert duration to string
587 OUString durationToText(css::util::Duration const& i_rDur) noexcept
589 OUStringBuffer buf;
590 ::sax::Converter::convertDuration(buf, i_rDur);
591 return buf.makeStringAndClear();
594 // convert duration to string
595 OUString durationToText(sal_Int32 i_value) noexcept
597 css::util::Duration ud;
598 ud.Days = static_cast<sal_Int16>(i_value / (24 * 3600));
599 ud.Hours = static_cast<sal_Int16>((i_value % (24 * 3600)) / 3600);
600 ud.Minutes = static_cast<sal_Int16>((i_value % 3600) / 60);
601 ud.Seconds = static_cast<sal_Int16>(i_value % 60);
602 ud.NanoSeconds = 0;
603 return durationToText(ud);
606 // extract base URL (necessary for converting relative links)
607 css::uno::Reference< css::beans::XPropertySet >
608 SfxDocumentMetaData::getURLProperties(
609 const css::uno::Sequence< css::beans::PropertyValue > & i_rMedium) const
611 css::uno::Reference< css::beans::XPropertyBag> xPropArg = css::beans::PropertyBag::createDefault( m_xContext );
612 try {
613 css::uno::Any baseUri;
614 for (const auto& rProp : i_rMedium) {
615 if (rProp.Name == "DocumentBaseURL") {
616 baseUri = rProp.Value;
617 } else if (rProp.Name == "URL") {
618 if (!baseUri.hasValue()) {
619 baseUri = rProp.Value;
621 } else if (rProp.Name == "HierarchicalDocumentName") {
622 xPropArg->addProperty(
623 "StreamRelPath",
624 css::beans::PropertyAttribute::MAYBEVOID,
625 rProp.Value);
628 if (baseUri.hasValue()) {
629 xPropArg->addProperty(
630 "BaseURI", css::beans::PropertyAttribute::MAYBEVOID,
631 baseUri);
633 xPropArg->addProperty("StreamName",
634 css::beans::PropertyAttribute::MAYBEVOID,
635 css::uno::Any(s_meta));
636 } catch (const css::uno::Exception &) {
637 // ignore
639 return css::uno::Reference< css::beans::XPropertySet>(xPropArg,
640 css::uno::UNO_QUERY_THROW);
643 // return the text of the (hopefully unique, i.e., normalize first!) text
644 // node _below_ the given node
645 /// @throws css::uno::RuntimeException
646 OUString
647 getNodeText(const css::uno::Reference<css::xml::dom::XNode>& i_xNode)
649 if (!i_xNode.is())
650 throw css::uno::RuntimeException("SfxDocumentMetaData::getNodeText: argument is null", i_xNode);
651 for (css::uno::Reference<css::xml::dom::XNode> c = i_xNode->getFirstChild();
652 c.is();
653 c = c->getNextSibling()) {
654 if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
655 try {
656 return c->getNodeValue();
657 } catch (const css::xml::dom::DOMException &) { // too big?
658 return OUString();
662 return OUString();
665 OUString
666 SfxDocumentMetaData::getMetaText(const char* i_name) const
667 // throw (css::uno::RuntimeException)
669 checkInit();
671 const OUString name( OUString::createFromAscii(i_name) );
672 assert(m_meta.find(name) != m_meta.end());
673 css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
674 return (xNode.is()) ? getNodeText(xNode) : OUString();
677 bool
678 SfxDocumentMetaData::setMetaText(const OUString& name,
679 const OUString & i_rValue)
680 // throw (css::uno::RuntimeException)
682 checkInit();
684 assert(m_meta.find(name) != m_meta.end());
685 css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
687 try {
688 if (i_rValue.isEmpty()) {
689 if (xNode.is()) { // delete
690 m_xParent->removeChild(xNode);
691 xNode.clear();
692 m_meta[name] = xNode;
693 return true;
694 } else {
695 return false;
697 } else {
698 if (xNode.is()) { // update
699 for (css::uno::Reference<css::xml::dom::XNode> c =
700 xNode->getFirstChild();
701 c.is();
702 c = c->getNextSibling()) {
703 if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
704 if (c->getNodeValue() != i_rValue) {
705 c->setNodeValue(i_rValue);
706 return true;
707 } else {
708 return false;
712 } else { // insert
713 xNode.set(m_xDoc->createElementNS(getNameSpace(name), name),
714 css::uno::UNO_QUERY_THROW);
715 m_xParent->appendChild(xNode);
716 m_meta[name] = xNode;
718 css::uno::Reference<css::xml::dom::XNode> xTextNode(
719 m_xDoc->createTextNode(i_rValue), css::uno::UNO_QUERY_THROW);
720 xNode->appendChild(xTextNode);
721 return true;
723 } catch (const css::xml::dom::DOMException &) {
724 css::uno::Any anyEx = cppu::getCaughtException();
725 throw css::lang::WrappedTargetRuntimeException(
726 "SfxDocumentMetaData::setMetaText: DOM exception",
727 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
731 void
732 SfxDocumentMetaData::setMetaTextAndNotify(const OUString & i_name,
733 const OUString & i_rValue)
734 // throw (css::uno::RuntimeException)
736 ::osl::ClearableMutexGuard g(m_aMutex);
737 if (setMetaText(i_name, i_rValue)) {
738 g.clear();
739 setModified(true);
743 OUString
744 SfxDocumentMetaData::getMetaAttr(const OUString& name, const OUString& i_attr) const
745 // throw (css::uno::RuntimeException)
747 assert(m_meta.find(name) != m_meta.end());
748 css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
749 if (xNode.is()) {
750 css::uno::Reference<css::xml::dom::XElement> xElem(xNode,
751 css::uno::UNO_QUERY_THROW);
752 return xElem->getAttributeNS(getNameSpace(i_attr),
753 getQualifier(i_attr).second);
754 } else {
755 return OUString();
759 css::uno::Sequence< OUString>
760 SfxDocumentMetaData::getMetaList(const char* i_name) const
761 // throw (css::uno::RuntimeException)
763 checkInit();
764 OUString name = OUString::createFromAscii(i_name);
765 assert(m_metaList.find(name) != m_metaList.end());
766 std::vector<css::uno::Reference<css::xml::dom::XNode> > const & vec =
767 m_metaList.find(name)->second;
768 css::uno::Sequence< OUString> ret(vec.size());
769 std::transform(vec.begin(), vec.end(), ret.getArray(),
770 [](const auto& node) { return getNodeText(node); });
771 return ret;
774 bool
775 SfxDocumentMetaData::setMetaList(const OUString& name,
776 const css::uno::Sequence<OUString> & i_rValue,
777 AttrVector const* i_pAttrs)
778 // throw (css::uno::RuntimeException)
780 checkInit();
781 assert((i_pAttrs == nullptr) ||
782 (static_cast<size_t>(i_rValue.getLength()) == i_pAttrs->size()));
784 try {
785 assert(m_metaList.find(name) != m_metaList.end());
786 std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
787 m_metaList[name];
789 // if nothing changed, do nothing
790 // alas, this does not check for permutations, or attributes...
791 if (nullptr == i_pAttrs) {
792 if (static_cast<size_t>(i_rValue.getLength()) == vec.size()) {
793 bool isEqual(true);
794 for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
795 css::uno::Reference<css::xml::dom::XNode> xNode(vec.at(i));
796 if (xNode.is()) {
797 OUString val = getNodeText(xNode);
798 if (val != i_rValue[i]) {
799 isEqual = false;
800 break;
804 if (isEqual) return false;
808 // remove old meta data nodes
810 std::vector<css::uno::Reference<css::xml::dom::XNode> >
811 ::reverse_iterator it(vec.rbegin());
812 try {
813 for ( ;it != vec.rend(); ++it)
815 m_xParent->removeChild(*it);
818 catch (...)
820 // Clean up already removed nodes
821 vec.erase(it.base(), vec.end());
822 throw;
824 vec.clear();
827 // insert new meta data nodes into DOM tree
828 for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
829 css::uno::Reference<css::xml::dom::XElement> xElem(
830 m_xDoc->createElementNS(getNameSpace(name), name),
831 css::uno::UNO_SET_THROW);
832 css::uno::Reference<css::xml::dom::XNode> xNode(xElem,
833 css::uno::UNO_QUERY_THROW);
834 css::uno::Reference<css::xml::dom::XNode> xTextNode(
835 m_xDoc->createTextNode(i_rValue[i]), css::uno::UNO_QUERY_THROW);
836 // set attributes
837 if (i_pAttrs != nullptr) {
838 for (auto const& elem : (*i_pAttrs)[i])
840 xElem->setAttributeNS(getNameSpace(elem.first),
841 elem.first, elem.second);
844 xNode->appendChild(xTextNode);
845 m_xParent->appendChild(xNode);
846 vec.push_back(xNode);
849 return true;
850 } catch (const css::xml::dom::DOMException &) {
851 css::uno::Any anyEx = cppu::getCaughtException();
852 throw css::lang::WrappedTargetRuntimeException(
853 "SfxDocumentMetaData::setMetaList: DOM exception",
854 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
858 // convert property list to string list and attribute list
859 std::pair<css::uno::Sequence< OUString>, AttrVector>
860 propsToStrings(css::uno::Reference<css::beans::XPropertySet> const & i_xPropSet)
862 ::std::vector< OUString > values;
863 AttrVector attrs;
865 css::uno::Reference<css::beans::XPropertySetInfo> xSetInfo
866 = i_xPropSet->getPropertySetInfo();
867 css::uno::Sequence<css::beans::Property> props = xSetInfo->getProperties();
869 for (sal_Int32 i = 0; i < props.getLength(); ++i) {
870 if (props[i].Attributes & css::beans::PropertyAttribute::TRANSIENT) {
871 continue;
873 const OUString name = props[i].Name;
874 css::uno::Any any;
875 try {
876 any = i_xPropSet->getPropertyValue(name);
877 } catch (const css::uno::Exception &) {
878 // ignore
880 const css::uno::Type & type = any.getValueType();
881 std::vector<std::pair<OUString, OUString> > as;
882 as.emplace_back("meta:name", name);
883 static constexpr OUString vt = u"meta:value-type"_ustr;
885 // convert according to type
886 if (type == ::cppu::UnoType<bool>::get()) {
887 bool b = false;
888 any >>= b;
889 OUStringBuffer buf;
890 ::sax::Converter::convertBool(buf, b);
891 values.push_back(buf.makeStringAndClear());
892 as.emplace_back(vt, OUString("boolean"));
893 } else if (type == ::cppu::UnoType< OUString>::get()) {
894 OUString s;
895 any >>= s;
896 values.push_back(s);
897 // #i90847# OOo 2.x does stupid things if value-type="string";
898 // fortunately string is default anyway, so we can just omit it
899 // #i107502#: however, OOo 2.x only reads 4 user-defined without @value-type
900 // => best backward compatibility: first 4 without @value-type, rest with
901 if (4 <= i)
903 as.emplace_back(vt, OUString("string"));
905 } else if (type == ::cppu::UnoType<css::util::DateTime>::get()) {
906 css::util::DateTime dt;
907 any >>= dt;
908 values.push_back(dateTimeToText(dt));
909 as.emplace_back(vt, OUString("date"));
910 } else if (type == ::cppu::UnoType<css::util::Date>::get()) {
911 css::util::Date d;
912 any >>= d;
913 values.push_back(dateToText(d, nullptr));
914 as.emplace_back(vt,OUString("date"));
915 } else if (type == ::cppu::UnoType<css::util::DateTimeWithTimezone>::get()) {
916 css::util::DateTimeWithTimezone dttz;
917 any >>= dttz;
918 values.push_back(dateTimeToText(dttz.DateTimeInTZ, &dttz.Timezone));
919 as.emplace_back(vt, OUString("date"));
920 } else if (type == ::cppu::UnoType<css::util::DateWithTimezone>::get()) {
921 css::util::DateWithTimezone dtz;
922 any >>= dtz;
923 values.push_back(dateToText(dtz.DateInTZ, &dtz.Timezone));
924 as.emplace_back(vt, OUString("date"));
925 } else if (type == ::cppu::UnoType<css::util::Time>::get()) {
926 // #i97029#: replaced by Duration
927 // Time is supported for backward compatibility with OOo 3.x, x<=2
928 css::util::Time ut;
929 any >>= ut;
930 css::util::Duration ud;
931 ud.Hours = ut.Hours;
932 ud.Minutes = ut.Minutes;
933 ud.Seconds = ut.Seconds;
934 ud.NanoSeconds = ut.NanoSeconds;
935 values.push_back(durationToText(ud));
936 as.emplace_back(vt, OUString("time"));
937 } else if (type == ::cppu::UnoType<css::util::Duration>::get()) {
938 css::util::Duration ud;
939 any >>= ud;
940 values.push_back(durationToText(ud));
941 as.emplace_back(vt, OUString("time"));
942 } else if (::cppu::UnoType<double>::get().isAssignableFrom(type)) {
943 // support not just double, but anything that can be converted
944 double d = 0;
945 any >>= d;
946 OUStringBuffer buf;
947 ::sax::Converter::convertDouble(buf, d);
948 values.push_back(buf.makeStringAndClear());
949 as.emplace_back(vt, OUString("float"));
950 } else {
951 SAL_WARN("sfx.doc", "Unsupported property type: " << any.getValueTypeName() );
952 continue;
954 attrs.push_back(as);
957 return std::make_pair(comphelper::containerToSequence(values), attrs);
960 // remove the given element from the DOM, and iff i_pAttrs != 0 insert new one
961 void
962 SfxDocumentMetaData::updateElement(const OUString& name,
963 std::vector<std::pair<OUString, OUString> >* i_pAttrs)
965 try {
966 // remove old element
967 css::uno::Reference<css::xml::dom::XNode> xNode =
968 m_meta.find(name)->second;
969 if (xNode.is()) {
970 m_xParent->removeChild(xNode);
971 xNode.clear();
973 // add new element
974 if (nullptr != i_pAttrs) {
975 css::uno::Reference<css::xml::dom::XElement> xElem(
976 m_xDoc->createElementNS(getNameSpace(name), name),
977 css::uno::UNO_SET_THROW);
978 xNode.set(xElem, css::uno::UNO_QUERY_THROW);
979 // set attributes
980 for (auto const& elem : *i_pAttrs)
982 xElem->setAttributeNS(getNameSpace(elem.first),
983 elem.first, elem.second);
985 m_xParent->appendChild(xNode);
987 m_meta[name] = xNode;
988 } catch (const css::xml::dom::DOMException &) {
989 css::uno::Any anyEx = cppu::getCaughtException();
990 throw css::lang::WrappedTargetRuntimeException(
991 "SfxDocumentMetaData::updateElement: DOM exception",
992 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
996 // update user-defined meta data in DOM tree
997 void SfxDocumentMetaData::updateUserDefinedAndAttributes()
999 createUserDefined();
1000 const css::uno::Reference<css::beans::XPropertySet> xPSet(m_xUserDefined,
1001 css::uno::UNO_QUERY_THROW);
1002 const std::pair<css::uno::Sequence< OUString>, AttrVector>
1003 udStringsAttrs( propsToStrings(xPSet) );
1004 (void) setMetaList("meta:user-defined", udStringsAttrs.first,
1005 &udStringsAttrs.second);
1007 // update elements with attributes
1008 std::vector<std::pair<OUString, OUString> > attributes;
1009 if (!m_TemplateName.isEmpty() || !m_TemplateURL.isEmpty()
1010 || isValidDateTime(m_TemplateDate)) {
1011 attributes.emplace_back("xlink:type", OUString("simple"));
1012 attributes.emplace_back("xlink:actuate", OUString("onRequest"));
1013 attributes.emplace_back("xlink:title", m_TemplateName);
1014 attributes.emplace_back("xlink:href", m_TemplateURL );
1015 if (isValidDateTime(m_TemplateDate)) {
1016 attributes.emplace_back(
1017 "meta:date", dateTimeToText(m_TemplateDate));
1019 updateElement("meta:template", &attributes);
1020 } else {
1021 updateElement("meta:template");
1023 attributes.clear();
1025 if (!m_AutoloadURL.isEmpty() || (0 != m_AutoloadSecs)) {
1026 attributes.emplace_back("xlink:href", m_AutoloadURL );
1027 attributes.emplace_back("meta:delay",
1028 durationToText(m_AutoloadSecs));
1029 updateElement("meta:auto-reload", &attributes);
1030 } else {
1031 updateElement("meta:auto-reload");
1033 attributes.clear();
1035 if (!m_DefaultTarget.isEmpty()) {
1036 attributes.emplace_back(
1037 "office:target-frame-name",
1038 m_DefaultTarget);
1039 // xlink:show: _blank -> new, any other value -> replace
1040 const char* show = m_DefaultTarget == "_blank" ? "new" : "replace";
1041 attributes.emplace_back(
1042 "xlink:show",
1043 OUString::createFromAscii(show));
1044 updateElement("meta:hyperlink-behaviour", &attributes);
1045 } else {
1046 updateElement("meta:hyperlink-behaviour");
1048 attributes.clear();
1051 // create empty DOM tree (XDocument)
1052 css::uno::Reference<css::xml::dom::XDocument>
1053 SfxDocumentMetaData::createDOM() const // throw (css::uno::RuntimeException)
1055 css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder( css::xml::dom::DocumentBuilder::create(m_xContext) );
1056 css::uno::Reference<css::xml::dom::XDocument> xDoc = xBuilder->newDocument();
1057 if (!xDoc.is())
1058 throw css::uno::RuntimeException(
1059 "SfxDocumentMetaData::createDOM: cannot create new document",
1060 *const_cast<SfxDocumentMetaData*>(this));
1061 return xDoc;
1064 void
1065 SfxDocumentMetaData::checkInit() const // throw (css::uno::RuntimeException)
1067 if (!m_isInitialized) {
1068 throw css::uno::RuntimeException(
1069 "SfxDocumentMetaData::checkInit: not initialized",
1070 *const_cast<SfxDocumentMetaData*>(this));
1072 assert(m_xDoc.is() && m_xParent.is());
1075 void extractTagAndNamespaceUri(std::u16string_view aChildNodeName,
1076 std::u16string_view& rTagName, std::u16string_view& rNamespaceURI)
1078 size_t idx = aChildNodeName.find(':');
1079 assert(idx != std::u16string_view::npos);
1080 std::u16string_view aPrefix = aChildNodeName.substr(0, idx);
1081 rTagName = aChildNodeName.substr(idx + 1);
1082 if (aPrefix == u"dc")
1083 rNamespaceURI = s_nsDC;
1084 else if (aPrefix == u"meta")
1085 rNamespaceURI = s_nsODFMeta;
1086 else if (aPrefix == u"office")
1087 rNamespaceURI = s_nsODF;
1088 else
1089 assert(false);
1093 css::uno::Reference<css::xml::dom::XElement> getChildNodeByName(
1094 const css::uno::Reference<css::xml::dom::XNode>& xNode,
1095 std::u16string_view aChildNodeName)
1097 css::uno::Reference< css::xml::dom::XNodeList > xList = xNode->getChildNodes();
1098 if (!xList)
1099 return nullptr;
1100 std::u16string_view aTagName, aNamespaceURI;
1101 extractTagAndNamespaceUri(aChildNodeName, aTagName, aNamespaceURI);
1103 const sal_Int32 nLength(xList->getLength());
1104 for (sal_Int32 a(0); a < nLength; a++)
1106 const css::uno::Reference< css::xml::dom::XElement > xChild(xList->item(a), css::uno::UNO_QUERY);
1107 if (xChild && xChild->getNodeName() == aTagName && aNamespaceURI == xChild->getNamespaceURI())
1108 return xChild;
1110 return nullptr;
1114 std::vector<css::uno::Reference<css::xml::dom::XNode> > getChildNodeListByName(
1115 const css::uno::Reference<css::xml::dom::XNode>& xNode,
1116 std::u16string_view aChildNodeName)
1118 css::uno::Reference< css::xml::dom::XNodeList > xList = xNode->getChildNodes();
1119 if (!xList)
1120 return {};
1121 std::u16string_view aTagName, aNamespaceURI;
1122 extractTagAndNamespaceUri(aChildNodeName, aTagName, aNamespaceURI);
1123 std::vector<css::uno::Reference<css::xml::dom::XNode>> aList;
1124 const sal_Int32 nLength(xList->getLength());
1125 for (sal_Int32 a(0); a < nLength; a++)
1127 const css::uno::Reference< css::xml::dom::XElement > xChild(xList->item(a), css::uno::UNO_QUERY);
1128 if (xChild && xChild->getNodeName() == aTagName && aNamespaceURI == xChild->getNamespaceURI())
1129 aList.push_back(xChild);
1131 return aList;
1134 // initialize state from DOM tree
1135 void SfxDocumentMetaData::init(
1136 const css::uno::Reference<css::xml::dom::XDocument>& i_xDoc)
1138 if (!i_xDoc.is())
1139 throw css::uno::RuntimeException("SfxDocumentMetaData::init: no DOM tree given", *this);
1141 m_isInitialized = false;
1142 m_xDoc = i_xDoc;
1144 // select nodes for standard meta data stuff
1145 // NB: we do not handle the single-XML-file ODF variant, which would
1146 // have the root element office:document.
1147 // The root of such documents must be converted in the importer!
1148 css::uno::Reference<css::xml::dom::XNode> xDocNode(
1149 m_xDoc, css::uno::UNO_QUERY_THROW);
1150 m_xParent.clear();
1151 try {
1152 css::uno::Reference<css::xml::dom::XNode> xChild = getChildNodeByName(xDocNode, u"office:document-meta");
1153 if (xChild)
1154 m_xParent = getChildNodeByName(xChild, u"office:meta");
1155 } catch (const css::uno::Exception &) {
1158 if (!m_xParent.is()) {
1159 // all this create/append stuff may throw DOMException
1160 try {
1161 css::uno::Reference<css::xml::dom::XElement> xRElem;
1162 css::uno::Reference<css::xml::dom::XNode> xNode(
1163 i_xDoc->getFirstChild());
1164 while (xNode.is()) {
1165 if (css::xml::dom::NodeType_ELEMENT_NODE ==xNode->getNodeType())
1167 if ( xNode->getNamespaceURI() == s_nsODF && xNode->getLocalName() == "document-meta" )
1169 xRElem.set(xNode, css::uno::UNO_QUERY_THROW);
1170 break;
1172 else
1174 SAL_INFO("sfx.doc", "SfxDocumentMetaData::init(): "
1175 "deleting unexpected root element: "
1176 << xNode->getLocalName());
1177 i_xDoc->removeChild(xNode);
1178 xNode = i_xDoc->getFirstChild(); // start over
1180 } else {
1181 xNode = xNode->getNextSibling();
1184 if (!xRElem.is()) {
1185 static constexpr OUStringLiteral sOfficeDocumentMeta = u"office:document-meta";
1186 xRElem = i_xDoc->createElementNS(
1187 s_nsODF, sOfficeDocumentMeta);
1188 css::uno::Reference<css::xml::dom::XNode> xRNode(xRElem,
1189 css::uno::UNO_QUERY_THROW);
1190 i_xDoc->appendChild(xRNode);
1192 static constexpr OUStringLiteral sOfficeVersion = u"office:version";
1193 xRElem->setAttributeNS(s_nsODF, sOfficeVersion, "1.0");
1194 // does not exist, otherwise m_xParent would not be null
1195 static constexpr OUStringLiteral sOfficeMeta = u"office:meta";
1196 css::uno::Reference<css::xml::dom::XNode> xParent (
1197 i_xDoc->createElementNS(s_nsODF, sOfficeMeta),
1198 css::uno::UNO_QUERY_THROW);
1199 xRElem->appendChild(xParent);
1200 m_xParent = xParent;
1201 } catch (const css::xml::dom::DOMException &) {
1202 css::uno::Any anyEx = cppu::getCaughtException();
1203 throw css::lang::WrappedTargetRuntimeException(
1204 "SfxDocumentMetaData::init: DOM exception",
1205 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
1210 // select nodes for elements of which we only handle one occurrence
1211 for (const char **pName = s_stdMeta; *pName != nullptr; ++pName) {
1212 OUString name = OUString::createFromAscii(*pName);
1213 // NB: If a document contains more than one occurrence of a
1214 // meta-data element, we arbitrarily pick one of them here.
1215 // We do not remove the others, i.e., when we write the
1216 // document, it will contain the duplicates unchanged.
1217 // The ODF spec says that handling multiple occurrences is
1218 // application-specific.
1219 css::uno::Reference<css::xml::dom::XNode> xNode =
1220 getChildNodeByName(m_xParent, name);
1221 // Do not create an empty element if it is missing;
1222 // for certain elements, such as dateTime, this would be invalid
1223 m_meta[name] = xNode;
1226 // select nodes for elements of which we handle all occurrences
1227 for (const auto & name : s_stdMetaList) {
1228 std::vector<css::uno::Reference<css::xml::dom::XNode> > nodes =
1229 getChildNodeListByName(m_xParent, name);
1230 m_metaList[name] = nodes;
1233 // initialize members corresponding to attributes from DOM nodes
1234 static constexpr OUString sMetaTemplate = u"meta:template"_ustr;
1235 static constexpr OUString sMetaAutoReload = u"meta:auto-reload"_ustr;
1236 static constexpr OUStringLiteral sMetaHyperlinkBehaviour = u"meta:hyperlink-behaviour";
1237 m_TemplateName = getMetaAttr(sMetaTemplate, "xlink:title");
1238 m_TemplateURL = getMetaAttr(sMetaTemplate, "xlink:href");
1239 m_TemplateDate =
1240 textToDateTimeDefault(getMetaAttr(sMetaTemplate, "meta:date"));
1241 m_AutoloadURL = getMetaAttr(sMetaAutoReload, "xlink:href");
1242 m_AutoloadSecs =
1243 textToDuration(getMetaAttr(sMetaAutoReload, "meta:delay"));
1244 m_DefaultTarget =
1245 getMetaAttr(sMetaHyperlinkBehaviour, "office:target-frame-name");
1248 std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
1249 m_metaList[OUString("meta:user-defined")];
1250 m_xUserDefined.clear(); // #i105826#: reset (may be re-initialization)
1251 if ( !vec.empty() )
1253 createUserDefined();
1256 // user-defined meta data: initialize PropertySet from DOM nodes
1257 for (auto const& elem : vec)
1259 css::uno::Reference<css::xml::dom::XElement> xElem(elem,
1260 css::uno::UNO_QUERY_THROW);
1261 css::uno::Any any;
1262 OUString name = xElem->getAttributeNS(s_nsODFMeta, "name");
1263 OUString type = xElem->getAttributeNS(s_nsODFMeta, "value-type");
1264 OUString text = getNodeText(elem);
1265 if ( type == "float" ) {
1266 double d;
1267 if (::sax::Converter::convertDouble(d, text)) {
1268 any <<= d;
1269 } else {
1270 SAL_WARN("sfx.doc", "Invalid float: " << text);
1271 continue;
1273 } else if ( type == "date" ) {
1274 bool isDateTime;
1275 css::util::Date d;
1276 css::util::DateTime dt;
1277 std::optional<sal_Int16> nTimeZone;
1278 if (textToDateOrDateTime(d, dt, isDateTime, nTimeZone, text)) {
1279 if (isDateTime) {
1280 if (nTimeZone) {
1281 any <<= css::util::DateTimeWithTimezone(dt,
1282 *nTimeZone);
1283 } else {
1284 any <<= dt;
1286 } else {
1287 if (nTimeZone) {
1288 any <<= css::util::DateWithTimezone(d, *nTimeZone);
1289 } else {
1290 any <<= d;
1293 } else {
1294 SAL_WARN("sfx.doc", "Invalid date: " << text);
1295 continue;
1297 } else if ( type == "time" ) {
1298 css::util::Duration ud;
1299 if (textToDuration(ud, text)) {
1300 any <<= ud;
1301 } else {
1302 SAL_WARN("sfx.doc", "Invalid time: " << text);
1303 continue;
1305 } else if ( type == "boolean" ) {
1306 bool b;
1307 if (::sax::Converter::convertBool(b, text)) {
1308 any <<= b;
1309 } else {
1310 SAL_WARN("sfx.doc", "Invalid boolean: " << text);
1311 continue;
1313 } else { // default
1314 any <<= text;
1316 try {
1317 m_xUserDefined->addProperty(name,
1318 css::beans::PropertyAttribute::REMOVABLE, any);
1319 } catch (const css::beans::PropertyExistException &) {
1320 SAL_WARN("sfx.doc", "Duplicate: " << name);
1321 // ignore; duplicate
1322 } catch (const css::beans::IllegalTypeException &) {
1323 SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal type: " << name);
1324 } catch (const css::lang::IllegalArgumentException &) {
1325 SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal arg: " << name);
1329 m_isModified = false;
1330 m_isInitialized = true;
1334 SfxDocumentMetaData::SfxDocumentMetaData(
1335 css::uno::Reference< css::uno::XComponentContext > const & context)
1336 : BaseMutex()
1337 , SfxDocumentMetaData_Base(m_aMutex)
1338 , m_xContext(context)
1339 , m_NotifyListeners(m_aMutex)
1340 , m_isInitialized(false)
1341 , m_isModified(false)
1342 , m_AutoloadSecs(0)
1344 assert(context.is());
1345 assert(context->getServiceManager().is());
1346 init(createDOM());
1349 // com.sun.star.uno.XServiceInfo:
1350 OUString SAL_CALL
1351 SfxDocumentMetaData::getImplementationName()
1353 return "SfxDocumentMetaData";
1356 sal_Bool SAL_CALL
1357 SfxDocumentMetaData::supportsService(OUString const & serviceName)
1359 return cppu::supportsService(this, serviceName);
1362 css::uno::Sequence< OUString > SAL_CALL
1363 SfxDocumentMetaData::getSupportedServiceNames()
1365 css::uno::Sequence< OUString > s { "com.sun.star.document.DocumentProperties" };
1366 return s;
1370 // css::lang::XComponent:
1371 void SAL_CALL SfxDocumentMetaData::dispose()
1373 ::osl::MutexGuard g(m_aMutex);
1374 if (!m_isInitialized) {
1375 return;
1377 WeakComponentImplHelperBase::dispose(); // superclass
1378 m_NotifyListeners.disposeAndClear(css::lang::EventObject(
1379 getXWeak()));
1380 m_isInitialized = false;
1381 m_meta.clear();
1382 m_metaList.clear();
1383 m_xParent.clear();
1384 m_xDoc.clear();
1385 m_xUserDefined.clear();
1389 // css::document::XDocumentProperties:
1390 OUString SAL_CALL
1391 SfxDocumentMetaData::getAuthor()
1393 ::osl::MutexGuard g(m_aMutex);
1394 return getMetaText("meta:initial-creator");
1397 void SAL_CALL SfxDocumentMetaData::setAuthor(const OUString & the_value)
1399 setMetaTextAndNotify("meta:initial-creator", the_value);
1403 OUString SAL_CALL
1404 SfxDocumentMetaData::getGenerator()
1406 ::osl::MutexGuard g(m_aMutex);
1407 return getMetaText("meta:generator");
1410 void SAL_CALL
1411 SfxDocumentMetaData::setGenerator(const OUString & the_value)
1413 setMetaTextAndNotify("meta:generator", the_value);
1416 css::util::DateTime SAL_CALL
1417 SfxDocumentMetaData::getCreationDate()
1419 ::osl::MutexGuard g(m_aMutex);
1420 return textToDateTimeDefault(getMetaText("meta:creation-date"));
1423 void SAL_CALL
1424 SfxDocumentMetaData::setCreationDate(const css::util::DateTime & the_value)
1426 setMetaTextAndNotify("meta:creation-date", dateTimeToText(the_value));
1429 OUString SAL_CALL
1430 SfxDocumentMetaData::getTitle()
1432 ::osl::MutexGuard g(m_aMutex);
1433 return getMetaText("dc:title");
1436 void SAL_CALL SfxDocumentMetaData::setTitle(const OUString & the_value)
1438 setMetaTextAndNotify("dc:title", the_value);
1441 OUString SAL_CALL
1442 SfxDocumentMetaData::getSubject()
1444 ::osl::MutexGuard g(m_aMutex);
1445 return getMetaText("dc:subject");
1448 void SAL_CALL
1449 SfxDocumentMetaData::setSubject(const OUString & the_value)
1451 setMetaTextAndNotify("dc:subject", the_value);
1454 OUString SAL_CALL
1455 SfxDocumentMetaData::getDescription()
1457 ::osl::MutexGuard g(m_aMutex);
1458 return getMetaText("dc:description");
1461 void SAL_CALL
1462 SfxDocumentMetaData::setDescription(const OUString & the_value)
1464 setMetaTextAndNotify("dc:description", the_value);
1467 css::uno::Sequence< OUString >
1468 SAL_CALL SfxDocumentMetaData::getKeywords()
1470 ::osl::MutexGuard g(m_aMutex);
1471 return getMetaList("meta:keyword");
1474 void SAL_CALL
1475 SfxDocumentMetaData::setKeywords(
1476 const css::uno::Sequence< OUString > & the_value)
1478 ::osl::ClearableMutexGuard g(m_aMutex);
1479 if (setMetaList("meta:keyword", the_value, nullptr)) {
1480 g.clear();
1481 setModified(true);
1485 // css::document::XDocumentProperties2
1486 css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getContributor()
1488 ::osl::MutexGuard g(m_aMutex);
1489 return getMetaList("dc:contributor");
1492 void SAL_CALL SfxDocumentMetaData::setContributor(const css::uno::Sequence<OUString>& the_value)
1494 ::osl::ClearableMutexGuard g(m_aMutex);
1495 if (setMetaList("dc:contributor", the_value, nullptr))
1497 g.clear();
1498 setModified(true);
1502 OUString SAL_CALL SfxDocumentMetaData::getCoverage()
1504 ::osl::MutexGuard g(m_aMutex);
1505 return getMetaText("dc:coverage");
1508 void SAL_CALL SfxDocumentMetaData::setCoverage(const OUString& the_value)
1510 setMetaTextAndNotify("dc:coverage", the_value);
1513 OUString SAL_CALL SfxDocumentMetaData::getIdentifier()
1515 ::osl::MutexGuard g(m_aMutex);
1516 return getMetaText("dc:identifier");
1519 void SAL_CALL SfxDocumentMetaData::setIdentifier(const OUString& the_value)
1521 setMetaTextAndNotify("dc:identifier", the_value);
1524 css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getPublisher()
1526 ::osl::MutexGuard g(m_aMutex);
1527 return getMetaList("dc:publisher");
1530 void SAL_CALL SfxDocumentMetaData::setPublisher(const css::uno::Sequence<OUString>& the_value)
1532 ::osl::ClearableMutexGuard g(m_aMutex);
1533 if (setMetaList("dc:publisher", the_value, nullptr))
1535 g.clear();
1536 setModified(true);
1540 css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getRelation()
1542 ::osl::MutexGuard g(m_aMutex);
1543 return getMetaList("dc:relation");
1546 void SAL_CALL SfxDocumentMetaData::setRelation(const css::uno::Sequence<OUString>& the_value)
1548 ::osl::ClearableMutexGuard g(m_aMutex);
1549 if (setMetaList("dc:relation", the_value, nullptr))
1551 g.clear();
1552 setModified(true);
1556 OUString SAL_CALL SfxDocumentMetaData::getRights()
1558 ::osl::MutexGuard g(m_aMutex);
1559 return getMetaText("dc:rights");
1562 void SAL_CALL SfxDocumentMetaData::setRights(const OUString& the_value)
1564 setMetaTextAndNotify("dc:rights", the_value);
1567 OUString SAL_CALL SfxDocumentMetaData::getSource()
1569 ::osl::MutexGuard g(m_aMutex);
1570 return getMetaText("dc:source");
1573 void SAL_CALL SfxDocumentMetaData::setSource(const OUString& the_value)
1575 setMetaTextAndNotify("dc:source", the_value);
1578 OUString SAL_CALL SfxDocumentMetaData::getType()
1580 ::osl::MutexGuard g(m_aMutex);
1581 return getMetaText("dc:type");
1584 void SAL_CALL SfxDocumentMetaData::setType(const OUString& the_value)
1586 setMetaTextAndNotify("dc:type", the_value);
1589 css::lang::Locale SAL_CALL
1590 SfxDocumentMetaData::getLanguage()
1592 ::osl::MutexGuard g(m_aMutex);
1593 css::lang::Locale loc( LanguageTag::convertToLocale( getMetaText("dc:language"), false));
1594 return loc;
1597 void SAL_CALL
1598 SfxDocumentMetaData::setLanguage(const css::lang::Locale & the_value)
1600 OUString text( LanguageTag::convertToBcp47( the_value, false));
1601 setMetaTextAndNotify("dc:language", text);
1604 OUString SAL_CALL
1605 SfxDocumentMetaData::getModifiedBy()
1607 ::osl::MutexGuard g(m_aMutex);
1608 return getMetaText("dc:creator");
1611 void SAL_CALL
1612 SfxDocumentMetaData::setModifiedBy(const OUString & the_value)
1614 setMetaTextAndNotify("dc:creator", the_value);
1617 css::util::DateTime SAL_CALL
1618 SfxDocumentMetaData::getModificationDate()
1620 ::osl::MutexGuard g(m_aMutex);
1621 return textToDateTimeDefault(getMetaText("dc:date"));
1624 void SAL_CALL
1625 SfxDocumentMetaData::setModificationDate(const css::util::DateTime & the_value)
1627 setMetaTextAndNotify("dc:date", dateTimeToText(the_value));
1630 OUString SAL_CALL
1631 SfxDocumentMetaData::getPrintedBy()
1633 ::osl::MutexGuard g(m_aMutex);
1634 return getMetaText("meta:printed-by");
1637 void SAL_CALL
1638 SfxDocumentMetaData::setPrintedBy(const OUString & the_value)
1640 setMetaTextAndNotify("meta:printed-by", the_value);
1643 css::util::DateTime SAL_CALL
1644 SfxDocumentMetaData::getPrintDate()
1646 ::osl::MutexGuard g(m_aMutex);
1647 return textToDateTimeDefault(getMetaText("meta:print-date"));
1650 void SAL_CALL
1651 SfxDocumentMetaData::setPrintDate(const css::util::DateTime & the_value)
1653 setMetaTextAndNotify("meta:print-date", dateTimeToText(the_value));
1656 OUString SAL_CALL
1657 SfxDocumentMetaData::getTemplateName()
1659 ::osl::MutexGuard g(m_aMutex);
1660 checkInit();
1661 return m_TemplateName;
1664 void SAL_CALL
1665 SfxDocumentMetaData::setTemplateName(const OUString & the_value)
1667 ::osl::ClearableMutexGuard g(m_aMutex);
1668 checkInit();
1669 if (m_TemplateName != the_value) {
1670 m_TemplateName = the_value;
1671 g.clear();
1672 setModified(true);
1676 OUString SAL_CALL
1677 SfxDocumentMetaData::getTemplateURL()
1679 ::osl::MutexGuard g(m_aMutex);
1680 checkInit();
1681 return m_TemplateURL;
1684 void SAL_CALL
1685 SfxDocumentMetaData::setTemplateURL(const OUString & the_value)
1687 ::osl::ClearableMutexGuard g(m_aMutex);
1688 checkInit();
1689 if (m_TemplateURL != the_value) {
1690 m_TemplateURL = the_value;
1691 g.clear();
1692 setModified(true);
1696 css::util::DateTime SAL_CALL
1697 SfxDocumentMetaData::getTemplateDate()
1699 ::osl::MutexGuard g(m_aMutex);
1700 checkInit();
1701 return m_TemplateDate;
1704 void SAL_CALL
1705 SfxDocumentMetaData::setTemplateDate(const css::util::DateTime & the_value)
1707 ::osl::ClearableMutexGuard g(m_aMutex);
1708 checkInit();
1709 if (m_TemplateDate != the_value) {
1710 m_TemplateDate = the_value;
1711 g.clear();
1712 setModified(true);
1716 OUString SAL_CALL
1717 SfxDocumentMetaData::getAutoloadURL()
1719 ::osl::MutexGuard g(m_aMutex);
1720 checkInit();
1721 return m_AutoloadURL;
1724 void SAL_CALL
1725 SfxDocumentMetaData::setAutoloadURL(const OUString & the_value)
1727 ::osl::ClearableMutexGuard g(m_aMutex);
1728 checkInit();
1729 if (m_AutoloadURL != the_value) {
1730 m_AutoloadURL = the_value;
1731 g.clear();
1732 setModified(true);
1736 ::sal_Int32 SAL_CALL
1737 SfxDocumentMetaData::getAutoloadSecs()
1739 ::osl::MutexGuard g(m_aMutex);
1740 checkInit();
1741 return m_AutoloadSecs;
1744 void SAL_CALL
1745 SfxDocumentMetaData::setAutoloadSecs(::sal_Int32 the_value)
1747 if (the_value < 0)
1748 throw css::lang::IllegalArgumentException(
1749 "SfxDocumentMetaData::setAutoloadSecs: argument is negative",
1750 *this, 0);
1751 ::osl::ClearableMutexGuard g(m_aMutex);
1752 checkInit();
1753 if (m_AutoloadSecs != the_value) {
1754 m_AutoloadSecs = the_value;
1755 g.clear();
1756 setModified(true);
1760 OUString SAL_CALL
1761 SfxDocumentMetaData::getDefaultTarget()
1763 ::osl::MutexGuard g(m_aMutex);
1764 checkInit();
1765 return m_DefaultTarget;
1768 void SAL_CALL
1769 SfxDocumentMetaData::setDefaultTarget(const OUString & the_value)
1771 ::osl::ClearableMutexGuard g(m_aMutex);
1772 checkInit();
1773 if (m_DefaultTarget != the_value) {
1774 m_DefaultTarget = the_value;
1775 g.clear();
1776 setModified(true);
1780 css::uno::Sequence< css::beans::NamedValue > SAL_CALL
1781 SfxDocumentMetaData::getDocumentStatistics()
1783 ::osl::MutexGuard g(m_aMutex);
1784 checkInit();
1785 ::std::vector<css::beans::NamedValue> stats;
1786 for (size_t i = 0; s_stdStats[i] != nullptr; ++i) {
1787 OUString text = getMetaAttr("meta:document-statistic", s_stdStatAttrs[i]);
1788 if (text.isEmpty()) continue;
1789 css::beans::NamedValue stat;
1790 stat.Name = OUString::createFromAscii(s_stdStats[i]);
1791 sal_Int32 val;
1792 css::uno::Any any;
1793 if (!::sax::Converter::convertNumber(val, text, 0) || (val < 0)) {
1794 val = 0;
1795 SAL_WARN("sfx.doc", "Invalid number: " << text);
1797 any <<= val;
1798 stat.Value = any;
1799 stats.push_back(stat);
1802 return ::comphelper::containerToSequence(stats);
1805 void SAL_CALL
1806 SfxDocumentMetaData::setDocumentStatistics(
1807 const css::uno::Sequence< css::beans::NamedValue > & the_value)
1810 osl::MutexGuard g(m_aMutex);
1811 checkInit();
1812 std::vector<std::pair<OUString, OUString> > attributes;
1813 for (const auto& rValue : the_value) {
1814 const OUString name = rValue.Name;
1815 // inefficiently search for matching attribute
1816 for (size_t j = 0; s_stdStats[j] != nullptr; ++j) {
1817 if (name.equalsAscii(s_stdStats[j])) {
1818 const css::uno::Any any = rValue.Value;
1819 sal_Int32 val = 0;
1820 if (any >>= val) {
1821 attributes.emplace_back(s_stdStatAttrs[j],
1822 OUString::number(val));
1824 else {
1825 SAL_WARN("sfx.doc", "Invalid statistic: " << name);
1827 break;
1831 updateElement("meta:document-statistic", &attributes);
1833 setModified(true);
1836 ::sal_Int16 SAL_CALL
1837 SfxDocumentMetaData::getEditingCycles()
1839 ::osl::MutexGuard g(m_aMutex);
1840 OUString text = getMetaText("meta:editing-cycles");
1841 sal_Int32 ret;
1842 if (::sax::Converter::convertNumber(ret, text,
1843 0, std::numeric_limits<sal_Int16>::max())) {
1844 return static_cast<sal_Int16>(ret);
1845 } else {
1846 return 0;
1850 void SAL_CALL
1851 SfxDocumentMetaData::setEditingCycles(::sal_Int16 the_value)
1853 if (the_value < 0)
1854 throw css::lang::IllegalArgumentException(
1855 "SfxDocumentMetaData::setEditingCycles: argument is negative",
1856 *this, 0);
1857 setMetaTextAndNotify("meta:editing-cycles", OUString::number(the_value));
1860 ::sal_Int32 SAL_CALL
1861 SfxDocumentMetaData::getEditingDuration()
1863 ::osl::MutexGuard g(m_aMutex);
1864 return textToDuration(getMetaText("meta:editing-duration"));
1867 void SAL_CALL
1868 SfxDocumentMetaData::setEditingDuration(::sal_Int32 the_value)
1870 if (the_value < 0)
1871 throw css::lang::IllegalArgumentException(
1872 "SfxDocumentMetaData::setEditingDuration: argument is negative",
1873 *this, 0);
1874 setMetaTextAndNotify("meta:editing-duration", durationToText(the_value));
1877 void SAL_CALL
1878 SfxDocumentMetaData::resetUserData(const OUString & the_value)
1880 ::osl::ClearableMutexGuard g(m_aMutex);
1882 bool bModified( false );
1883 bModified |= setMetaText("meta:initial-creator", the_value);
1884 ::DateTime now( ::DateTime::SYSTEM );
1885 css::util::DateTime uDT(now.GetUNODateTime());
1886 bModified |= setMetaText("meta:creation-date", dateTimeToText(uDT));
1887 bModified |= setMetaText("dc:creator", OUString());
1888 bModified |= setMetaText("meta:printed-by", OUString());
1889 bModified |= setMetaText("dc:date", dateTimeToText(css::util::DateTime()));
1890 bModified |= setMetaText("meta:print-date",
1891 dateTimeToText(css::util::DateTime()));
1892 bModified |= setMetaText("meta:editing-duration", durationToText(0));
1893 bModified |= setMetaText("meta:editing-cycles",
1894 "1");
1896 if (bModified) {
1897 g.clear();
1898 setModified(true);
1903 css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
1904 SfxDocumentMetaData::getUserDefinedProperties()
1906 ::osl::MutexGuard g(m_aMutex);
1907 checkInit();
1908 createUserDefined();
1909 return m_xUserDefined;
1913 void SAL_CALL
1914 SfxDocumentMetaData::loadFromStorage(
1915 const css::uno::Reference< css::embed::XStorage > & xStorage,
1916 const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1918 if (!xStorage.is())
1919 throw css::lang::IllegalArgumentException("SfxDocumentMetaData::loadFromStorage: argument is null", *this, 0);
1920 ::osl::MutexGuard g(m_aMutex);
1922 // open meta data file
1923 css::uno::Reference<css::io::XStream> xStream(
1924 xStorage->openStreamElement(
1925 s_meta,
1926 css::embed::ElementModes::READ) );
1927 if (!xStream.is()) throw css::uno::RuntimeException();
1928 css::uno::Reference<css::io::XInputStream> xInStream =
1929 xStream->getInputStream();
1930 if (!xInStream.is()) throw css::uno::RuntimeException();
1932 // create DOM parser service
1933 css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
1934 m_xContext->getServiceManager());
1935 css::xml::sax::InputSource input;
1936 input.aInputStream = xInStream;
1938 sal_uInt64 version = SotStorage::GetVersion( xStorage );
1939 // Oasis is also the default (0)
1940 bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
1941 const char *pServiceName = bOasis
1942 ? "com.sun.star.document.XMLOasisMetaImporter"
1943 : "com.sun.star.document.XMLMetaImporter";
1945 // set base URL
1946 css::uno::Reference<css::beans::XPropertySet> xPropArg =
1947 getURLProperties(Medium);
1948 try {
1949 xPropArg->getPropertyValue("BaseURI")
1950 >>= input.sSystemId;
1951 input.sSystemId += OUString::Concat("/") + s_meta;
1952 } catch (const css::uno::Exception &) {
1953 input.sSystemId = s_meta;
1955 css::uno::Sequence< css::uno::Any > args{ css::uno::Any(xPropArg) };
1957 // the underlying SvXMLImport implements XFastParser, XImporter, XFastDocumentHandler
1958 css::uno::Reference<XInterface> xFilter =
1959 xMsf->createInstanceWithArgumentsAndContext(
1960 OUString::createFromAscii(pServiceName), args, m_xContext);
1961 assert(xFilter);
1962 css::uno::Reference<css::xml::sax::XFastParser> xFastParser(xFilter, css::uno::UNO_QUERY);
1963 css::uno::Reference<css::document::XImporter> xImp(xFilter, css::uno::UNO_QUERY_THROW);
1964 xImp->setTargetDocument(css::uno::Reference<css::lang::XComponent>(this));
1965 try {
1966 if (xFastParser)
1967 xFastParser->parseStream(input);
1968 else
1970 css::uno::Reference<css::xml::sax::XDocumentHandler> xDocHandler(xFilter, css::uno::UNO_QUERY_THROW);
1971 css::uno::Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(m_xContext);
1972 xParser->setDocumentHandler(xDocHandler);
1973 xParser->parseStream(input);
1975 } catch (const css::xml::sax::SAXException &) {
1976 throw css::io::WrongFormatException(
1977 "SfxDocumentMetaData::loadFromStorage:"
1978 " XML parsing exception", *this);
1980 // NB: the implementation of XMLOasisMetaImporter calls initialize
1981 checkInit();
1984 void SAL_CALL
1985 SfxDocumentMetaData::storeToStorage(
1986 const css::uno::Reference< css::embed::XStorage > & xStorage,
1987 const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1989 if (!xStorage.is())
1990 throw css::lang::IllegalArgumentException(
1991 "SfxDocumentMetaData::storeToStorage: argument is null", *this, 0);
1992 ::osl::MutexGuard g(m_aMutex);
1993 checkInit();
1995 // update user-defined meta data in DOM tree
1996 // updateUserDefinedAndAttributes(); // this will be done in serialize!
1998 // write into storage
1999 css::uno::Reference<css::io::XStream> xStream =
2000 xStorage->openStreamElement(s_meta,
2001 css::embed::ElementModes::WRITE
2002 | css::embed::ElementModes::TRUNCATE);
2003 if (!xStream.is()) throw css::uno::RuntimeException();
2004 css::uno::Reference< css::beans::XPropertySet > xStreamProps(xStream,
2005 css::uno::UNO_QUERY_THROW);
2006 xStreamProps->setPropertyValue(
2007 "MediaType",
2008 css::uno::Any(OUString("text/xml")));
2009 xStreamProps->setPropertyValue(
2010 "Compressed",
2011 css::uno::Any(false));
2012 xStreamProps->setPropertyValue(
2013 "UseCommonStoragePasswordEncryption",
2014 css::uno::Any(false));
2015 css::uno::Reference<css::io::XOutputStream> xOutStream =
2016 xStream->getOutputStream();
2017 if (!xOutStream.is()) throw css::uno::RuntimeException();
2018 css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
2019 m_xContext->getServiceManager());
2020 css::uno::Reference<css::xml::sax::XWriter> xSaxWriter(
2021 css::xml::sax::Writer::create(m_xContext));
2022 xSaxWriter->setOutputStream(xOutStream);
2024 const sal_uInt64 version = SotStorage::GetVersion( xStorage );
2025 // Oasis is also the default (0)
2026 const bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
2027 const char *pServiceName = bOasis
2028 ? "com.sun.star.document.XMLOasisMetaExporter"
2029 : "com.sun.star.document.XMLMetaExporter";
2031 // set base URL
2032 css::uno::Reference<css::beans::XPropertySet> xPropArg =
2033 getURLProperties(Medium);
2034 css::uno::Sequence< css::uno::Any > args{ css::uno::Any(xSaxWriter), css::uno::Any(xPropArg) };
2036 css::uno::Reference<css::document::XExporter> xExp(
2037 xMsf->createInstanceWithArgumentsAndContext(
2038 OUString::createFromAscii(pServiceName), args, m_xContext),
2039 css::uno::UNO_QUERY_THROW);
2040 xExp->setSourceDocument(css::uno::Reference<css::lang::XComponent>(this));
2041 css::uno::Reference<css::document::XFilter> xFilter(xExp,
2042 css::uno::UNO_QUERY_THROW);
2043 if (!xFilter->filter(css::uno::Sequence< css::beans::PropertyValue >())) {
2044 throw css::io::IOException(
2045 "SfxDocumentMetaData::storeToStorage: cannot filter", *this);
2047 css::uno::Reference<css::embed::XTransactedObject> xTransaction(
2048 xStorage, css::uno::UNO_QUERY);
2049 if (xTransaction.is()) {
2050 xTransaction->commit();
2054 void SAL_CALL
2055 SfxDocumentMetaData::loadFromMedium(const OUString & URL,
2056 const css::uno::Sequence< css::beans::PropertyValue > & Medium)
2058 css::uno::Reference<css::io::XInputStream> xIn;
2059 utl::MediaDescriptor md(Medium);
2060 // if we have a URL parameter, it replaces the one in the media descriptor
2061 if (!URL.isEmpty()) {
2062 md[ utl::MediaDescriptor::PROP_URL ] <<= URL;
2063 md[ utl::MediaDescriptor::PROP_READONLY ] <<= true;
2065 if (md.addInputStream()) {
2066 md[ utl::MediaDescriptor::PROP_INPUTSTREAM ] >>= xIn;
2068 css::uno::Reference<css::embed::XStorage> xStorage;
2069 try {
2070 if (xIn.is()) {
2071 xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
2072 xIn, m_xContext);
2073 } else { // fallback to url parameter
2074 xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
2075 URL, css::embed::ElementModes::READ, m_xContext);
2077 } catch (const css::uno::RuntimeException &) {
2078 throw;
2079 } catch (const css::io::IOException &) {
2080 throw;
2081 } catch (const css::uno::Exception &) {
2082 css::uno::Any anyEx = cppu::getCaughtException();
2083 throw css::lang::WrappedTargetException(
2084 "SfxDocumentMetaData::loadFromMedium: exception",
2085 css::uno::Reference<css::uno::XInterface>(*this),
2086 anyEx);
2088 if (!xStorage.is()) {
2089 throw css::uno::RuntimeException(
2090 "SfxDocumentMetaData::loadFromMedium: cannot get Storage",
2091 *this);
2093 loadFromStorage(xStorage, md.getAsConstPropertyValueList());
2096 void SAL_CALL
2097 SfxDocumentMetaData::storeToMedium(const OUString & URL,
2098 const css::uno::Sequence< css::beans::PropertyValue > & Medium)
2100 utl::MediaDescriptor md(Medium);
2101 if (!URL.isEmpty()) {
2102 md[ utl::MediaDescriptor::PROP_URL ] <<= URL;
2104 SfxMedium aMedium(md.getAsConstPropertyValueList());
2105 css::uno::Reference<css::embed::XStorage> xStorage
2106 = aMedium.GetOutputStorage();
2109 if (!xStorage.is()) {
2110 throw css::uno::RuntimeException(
2111 "SfxDocumentMetaData::storeToMedium: cannot get Storage",
2112 *this);
2114 // set MIME type of the storage
2115 utl::MediaDescriptor::const_iterator iter
2116 = md.find(utl::MediaDescriptor::PROP_MEDIATYPE);
2117 if (iter != md.end()) {
2118 css::uno::Reference< css::beans::XPropertySet > xProps(xStorage,
2119 css::uno::UNO_QUERY_THROW);
2120 xProps->setPropertyValue(
2121 utl::MediaDescriptor::PROP_MEDIATYPE,
2122 iter->second);
2124 storeToStorage(xStorage, md.getAsConstPropertyValueList());
2127 const bool bOk = aMedium.Commit();
2128 aMedium.Close();
2129 if ( !bOk ) {
2130 ErrCodeMsg nError = aMedium.GetErrorIgnoreWarning();
2131 if ( nError == ERRCODE_NONE ) {
2132 nError = ERRCODE_IO_GENERAL;
2135 throw css::task::ErrorCodeIOException(
2136 "SfxDocumentMetaData::storeToMedium <" + URL + "> Commit failed: " + nError.toString(),
2137 css::uno::Reference< css::uno::XInterface >(), sal_uInt32(nError.GetCode()));
2142 // css::lang::XInitialization:
2143 void SAL_CALL SfxDocumentMetaData::initialize( const css::uno::Sequence< css::uno::Any > & aArguments)
2145 // possible arguments:
2146 // - no argument: default initialization (empty DOM)
2147 // - 1 argument, XDocument: initialize with given DOM and empty base URL
2148 // NB: links in document must be absolute
2150 ::osl::MutexGuard g(m_aMutex);
2151 css::uno::Reference<css::xml::dom::XDocument> xDoc;
2153 for (sal_Int32 i = 0; i < aArguments.getLength(); ++i) {
2154 const css::uno::Any any = aArguments[i];
2155 if (!(any >>= xDoc)) {
2156 throw css::lang::IllegalArgumentException(
2157 "SfxDocumentMetaData::initialize: argument must be XDocument",
2158 *this, static_cast<sal_Int16>(i));
2160 if (!xDoc.is()) {
2161 throw css::lang::IllegalArgumentException(
2162 "SfxDocumentMetaData::initialize: argument is null",
2163 *this, static_cast<sal_Int16>(i));
2167 if (!xDoc.is()) {
2168 // For a new document, we create a new DOM tree here.
2169 xDoc = createDOM();
2172 init(xDoc);
2175 // css::util::XCloneable:
2176 css::uno::Reference<css::util::XCloneable> SAL_CALL
2177 SfxDocumentMetaData::createClone()
2179 ::osl::MutexGuard g(m_aMutex);
2180 checkInit();
2182 rtl::Reference<SfxDocumentMetaData> pNew = createMe(m_xContext);
2184 // NB: do not copy the modification listeners, only DOM
2185 css::uno::Reference<css::xml::dom::XDocument> xDoc = createDOM();
2186 try {
2187 updateUserDefinedAndAttributes();
2188 // deep copy of root node
2189 css::uno::Reference<css::xml::dom::XNode> xRoot(
2190 m_xDoc->getDocumentElement(), css::uno::UNO_QUERY_THROW);
2191 css::uno::Reference<css::xml::dom::XNode> xRootNew(
2192 xDoc->importNode(xRoot, true));
2193 xDoc->appendChild(xRootNew);
2194 pNew->init(xDoc);
2195 } catch (const css::uno::RuntimeException &) {
2196 throw;
2197 } catch (const css::uno::Exception &) {
2198 css::uno::Any anyEx = cppu::getCaughtException();
2199 throw css::lang::WrappedTargetRuntimeException(
2200 "SfxDocumentMetaData::createClone: exception",
2201 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
2203 return css::uno::Reference<css::util::XCloneable> (pNew);
2206 // css::util::XModifiable:
2207 sal_Bool SAL_CALL SfxDocumentMetaData::isModified( )
2209 ::osl::MutexGuard g(m_aMutex);
2210 checkInit();
2211 css::uno::Reference<css::util::XModifiable> xMB(m_xUserDefined,
2212 css::uno::UNO_QUERY);
2213 return m_isModified || (xMB.is() && xMB->isModified());
2216 void SAL_CALL SfxDocumentMetaData::setModified( sal_Bool bModified )
2218 css::uno::Reference<css::util::XModifiable> xMB;
2219 { // do not lock mutex while notifying (#i93514#) to prevent deadlock
2220 ::osl::MutexGuard g(m_aMutex);
2221 checkInit();
2222 m_isModified = bModified;
2223 if ( !bModified && m_xUserDefined.is() )
2225 xMB.set(m_xUserDefined, css::uno::UNO_QUERY);
2226 assert(xMB.is() &&
2227 "SfxDocumentMetaData::setModified: PropertyBag not Modifiable?");
2230 if (bModified) {
2231 try {
2232 css::uno::Reference<css::uno::XInterface> xThis(*this);
2233 css::lang::EventObject event(xThis);
2234 m_NotifyListeners.notifyEach(&css::util::XModifyListener::modified,
2235 event);
2236 } catch (const css::uno::RuntimeException &) {
2237 throw;
2238 } catch (const css::uno::Exception &) {
2239 // ignore
2240 TOOLS_WARN_EXCEPTION("sfx.doc", "setModified");
2242 } else {
2243 if (xMB.is()) {
2244 xMB->setModified(false);
2249 // css::util::XModifyBroadcaster:
2250 void SAL_CALL SfxDocumentMetaData::addModifyListener(
2251 const css::uno::Reference< css::util::XModifyListener > & xListener)
2253 ::osl::MutexGuard g(m_aMutex);
2254 checkInit();
2255 m_NotifyListeners.addInterface(xListener);
2256 css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
2257 css::uno::UNO_QUERY);
2258 if (xMB.is()) {
2259 xMB->addModifyListener(xListener);
2263 void SAL_CALL SfxDocumentMetaData::removeModifyListener(
2264 const css::uno::Reference< css::util::XModifyListener > & xListener)
2266 ::osl::MutexGuard g(m_aMutex);
2267 checkInit();
2268 m_NotifyListeners.removeInterface(xListener);
2269 css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
2270 css::uno::UNO_QUERY);
2271 if (xMB.is()) {
2272 xMB->removeModifyListener(xListener);
2276 // css::xml::sax::XSAXSerializable
2277 void SAL_CALL SfxDocumentMetaData::serialize(
2278 const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
2279 const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces)
2281 ::osl::MutexGuard g(m_aMutex);
2282 checkInit();
2283 updateUserDefinedAndAttributes();
2284 css::uno::Reference<css::xml::sax::XSAXSerializable> xSAXable(m_xDoc,
2285 css::uno::UNO_QUERY_THROW);
2286 xSAXable->serialize(i_xHandler, i_rNamespaces);
2289 void SfxDocumentMetaData::createUserDefined()
2291 // user-defined meta data: create PropertyBag which only accepts property
2292 // values of allowed types
2293 if ( m_xUserDefined.is() )
2294 return;
2296 css::uno::Sequence<css::uno::Type> types{
2297 ::cppu::UnoType<bool>::get(),
2298 ::cppu::UnoType< OUString>::get(),
2299 ::cppu::UnoType<css::util::DateTime>::get(),
2300 ::cppu::UnoType<css::util::Date>::get(),
2301 ::cppu::UnoType<css::util::DateTimeWithTimezone>::get(),
2302 ::cppu::UnoType<css::util::DateWithTimezone>::get(),
2303 ::cppu::UnoType<css::util::Duration>::get(),
2304 ::cppu::UnoType<float>::get(),
2305 ::cppu::UnoType<double>::get(),
2306 ::cppu::UnoType<sal_Int16>::get(),
2307 ::cppu::UnoType<sal_Int32>::get(),
2308 ::cppu::UnoType<sal_Int64>::get(),
2309 // Time is supported for backward compatibility with OOo 3.x, x<=2
2310 ::cppu::UnoType<css::util::Time>::get()
2312 // #i94175#: ODF allows empty user-defined property names!
2313 m_xUserDefined.set(
2314 css::beans::PropertyBag::createWithTypes( m_xContext, types, true/*AllowEmptyPropertyName*/, false/*AutomaticAddition*/ ),
2315 css::uno::UNO_QUERY_THROW);
2317 const css::uno::Reference<css::util::XModifyBroadcaster> xMB(
2318 m_xUserDefined, css::uno::UNO_QUERY);
2319 if (xMB.is())
2321 const std::vector<css::uno::Reference<css::util::XModifyListener> >
2322 listeners(m_NotifyListeners.getElements());
2323 for (const auto& l : listeners) {
2324 xMB->addModifyListener(l);
2329 } // closing anonymous implementation namespace
2331 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
2332 CompatWriterDocPropsImpl_get_implementation(
2333 css::uno::XComponentContext *context,
2334 css::uno::Sequence<css::uno::Any> const &)
2336 return cppu::acquire(new CompatWriterDocPropsImpl(context));
2339 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
2340 SfxDocumentMetaData_get_implementation(
2341 css::uno::XComponentContext *context,
2342 css::uno::Sequence<css::uno::Any> const &)
2344 return cppu::acquire(new SfxDocumentMetaData(context));
2347 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */