1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
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/lang/XInitialization.hpp>
28 #include <com/sun/star/util/XCloneable.hpp>
29 #include <com/sun/star/util/XModifiable.hpp>
30 #include <com/sun/star/xml/sax/SAXException.hpp>
31 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
32 
33 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
34 #include <com/sun/star/lang/EventObject.hpp>
35 #include <com/sun/star/beans/IllegalTypeException.hpp>
36 #include <com/sun/star/beans/PropertyExistException.hpp>
37 #include <com/sun/star/beans/XPropertySet.hpp>
38 #include <com/sun/star/beans/XPropertySetInfo.hpp>
39 #include <com/sun/star/beans/PropertyAttribute.hpp>
40 #include <com/sun/star/task/ErrorCodeIOException.hpp>
41 #include <com/sun/star/embed/XStorage.hpp>
42 #include <com/sun/star/embed/XTransactedObject.hpp>
43 #include <com/sun/star/embed/ElementModes.hpp>
44 #include <com/sun/star/io/WrongFormatException.hpp>
45 #include <com/sun/star/io/XStream.hpp>
46 #include <com/sun/star/document/XImporter.hpp>
47 #include <com/sun/star/document/XExporter.hpp>
48 #include <com/sun/star/document/XFilter.hpp>
49 #include <com/sun/star/xml/sax/Writer.hpp>
50 #include <com/sun/star/xml/sax/Parser.hpp>
51 #include <com/sun/star/xml/sax/XFastParser.hpp>
52 #include <com/sun/star/xml/dom/DOMException.hpp>
53 #include <com/sun/star/xml/dom/XDocument.hpp>
54 #include <com/sun/star/xml/dom/XElement.hpp>
55 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
56 #include <com/sun/star/xml/dom/NodeType.hpp>
57 #include <com/sun/star/xml/xpath/XPathAPI.hpp>
58 #include <com/sun/star/util/Date.hpp>
59 #include <com/sun/star/util/Time.hpp>
60 #include <com/sun/star/util/DateWithTimezone.hpp>
61 #include <com/sun/star/util/DateTimeWithTimezone.hpp>
62 #include <com/sun/star/util/Duration.hpp>
63 
64 #include <rtl/ustrbuf.hxx>
65 #include <tools/datetime.hxx>
66 #include <tools/diagnose_ex.h>
67 #include <osl/mutex.hxx>
68 #include <comphelper/fileformat.h>
69 #include <cppuhelper/basemutex.hxx>
70 #include <comphelper/interfacecontainer2.hxx>
71 #include <comphelper/storagehelper.hxx>
72 #include <unotools/mediadescriptor.hxx>
73 #include <comphelper/sequence.hxx>
74 #include <sot/storage.hxx>
75 #include <sfx2/docfile.hxx>
76 #include <sax/tools/converter.hxx>
77 #include <i18nlangtag/languagetag.hxx>
78 #include <optional>
79 
80 #include <utility>
81 #include <vector>
82 #include <map>
83 #include <cstring>
84 #include <limits>
85 
86 
87 #include <cppuhelper/implbase.hxx>
88 #include <cppuhelper/supportsservice.hxx>
89 #include <com/sun/star/document/XCompatWriterDocProperties.hpp>
90 #include <com/sun/star/beans/PropertyBag.hpp>
91 
92 /**
93  * This file contains the implementation of the service
94  * com.sun.star.document.DocumentProperties.
95  * This service enables access to the meta-data stored in documents.
96  * Currently, this service only handles documents in ODF format.
97  *
98  * The implementation uses an XML DOM to store the properties.
99  * This approach was taken because it allows for preserving arbitrary XML data
100  * in loaded documents, which will be stored unmodified when saving the
101  * document again.
102  *
103  * Upon access, some properties are directly read from and updated in the DOM.
104  * Exception: it seems impossible to get notified upon addition of a property
105  * to a com.sun.star.beans.PropertyBag, which is used for storing user-defined
106  * properties; because of this, user-defined properties are updated in the
107  * XML DOM only when storing the document.
108  * Exception 2: when setting certain properties which correspond to attributes
109  * in the XML DOM, we want to remove the corresponding XML element. Detecting
110  * this condition can get messy, so we store all such properties as members,
111  * and update the DOM tree only when storing the document (in
112  * <method>updateUserDefinedAndAttributes</method>).
113  *
114  */
115 
116 /// anonymous implementation namespace
117 namespace {
118 
119 /// a list of attribute-lists, where attribute means name and content
120 typedef std::vector<std::vector<std::pair<const char*, OUString> > >
121         AttrVector;
122 
123 typedef ::cppu::WeakComponentImplHelper<
124             css::lang::XServiceInfo,
125             css::document::XDocumentProperties,
126             css::lang::XInitialization,
127             css::util::XCloneable,
128             css::util::XModifiable,
129             css::xml::sax::XSAXSerializable>
130     SfxDocumentMetaData_Base;
131 
132 class SfxDocumentMetaData:
133     private ::cppu::BaseMutex,
134     public SfxDocumentMetaData_Base
135 {
136 public:
137     explicit SfxDocumentMetaData(
138         css::uno::Reference< css::uno::XComponentContext > const & context);
139     SfxDocumentMetaData(const SfxDocumentMetaData&) = delete;
140     SfxDocumentMetaData& operator=(const SfxDocumentMetaData&) = delete;
141 
142     // css::lang::XServiceInfo:
143     virtual OUString SAL_CALL getImplementationName() override;
144     virtual sal_Bool SAL_CALL supportsService(
145         const OUString & ServiceName) override;
146     virtual css::uno::Sequence< OUString > SAL_CALL
147         getSupportedServiceNames() override;
148 
149     // css::lang::XComponent:
150     virtual void SAL_CALL dispose() override;
151 
152     // css::document::XDocumentProperties:
153     virtual OUString SAL_CALL getAuthor() override;
154     virtual void SAL_CALL setAuthor(const OUString & the_value) override;
155     virtual OUString SAL_CALL getGenerator() override;
156     virtual void SAL_CALL setGenerator(const OUString & the_value) override;
157     virtual css::util::DateTime SAL_CALL getCreationDate() override;
158     virtual void SAL_CALL setCreationDate(const css::util::DateTime & the_value) override;
159     virtual OUString SAL_CALL getTitle() override;
160     virtual void SAL_CALL setTitle(const OUString & the_value) override;
161     virtual OUString SAL_CALL getSubject() override;
162     virtual void SAL_CALL setSubject(const OUString & the_value) override;
163     virtual OUString SAL_CALL getDescription() override;
164     virtual void SAL_CALL setDescription(const OUString & the_value) override;
165     virtual css::uno::Sequence< OUString > SAL_CALL getKeywords() override;
166     virtual void SAL_CALL setKeywords(
167         const css::uno::Sequence< OUString > & the_value) override;
168     virtual css::lang::Locale SAL_CALL getLanguage() override;
169     virtual void SAL_CALL setLanguage(const css::lang::Locale & the_value) override;
170     virtual OUString SAL_CALL getModifiedBy() override;
171     virtual void SAL_CALL setModifiedBy(const OUString & the_value) override;
172     virtual css::util::DateTime SAL_CALL getModificationDate() override;
173     virtual void SAL_CALL setModificationDate(
174             const css::util::DateTime & the_value) override;
175     virtual OUString SAL_CALL getPrintedBy() override;
176     virtual void SAL_CALL setPrintedBy(const OUString & the_value) override;
177     virtual css::util::DateTime SAL_CALL getPrintDate() override;
178     virtual void SAL_CALL setPrintDate(const css::util::DateTime & the_value) override;
179     virtual OUString SAL_CALL getTemplateName() override;
180     virtual void SAL_CALL setTemplateName(const OUString & the_value) override;
181     virtual OUString SAL_CALL getTemplateURL() override;
182     virtual void SAL_CALL setTemplateURL(const OUString & the_value) override;
183     virtual css::util::DateTime SAL_CALL getTemplateDate() override;
184     virtual void SAL_CALL setTemplateDate(const css::util::DateTime & the_value) override;
185     virtual OUString SAL_CALL getAutoloadURL() override;
186     virtual void SAL_CALL setAutoloadURL(const OUString & the_value) override;
187     virtual ::sal_Int32 SAL_CALL getAutoloadSecs() override;
188     virtual void SAL_CALL setAutoloadSecs(::sal_Int32 the_value) override;
189     virtual OUString SAL_CALL getDefaultTarget() override;
190     virtual void SAL_CALL setDefaultTarget(const OUString & the_value) override;
191     virtual css::uno::Sequence< css::beans::NamedValue > SAL_CALL
192         getDocumentStatistics() override;
193     virtual void SAL_CALL setDocumentStatistics(
194         const css::uno::Sequence< css::beans::NamedValue > & the_value) override;
195     virtual ::sal_Int16 SAL_CALL getEditingCycles() override;
196     virtual void SAL_CALL setEditingCycles(::sal_Int16 the_value) override;
197     virtual ::sal_Int32 SAL_CALL getEditingDuration() override;
198     virtual void SAL_CALL setEditingDuration(::sal_Int32 the_value) override;
199     virtual void SAL_CALL resetUserData(const OUString & the_value) override;
200     virtual css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
201         getUserDefinedProperties() override;
202     virtual void SAL_CALL loadFromStorage(
203         const css::uno::Reference< css::embed::XStorage > & Storage,
204         const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
205     virtual void SAL_CALL loadFromMedium(const OUString & URL,
206         const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
207     virtual void SAL_CALL storeToStorage(
208         const css::uno::Reference< css::embed::XStorage > & Storage,
209         const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
210     virtual void SAL_CALL storeToMedium(const OUString & URL,
211         const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
212 
213     // css::lang::XInitialization:
214     virtual void SAL_CALL initialize(
215         const css::uno::Sequence< css::uno::Any > & aArguments) override;
216 
217     // css::util::XCloneable:
218     virtual css::uno::Reference<css::util::XCloneable> SAL_CALL createClone() override;
219 
220     // css::util::XModifiable:
221     virtual sal_Bool SAL_CALL isModified(  ) override;
222     virtual void SAL_CALL setModified( sal_Bool bModified ) override;
223 
224     // css::util::XModifyBroadcaster:
225     virtual void SAL_CALL addModifyListener(
226         const css::uno::Reference< css::util::XModifyListener > & xListener) override;
227     virtual void SAL_CALL removeModifyListener(
228         const css::uno::Reference< css::util::XModifyListener > & xListener) override;
229 
230     // css::xml::sax::XSAXSerializable
231     virtual void SAL_CALL serialize(
232         const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
233         const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces) override;
234 
235 protected:
~SfxDocumentMetaData()236     virtual ~SfxDocumentMetaData() override {}
createMe(css::uno::Reference<css::uno::XComponentContext> const & context)237     virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) { return new SfxDocumentMetaData( context ); };
238     const css::uno::Reference< css::uno::XComponentContext > m_xContext;
239 
240     /// for notification
241     ::comphelper::OInterfaceContainerHelper2 m_NotifyListeners;
242     /// flag: false means not initialized yet, or disposed
243     bool m_isInitialized;
244     /// flag
245     bool m_isModified;
246     /// meta-data DOM tree
247     css::uno::Reference< css::xml::dom::XDocument > m_xDoc;
248     /// meta-data super node in the meta-data DOM tree
249     css::uno::Reference< css::xml::dom::XNode> m_xParent;
250     /// standard meta data (single occurrence)
251     std::map< OUString, css::uno::Reference<css::xml::dom::XNode> >
252         m_meta;
253     /// standard meta data (multiple occurrences)
254     std::map< OUString,
255         std::vector<css::uno::Reference<css::xml::dom::XNode> > > m_metaList;
256     /// user-defined meta data (meta:user-defined) @ATTENTION may be null!
257     css::uno::Reference<css::beans::XPropertyContainer> m_xUserDefined;
258     // now for some meta-data attributes; these are not updated directly in the
259     // DOM because updates (detecting "empty" elements) would be quite messy
260     OUString m_TemplateName;
261     OUString m_TemplateURL;
262     css::util::DateTime m_TemplateDate;
263     OUString m_AutoloadURL;
264     sal_Int32 m_AutoloadSecs;
265     OUString m_DefaultTarget;
266 
267     /// check if we are initialized properly
268     void checkInit() const;
269     /// initialize state from given DOM tree
270     void init(const css::uno::Reference<css::xml::dom::XDocument>& i_xDom);
271     /// update element in DOM tree
272     void updateElement(const char *i_name,
273         std::vector<std::pair<const char *, OUString> >* i_pAttrs = nullptr);
274     /// update user-defined meta data and attributes in DOM tree
275     void updateUserDefinedAndAttributes();
276     /// create empty DOM tree (XDocument)
277     css::uno::Reference<css::xml::dom::XDocument> createDOM() const;
278     /// extract base URL (necessary for converting relative links)
279     css::uno::Reference<css::beans::XPropertySet> getURLProperties(
280         const css::uno::Sequence<css::beans::PropertyValue> & i_rMedium) const;
281     /// get text of standard meta data element
282     OUString getMetaText(const char* i_name) const;
283     /// set text of standard meta data element iff not equal to existing text
284     bool setMetaText(const char* i_name,
285         const OUString & i_rValue);
286     /// set text of standard meta data element iff not equal to existing text
287     void setMetaTextAndNotify(const char* i_name,
288         const OUString & i_rValue);
289     /// get text of standard meta data element's attribute
290     OUString getMetaAttr(const char* i_name,
291         const char* i_attr) const;
292     /// get text of a list of standard meta data elements (multiple occ.)
293     css::uno::Sequence< OUString > getMetaList(
294         const char* i_name) const;
295     /// set text of a list of standard meta data elements (multiple occ.)
296     bool setMetaList(const char* i_name,
297         const css::uno::Sequence< OUString > & i_rValue,
298         AttrVector const*);
299     void createUserDefined();
300 };
301 
302 typedef ::cppu::ImplInheritanceHelper< SfxDocumentMetaData, css::document::XCompatWriterDocProperties > CompatWriterDocPropsImpl_BASE;
303 
304 class CompatWriterDocPropsImpl : public CompatWriterDocPropsImpl_BASE
305 {
306     OUString msManager;
307     OUString msCategory;
308     OUString msCompany;
309 protected:
createMe(css::uno::Reference<css::uno::XComponentContext> const & context)310     virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) override { return new CompatWriterDocPropsImpl( context ); };
311 public:
CompatWriterDocPropsImpl(css::uno::Reference<css::uno::XComponentContext> const & context)312     explicit CompatWriterDocPropsImpl( css::uno::Reference< css::uno::XComponentContext > const & context) : CompatWriterDocPropsImpl_BASE( context ) {}
313 
314 // XCompatWriterDocPropsImpl
getManager()315     virtual OUString SAL_CALL getManager() override { return msManager; }
setManager(const OUString & _manager)316     virtual void SAL_CALL setManager( const OUString& _manager ) override { msManager = _manager; }
getCategory()317     virtual OUString SAL_CALL getCategory() override { return msCategory; }
setCategory(const OUString & _category)318     virtual void SAL_CALL setCategory( const OUString& _category ) override { msCategory = _category; }
getCompany()319     virtual OUString SAL_CALL getCompany() override { return msCompany; }
setCompany(const OUString & _company)320     virtual void SAL_CALL setCompany( const OUString& _company ) override { msCompany = _company; }
321 
322 // XServiceInfo
getImplementationName()323     virtual OUString SAL_CALL getImplementationName(  ) override
324     {
325         return "CompatWriterDocPropsImpl";
326     }
327 
supportsService(const OUString & ServiceName)328     virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override
329     {
330         return cppu::supportsService(this, ServiceName);
331     }
332 
getSupportedServiceNames()333     virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames(  ) override
334     {
335         css::uno::Sequence<OUString> aServiceNames { "com.sun.star.writer.DocumentProperties" };
336         return aServiceNames;
337     }
338 };
339 
340 // NB: keep these two arrays in sync!
341 const char* s_stdStatAttrs[] = {
342     "meta:page-count",
343     "meta:table-count",
344     "meta:draw-count",
345     "meta:image-count",
346     "meta:object-count",
347     "meta:ole-object-count",
348     "meta:paragraph-count",
349     "meta:word-count",
350     "meta:character-count",
351     "meta:row-count",
352     "meta:frame-count",
353     "meta:sentence-count",
354     "meta:syllable-count",
355     "meta:non-whitespace-character-count",
356     "meta:cell-count",
357     nullptr
358 };
359 
360 // NB: keep these two arrays in sync!
361 const char* s_stdStats[] = {
362     "PageCount",
363     "TableCount",
364     "DrawCount",
365     "ImageCount",
366     "ObjectCount",
367     "OLEObjectCount",
368     "ParagraphCount",
369     "WordCount",
370     "CharacterCount",
371     "RowCount",
372     "FrameCount",
373     "SentenceCount",
374     "SyllableCount",
375     "NonWhitespaceCharacterCount",
376     "CellCount",
377     nullptr
378 };
379 
380 const char* s_stdMeta[] = {
381     "meta:generator",           // string
382     "dc:title",                 // string
383     "dc:description",           // string
384     "dc:subject",               // string
385     "meta:initial-creator",     // string
386     "dc:creator",               // string
387     "meta:printed-by",          // string
388     "meta:creation-date",       // dateTime
389     "dc:date",                  // dateTime
390     "meta:print-date",          // dateTime
391     "meta:template",            // XLink
392     "meta:auto-reload",
393     "meta:hyperlink-behaviour",
394     "dc:language",              // language
395     "meta:editing-cycles",      // nonNegativeInteger
396     "meta:editing-duration",    // duration
397     "meta:document-statistic",  // ... // note: statistic is singular, no s!
398     nullptr
399 };
400 
401 const char* s_stdMetaList[] = {
402     "meta:keyword",             // string*
403     "meta:user-defined",        // ...*
404     nullptr
405 };
406 
407 constexpr OUStringLiteral s_nsXLink = u"http://www.w3.org/1999/xlink";
408 constexpr OUStringLiteral s_nsDC = u"http://purl.org/dc/elements/1.1/";
409 constexpr OUStringLiteral s_nsODF = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0";
410 constexpr OUStringLiteral s_nsODFMeta = u"urn:oasis:names:tc:opendocument:xmlns:meta:1.0";
411 // constexpr OUStringLiteral s_nsOOo = "http://openoffice.org/2004/office"; // not used (yet?)
412 
413 constexpr OUStringLiteral s_meta = u"meta.xml";
414 
isValidDate(const css::util::Date & i_rDate)415 bool isValidDate(const css::util::Date & i_rDate)
416 {
417     return i_rDate.Month > 0;
418 }
419 
isValidDateTime(const css::util::DateTime & i_rDateTime)420 bool isValidDateTime(const css::util::DateTime & i_rDateTime)
421 {
422     return i_rDateTime.Month > 0;
423 }
424 
425 std::pair< OUString, OUString >
getQualifier(const char * i_name)426 getQualifier(const char* i_name) {
427     OUString nm = OUString::createFromAscii(i_name);
428     sal_Int32 ix = nm.indexOf(u':');
429     if (ix == -1) {
430         return std::make_pair(OUString(), nm);
431     } else {
432         return std::make_pair(nm.copy(0,ix), nm.copy(ix+1));
433     }
434 }
435 
436 // get namespace for standard qualified names
437 // NB: only call this with statically known strings!
getNameSpace(const char * i_qname)438 OUString getNameSpace(const char* i_qname) noexcept
439 {
440     assert(i_qname);
441     OUString ns;
442     OUString n = getQualifier(i_qname).first;
443     if ( n == "xlink" ) ns = s_nsXLink;
444     if ( n == "dc" ) ns = s_nsDC;
445     if ( n == "office" ) ns = s_nsODF;
446     if ( n == "meta" ) ns = s_nsODFMeta;
447     assert(!ns.isEmpty());
448     return ns;
449 }
450 
451 bool
textToDateOrDateTime(css::util::Date & io_rd,css::util::DateTime & io_rdt,bool & o_rIsDateTime,std::optional<sal_Int16> & o_rTimeZone,const OUString & i_text)452 textToDateOrDateTime(css::util::Date & io_rd, css::util::DateTime & io_rdt,
453         bool & o_rIsDateTime, std::optional<sal_Int16> & o_rTimeZone,
454         const OUString& i_text) noexcept
455 {
456     if (::sax::Converter::parseDateOrDateTime(
457                 &io_rd, io_rdt, o_rIsDateTime, &o_rTimeZone, i_text)) {
458         return true;
459     } else {
460         SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
461         return false;
462     }
463 }
464 
465 // convert string to date/time
466 bool
textToDateTime(css::util::DateTime & io_rdt,const OUString & i_text)467 textToDateTime(css::util::DateTime & io_rdt, const OUString& i_text) noexcept
468 {
469     if (::sax::Converter::parseDateTime(io_rdt, i_text)) {
470         return true;
471     } else {
472         SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
473         return false;
474     }
475 }
476 
477 // convert string to date/time with default return value
478 css::util::DateTime
textToDateTimeDefault(const OUString & i_text)479 textToDateTimeDefault(const OUString& i_text) noexcept
480 {
481     css::util::DateTime dt;
482     static_cast<void> (textToDateTime(dt, i_text));
483     // on conversion error: return default value (unchanged)
484     return dt;
485 }
486 
487 // convert date to string
488 OUString
dateToText(css::util::Date const & i_rd,sal_Int16 const * const pTimeZone)489 dateToText(css::util::Date const& i_rd,
490            sal_Int16 const*const pTimeZone) noexcept
491 {
492     if (isValidDate(i_rd)) {
493         OUStringBuffer buf;
494         ::sax::Converter::convertDate(buf, i_rd, pTimeZone);
495         return buf.makeStringAndClear();
496     } else {
497         return OUString();
498     }
499 }
500 
501 
502 // convert date/time to string
503 OUString
dateTimeToText(css::util::DateTime const & i_rdt,sal_Int16 const * const pTimeZone=nullptr)504 dateTimeToText(css::util::DateTime const& i_rdt,
505                sal_Int16 const*const pTimeZone = nullptr) noexcept
506 {
507     if (isValidDateTime(i_rdt)) {
508         OUStringBuffer buf(32);
509         ::sax::Converter::convertDateTime(buf, i_rdt, pTimeZone, true);
510         return buf.makeStringAndClear();
511     } else {
512         return OUString();
513     }
514 }
515 
516 // convert string to duration
517 bool
textToDuration(css::util::Duration & io_rDur,OUString const & i_rText)518 textToDuration(css::util::Duration& io_rDur, OUString const& i_rText)
519 noexcept
520 {
521     if (::sax::Converter::convertDuration(io_rDur, i_rText)) {
522         return true;
523     } else {
524         SAL_WARN_IF(!i_rText.isEmpty(), "sfx.doc", "Invalid duration: " << i_rText);
525         return false;
526     }
527 }
528 
textToDuration(OUString const & i_rText)529 sal_Int32 textToDuration(OUString const& i_rText) noexcept
530 {
531     css::util::Duration d;
532     if (textToDuration(d, i_rText)) {
533         // #i107372#: approximate years/months
534         const sal_Int32 days( (d.Years * 365) + (d.Months * 30) + d.Days );
535         return  (days * (24*3600))
536                 + (d.Hours * 3600) + (d.Minutes * 60) + d.Seconds;
537     } else {
538         return 0; // default
539     }
540 }
541 
542 // convert duration to string
durationToText(css::util::Duration const & i_rDur)543 OUString durationToText(css::util::Duration const& i_rDur) noexcept
544 {
545     OUStringBuffer buf;
546     ::sax::Converter::convertDuration(buf, i_rDur);
547     return buf.makeStringAndClear();
548 }
549 
550 // convert duration to string
durationToText(sal_Int32 i_value)551 OUString durationToText(sal_Int32 i_value) noexcept
552 {
553     css::util::Duration ud;
554     ud.Days    = static_cast<sal_Int16>(i_value / (24 * 3600));
555     ud.Hours   = static_cast<sal_Int16>((i_value % (24 * 3600)) / 3600);
556     ud.Minutes = static_cast<sal_Int16>((i_value % 3600) / 60);
557     ud.Seconds = static_cast<sal_Int16>(i_value % 60);
558     ud.NanoSeconds = 0;
559     return durationToText(ud);
560 }
561 
562 // extract base URL (necessary for converting relative links)
563 css::uno::Reference< css::beans::XPropertySet >
getURLProperties(const css::uno::Sequence<css::beans::PropertyValue> & i_rMedium) const564 SfxDocumentMetaData::getURLProperties(
565     const css::uno::Sequence< css::beans::PropertyValue > & i_rMedium) const
566 {
567     css::uno::Reference< css::beans::XPropertyBag> xPropArg = css::beans::PropertyBag::createDefault( m_xContext );
568     try {
569         css::uno::Any baseUri;
570         for (const auto& rProp : i_rMedium) {
571             if (rProp.Name == "DocumentBaseURL") {
572                 baseUri = rProp.Value;
573             } else if (rProp.Name == "URL") {
574                 if (!baseUri.hasValue()) {
575                     baseUri = rProp.Value;
576                 }
577             } else if (rProp.Name == "HierarchicalDocumentName") {
578                 xPropArg->addProperty(
579                     "StreamRelPath",
580                     css::beans::PropertyAttribute::MAYBEVOID,
581                     rProp.Value);
582             }
583         }
584         if (baseUri.hasValue()) {
585             xPropArg->addProperty(
586                 "BaseURI", css::beans::PropertyAttribute::MAYBEVOID,
587                 baseUri);
588         }
589         xPropArg->addProperty("StreamName",
590                 css::beans::PropertyAttribute::MAYBEVOID,
591                 css::uno::makeAny(OUString(s_meta)));
592     } catch (const css::uno::Exception &) {
593         // ignore
594     }
595     return css::uno::Reference< css::beans::XPropertySet>(xPropArg,
596                 css::uno::UNO_QUERY_THROW);
597 }
598 
599 // return the text of the (hopefully unique, i.e., normalize first!) text
600 // node _below_ the given node
601 /// @throws css::uno::RuntimeException
602 OUString
getNodeText(const css::uno::Reference<css::xml::dom::XNode> & i_xNode)603 getNodeText(const css::uno::Reference<css::xml::dom::XNode>& i_xNode)
604 {
605     if (!i_xNode.is())
606         throw css::uno::RuntimeException("SfxDocumentMetaData::getNodeText: argument is null", i_xNode);
607     for (css::uno::Reference<css::xml::dom::XNode> c = i_xNode->getFirstChild();
608             c.is();
609             c = c->getNextSibling()) {
610         if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
611             try {
612                 return c->getNodeValue();
613             } catch (const css::xml::dom::DOMException &) { // too big?
614                 return OUString();
615             }
616         }
617     }
618     return OUString();
619 }
620 
621 OUString
getMetaText(const char * i_name) const622 SfxDocumentMetaData::getMetaText(const char* i_name) const
623 //        throw (css::uno::RuntimeException)
624 {
625     checkInit();
626 
627     const OUString name( OUString::createFromAscii(i_name) );
628     assert(m_meta.find(name) != m_meta.end());
629     css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
630     return (xNode.is()) ? getNodeText(xNode) : OUString();
631 }
632 
633 bool
setMetaText(const char * i_name,const OUString & i_rValue)634 SfxDocumentMetaData::setMetaText(const char* i_name,
635         const OUString & i_rValue)
636     // throw (css::uno::RuntimeException)
637 {
638     checkInit();
639 
640     const OUString name( OUString::createFromAscii(i_name) );
641     assert(m_meta.find(name) != m_meta.end());
642     css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
643 
644     try {
645         if (i_rValue.isEmpty()) {
646             if (xNode.is()) { // delete
647                 m_xParent->removeChild(xNode);
648                 xNode.clear();
649                 m_meta[name] = xNode;
650                 return true;
651             } else {
652                 return false;
653             }
654         } else {
655             if (xNode.is()) { // update
656                 for (css::uno::Reference<css::xml::dom::XNode> c =
657                             xNode->getFirstChild();
658                         c.is();
659                         c = c->getNextSibling()) {
660                     if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
661                         if (c->getNodeValue() != i_rValue) {
662                             c->setNodeValue(i_rValue);
663                             return true;
664                         } else {
665                             return false;
666                         }
667                     }
668                 }
669             } else { // insert
670                 xNode.set(m_xDoc->createElementNS(getNameSpace(i_name), name),
671                             css::uno::UNO_QUERY_THROW);
672                 m_xParent->appendChild(xNode);
673                 m_meta[name] = xNode;
674             }
675             css::uno::Reference<css::xml::dom::XNode> xTextNode(
676                 m_xDoc->createTextNode(i_rValue), css::uno::UNO_QUERY_THROW);
677             xNode->appendChild(xTextNode);
678             return true;
679         }
680     } catch (const css::xml::dom::DOMException &) {
681         css::uno::Any anyEx = cppu::getCaughtException();
682         throw css::lang::WrappedTargetRuntimeException(
683                 "SfxDocumentMetaData::setMetaText: DOM exception",
684                 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
685     }
686 }
687 
688 void
setMetaTextAndNotify(const char * i_name,const OUString & i_rValue)689 SfxDocumentMetaData::setMetaTextAndNotify(const char* i_name,
690         const OUString & i_rValue)
691     // throw (css::uno::RuntimeException)
692 {
693     ::osl::ClearableMutexGuard g(m_aMutex);
694     if (setMetaText(i_name, i_rValue)) {
695         g.clear();
696         setModified(true);
697     }
698 }
699 
700 OUString
getMetaAttr(const char * i_name,const char * i_attr) const701 SfxDocumentMetaData::getMetaAttr(const char* i_name, const char* i_attr) const
702 //        throw (css::uno::RuntimeException)
703 {
704     OUString name = OUString::createFromAscii(i_name);
705     assert(m_meta.find(name) != m_meta.end());
706     css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
707     if (xNode.is()) {
708         css::uno::Reference<css::xml::dom::XElement> xElem(xNode,
709             css::uno::UNO_QUERY_THROW);
710         return xElem->getAttributeNS(getNameSpace(i_attr),
711                     getQualifier(i_attr).second);
712     } else {
713         return OUString();
714     }
715 }
716 
717 css::uno::Sequence< OUString>
getMetaList(const char * i_name) const718 SfxDocumentMetaData::getMetaList(const char* i_name) const
719 //        throw (css::uno::RuntimeException)
720 {
721     checkInit();
722     OUString name = OUString::createFromAscii(i_name);
723     assert(m_metaList.find(name) != m_metaList.end());
724     std::vector<css::uno::Reference<css::xml::dom::XNode> > const & vec =
725         m_metaList.find(name)->second;
726     css::uno::Sequence< OUString> ret(vec.size());
727     for (size_t i = 0; i < vec.size(); ++i) {
728         ret[i] = getNodeText(vec.at(i));
729     }
730     return ret;
731 }
732 
733 bool
setMetaList(const char * i_name,const css::uno::Sequence<OUString> & i_rValue,AttrVector const * i_pAttrs)734 SfxDocumentMetaData::setMetaList(const char* i_name,
735         const css::uno::Sequence<OUString> & i_rValue,
736         AttrVector const* i_pAttrs)
737     // throw (css::uno::RuntimeException)
738 {
739     checkInit();
740     assert((i_pAttrs == nullptr) ||
741            (static_cast<size_t>(i_rValue.getLength()) == i_pAttrs->size()));
742 
743     try {
744         OUString name = OUString::createFromAscii(i_name);
745         assert(m_metaList.find(name) != m_metaList.end());
746         std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
747             m_metaList[name];
748 
749         // if nothing changed, do nothing
750         // alas, this does not check for permutations, or attributes...
751         if (nullptr == i_pAttrs) {
752             if (static_cast<size_t>(i_rValue.getLength()) == vec.size()) {
753                 bool isEqual(true);
754                 for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
755                     css::uno::Reference<css::xml::dom::XNode> xNode(vec.at(i));
756                     if (xNode.is()) {
757                         OUString val = getNodeText(xNode);
758                         if (val != i_rValue[i]) {
759                             isEqual = false;
760                             break;
761                         }
762                     }
763                 }
764                 if (isEqual) return false;
765             }
766         }
767 
768         // remove old meta data nodes
769         {
770             std::vector<css::uno::Reference<css::xml::dom::XNode> >
771                 ::reverse_iterator it(vec.rbegin());
772             try {
773                 for ( ;it != vec.rend(); ++it)
774                 {
775                     m_xParent->removeChild(*it);
776                 }
777             }
778             catch (...)
779             {
780                 // Clean up already removed nodes
781                 vec.erase(it.base(), vec.end());
782                 throw;
783             }
784             vec.clear();
785         }
786 
787         // insert new meta data nodes into DOM tree
788         for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
789             css::uno::Reference<css::xml::dom::XElement> xElem(
790                 m_xDoc->createElementNS(getNameSpace(i_name), name),
791                 css::uno::UNO_SET_THROW);
792             css::uno::Reference<css::xml::dom::XNode> xNode(xElem,
793                 css::uno::UNO_QUERY_THROW);
794             css::uno::Reference<css::xml::dom::XNode> xTextNode(
795                 m_xDoc->createTextNode(i_rValue[i]), css::uno::UNO_QUERY_THROW);
796             // set attributes
797             if (i_pAttrs != nullptr) {
798                 for (auto const& elem : (*i_pAttrs)[i])
799                 {
800                     xElem->setAttributeNS(getNameSpace(elem.first),
801                         OUString::createFromAscii(elem.first),
802                         elem.second);
803                 }
804             }
805             xNode->appendChild(xTextNode);
806             m_xParent->appendChild(xNode);
807             vec.push_back(xNode);
808         }
809 
810         return true;
811     } catch (const css::xml::dom::DOMException &) {
812         css::uno::Any anyEx = cppu::getCaughtException();
813         throw css::lang::WrappedTargetRuntimeException(
814                 "SfxDocumentMetaData::setMetaList: DOM exception",
815                 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
816     }
817 }
818 
819 // convert property list to string list and attribute list
820 std::pair<css::uno::Sequence< OUString>, AttrVector>
propsToStrings(css::uno::Reference<css::beans::XPropertySet> const & i_xPropSet)821 propsToStrings(css::uno::Reference<css::beans::XPropertySet> const & i_xPropSet)
822 {
823     ::std::vector< OUString > values;
824     AttrVector attrs;
825 
826     css::uno::Reference<css::beans::XPropertySetInfo> xSetInfo
827         = i_xPropSet->getPropertySetInfo();
828     css::uno::Sequence<css::beans::Property> props = xSetInfo->getProperties();
829 
830     for (sal_Int32 i = 0; i < props.getLength(); ++i) {
831         if (props[i].Attributes & css::beans::PropertyAttribute::TRANSIENT) {
832             continue;
833         }
834         const OUString name = props[i].Name;
835         css::uno::Any any;
836         try {
837             any = i_xPropSet->getPropertyValue(name);
838         } catch (const css::uno::Exception &) {
839             // ignore
840         }
841         const css::uno::Type & type = any.getValueType();
842         std::vector<std::pair<const char*, OUString> > as;
843         as.emplace_back("meta:name", name);
844         const char* vt = "meta:value-type";
845 
846         // convert according to type
847         if (type == ::cppu::UnoType<bool>::get()) {
848             bool b = false;
849             any >>= b;
850             OUStringBuffer buf;
851             ::sax::Converter::convertBool(buf, b);
852             values.push_back(buf.makeStringAndClear());
853             as.emplace_back(vt, OUString("boolean"));
854         } else if (type == ::cppu::UnoType< OUString>::get()) {
855             OUString s;
856             any >>= s;
857             values.push_back(s);
858 // #i90847# OOo 2.x does stupid things if value-type="string";
859 // fortunately string is default anyway, so we can just omit it
860 // #i107502#: however, OOo 2.x only reads 4 user-defined without @value-type
861 // => best backward compatibility: first 4 without @value-type, rest with
862             if (4 <= i)
863             {
864                 as.emplace_back(vt, OUString("string"));
865             }
866         } else if (type == ::cppu::UnoType<css::util::DateTime>::get()) {
867             css::util::DateTime dt;
868             any >>= dt;
869             values.push_back(dateTimeToText(dt));
870             as.emplace_back(vt, OUString("date"));
871         } else if (type == ::cppu::UnoType<css::util::Date>::get()) {
872             css::util::Date d;
873             any >>= d;
874             values.push_back(dateToText(d, nullptr));
875             as.emplace_back(vt,OUString("date"));
876         } else if (type == ::cppu::UnoType<css::util::DateTimeWithTimezone>::get()) {
877             css::util::DateTimeWithTimezone dttz;
878             any >>= dttz;
879             values.push_back(dateTimeToText(dttz.DateTimeInTZ, &dttz.Timezone));
880             as.emplace_back(vt, OUString("date"));
881         } else if (type == ::cppu::UnoType<css::util::DateWithTimezone>::get()) {
882             css::util::DateWithTimezone dtz;
883             any >>= dtz;
884             values.push_back(dateToText(dtz.DateInTZ, &dtz.Timezone));
885             as.emplace_back(vt, OUString("date"));
886         } else if (type == ::cppu::UnoType<css::util::Time>::get()) {
887             // #i97029#: replaced by Duration
888             // Time is supported for backward compatibility with OOo 3.x, x<=2
889             css::util::Time ut;
890             any >>= ut;
891             css::util::Duration ud;
892             ud.Hours   = ut.Hours;
893             ud.Minutes = ut.Minutes;
894             ud.Seconds = ut.Seconds;
895             ud.NanoSeconds = ut.NanoSeconds;
896             values.push_back(durationToText(ud));
897             as.emplace_back(vt, OUString("time"));
898         } else if (type == ::cppu::UnoType<css::util::Duration>::get()) {
899             css::util::Duration ud;
900             any >>= ud;
901             values.push_back(durationToText(ud));
902             as.emplace_back(vt, OUString("time"));
903         } else if (::cppu::UnoType<double>::get().isAssignableFrom(type)) {
904             // support not just double, but anything that can be converted
905             double d = 0;
906             any >>= d;
907             OUStringBuffer buf;
908             ::sax::Converter::convertDouble(buf, d);
909             values.push_back(buf.makeStringAndClear());
910             as.emplace_back(vt, OUString("float"));
911         } else {
912             SAL_WARN("sfx.doc", "Unsupported property type: " << any.getValueTypeName() );
913             continue;
914         }
915         attrs.push_back(as);
916     }
917 
918     return std::make_pair(comphelper::containerToSequence(values), attrs);
919 }
920 
921 // remove the given element from the DOM, and iff i_pAttrs != 0 insert new one
922 void
updateElement(const char * i_name,std::vector<std::pair<const char *,OUString>> * i_pAttrs)923 SfxDocumentMetaData::updateElement(const char *i_name,
924         std::vector<std::pair<const char *, OUString> >* i_pAttrs)
925 {
926     OUString name = OUString::createFromAscii(i_name);
927     try {
928         // remove old element
929         css::uno::Reference<css::xml::dom::XNode> xNode =
930             m_meta.find(name)->second;
931         if (xNode.is()) {
932             m_xParent->removeChild(xNode);
933             xNode.clear();
934         }
935         // add new element
936         if (nullptr != i_pAttrs) {
937             css::uno::Reference<css::xml::dom::XElement> xElem(
938                 m_xDoc->createElementNS(getNameSpace(i_name), name),
939                     css::uno::UNO_SET_THROW);
940             xNode.set(xElem, css::uno::UNO_QUERY_THROW);
941             // set attributes
942             for (auto const& elem : *i_pAttrs)
943             {
944                 xElem->setAttributeNS(getNameSpace(elem.first),
945                     OUString::createFromAscii(elem.first), elem.second);
946             }
947             m_xParent->appendChild(xNode);
948         }
949         m_meta[name] = xNode;
950     } catch (const css::xml::dom::DOMException &) {
951         css::uno::Any anyEx = cppu::getCaughtException();
952         throw css::lang::WrappedTargetRuntimeException(
953                 "SfxDocumentMetaData::updateElement: DOM exception",
954                 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
955     }
956 }
957 
958 // update user-defined meta data in DOM tree
updateUserDefinedAndAttributes()959 void SfxDocumentMetaData::updateUserDefinedAndAttributes()
960 {
961     createUserDefined();
962     const css::uno::Reference<css::beans::XPropertySet> xPSet(m_xUserDefined,
963             css::uno::UNO_QUERY_THROW);
964     const std::pair<css::uno::Sequence< OUString>, AttrVector>
965         udStringsAttrs( propsToStrings(xPSet) );
966     (void) setMetaList("meta:user-defined", udStringsAttrs.first,
967             &udStringsAttrs.second);
968 
969     // update elements with attributes
970     std::vector<std::pair<const char *, OUString> > attributes;
971     if (!m_TemplateName.isEmpty() || !m_TemplateURL.isEmpty()
972             || isValidDateTime(m_TemplateDate)) {
973         attributes.emplace_back("xlink:type", OUString("simple"));
974         attributes.emplace_back("xlink:actuate", OUString("onRequest"));
975         attributes.emplace_back("xlink:title", m_TemplateName);
976         attributes.emplace_back("xlink:href", m_TemplateURL );
977         if (isValidDateTime(m_TemplateDate)) {
978             attributes.emplace_back(
979                 "meta:date", dateTimeToText(m_TemplateDate));
980         }
981         updateElement("meta:template", &attributes);
982     } else {
983         updateElement("meta:template");
984     }
985     attributes.clear();
986 
987     if (!m_AutoloadURL.isEmpty() || (0 != m_AutoloadSecs)) {
988         attributes.emplace_back("xlink:href", m_AutoloadURL );
989         attributes.emplace_back("meta:delay",
990                 durationToText(m_AutoloadSecs));
991         updateElement("meta:auto-reload", &attributes);
992     } else {
993         updateElement("meta:auto-reload");
994     }
995     attributes.clear();
996 
997     if (!m_DefaultTarget.isEmpty()) {
998         attributes.emplace_back(
999                 "office:target-frame-name",
1000                 m_DefaultTarget);
1001         // xlink:show: _blank -> new, any other value -> replace
1002         const char* show = m_DefaultTarget == "_blank" ? "new" : "replace";
1003         attributes.emplace_back(
1004                 "xlink:show",
1005                 OUString::createFromAscii(show));
1006         updateElement("meta:hyperlink-behaviour", &attributes);
1007     } else {
1008         updateElement("meta:hyperlink-behaviour");
1009     }
1010     attributes.clear();
1011 }
1012 
1013 // create empty DOM tree (XDocument)
1014 css::uno::Reference<css::xml::dom::XDocument>
createDOM() const1015 SfxDocumentMetaData::createDOM() const // throw (css::uno::RuntimeException)
1016 {
1017     css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder( css::xml::dom::DocumentBuilder::create(m_xContext) );
1018     css::uno::Reference<css::xml::dom::XDocument> xDoc = xBuilder->newDocument();
1019     if (!xDoc.is())
1020         throw css::uno::RuntimeException(
1021                 "SfxDocumentMetaData::createDOM: cannot create new document",
1022                 *const_cast<SfxDocumentMetaData*>(this));
1023     return xDoc;
1024 }
1025 
1026 void
checkInit() const1027 SfxDocumentMetaData::checkInit() const // throw (css::uno::RuntimeException)
1028 {
1029     if (!m_isInitialized) {
1030         throw css::uno::RuntimeException(
1031                 "SfxDocumentMetaData::checkInit: not initialized",
1032                 *const_cast<SfxDocumentMetaData*>(this));
1033     }
1034     assert(m_xDoc.is() && m_xParent.is());
1035 }
1036 
1037 // initialize state from DOM tree
init(const css::uno::Reference<css::xml::dom::XDocument> & i_xDoc)1038 void SfxDocumentMetaData::init(
1039         const css::uno::Reference<css::xml::dom::XDocument>& i_xDoc)
1040 {
1041     if (!i_xDoc.is())
1042         throw css::uno::RuntimeException("SfxDocumentMetaData::init: no DOM tree given", *this);
1043 
1044     css::uno::Reference<css::xml::xpath::XXPathAPI> xPath = css::xml::xpath::XPathAPI::create(m_xContext);
1045 
1046     m_isInitialized = false;
1047     m_xDoc = i_xDoc;
1048 
1049     // select nodes for standard meta data stuff
1050     xPath->registerNS("xlink", s_nsXLink);
1051     xPath->registerNS("dc", s_nsDC);
1052     xPath->registerNS("office", s_nsODF);
1053     xPath->registerNS("meta", s_nsODFMeta);
1054     // NB: we do not handle the single-XML-file ODF variant, which would
1055     //     have the root element office:document.
1056     //     The root of such documents must be converted in the importer!
1057     css::uno::Reference<css::xml::dom::XNode> xDocNode(
1058         m_xDoc, css::uno::UNO_QUERY_THROW);
1059     m_xParent.clear();
1060     try {
1061         m_xParent = xPath->selectSingleNode(xDocNode, "/child::office:document-meta/child::office:meta");
1062     } catch (const css::uno::Exception &) {
1063     }
1064 
1065     if (!m_xParent.is()) {
1066         // all this create/append stuff may throw DOMException
1067         try {
1068             css::uno::Reference<css::xml::dom::XElement> xRElem;
1069             css::uno::Reference<css::xml::dom::XNode> xNode(
1070                 i_xDoc->getFirstChild());
1071             while (xNode.is()) {
1072                 if (css::xml::dom::NodeType_ELEMENT_NODE ==xNode->getNodeType())
1073                 {
1074                     if ( xNode->getNamespaceURI() == s_nsODF && xNode->getLocalName() == "document-meta" )
1075                     {
1076                         xRElem.set(xNode, css::uno::UNO_QUERY_THROW);
1077                         break;
1078                     }
1079                     else
1080                     {
1081                         SAL_INFO("sfx.doc", "SfxDocumentMetaData::init(): "
1082                                 "deleting unexpected root element: "
1083                                 << xNode->getLocalName());
1084                         i_xDoc->removeChild(xNode);
1085                         xNode = i_xDoc->getFirstChild(); // start over
1086                     }
1087                 } else {
1088                     xNode = xNode->getNextSibling();
1089                 }
1090             }
1091             if (!xRElem.is()) {
1092                 xRElem = i_xDoc->createElementNS(
1093                     s_nsODF, "office:document-meta");
1094                 css::uno::Reference<css::xml::dom::XNode> xRNode(xRElem,
1095                     css::uno::UNO_QUERY_THROW);
1096                 i_xDoc->appendChild(xRNode);
1097             }
1098             xRElem->setAttributeNS(s_nsODF, "office:version", "1.0");
1099             // does not exist, otherwise m_xParent would not be null
1100             css::uno::Reference<css::xml::dom::XNode> xParent (
1101                 i_xDoc->createElementNS(s_nsODF, "office:meta"),
1102             css::uno::UNO_QUERY_THROW);
1103             xRElem->appendChild(xParent);
1104             m_xParent = xParent;
1105         } catch (const css::xml::dom::DOMException &) {
1106             css::uno::Any anyEx = cppu::getCaughtException();
1107             throw css::lang::WrappedTargetRuntimeException(
1108                     "SfxDocumentMetaData::init: DOM exception",
1109                     css::uno::Reference<css::uno::XInterface>(*this), anyEx);
1110         }
1111     }
1112 
1113 
1114     // select nodes for elements of which we only handle one occurrence
1115     for (const char **pName = s_stdMeta; *pName != nullptr; ++pName) {
1116         OUString name = OUString::createFromAscii(*pName);
1117         // NB: If a document contains more than one occurrence of a
1118         // meta-data element, we arbitrarily pick one of them here.
1119         // We do not remove the others, i.e., when we write the
1120         // document, it will contain the duplicates unchanged.
1121         // The ODF spec says that handling multiple occurrences is
1122         // application-specific.
1123         css::uno::Reference<css::xml::dom::XNode> xNode =
1124             xPath->selectSingleNode(m_xParent, "child::" + name);
1125         // Do not create an empty element if it is missing;
1126         // for certain elements, such as dateTime, this would be invalid
1127         m_meta[name] = xNode;
1128     }
1129 
1130     // select nodes for elements of which we handle all occurrences
1131     for (const char **pName = s_stdMetaList; *pName != nullptr; ++pName) {
1132         OUString name = OUString::createFromAscii(*pName);
1133         css::uno::Reference<css::xml::dom::XNodeList> nodes =
1134             xPath->selectNodeList(m_xParent, "child::" + name);
1135         std::vector<css::uno::Reference<css::xml::dom::XNode> > v;
1136         v.reserve(nodes->getLength());
1137         for (sal_Int32 i = 0; i < nodes->getLength(); ++i)
1138         {
1139             v.push_back(nodes->item(i));
1140         }
1141         m_metaList[name] = v;
1142     }
1143 
1144     // initialize members corresponding to attributes from DOM nodes
1145     m_TemplateName  = getMetaAttr("meta:template", "xlink:title");
1146     m_TemplateURL   = getMetaAttr("meta:template", "xlink:href");
1147     m_TemplateDate  =
1148         textToDateTimeDefault(getMetaAttr("meta:template", "meta:date"));
1149     m_AutoloadURL   = getMetaAttr("meta:auto-reload", "xlink:href");
1150     m_AutoloadSecs  =
1151         textToDuration(getMetaAttr("meta:auto-reload", "meta:delay"));
1152     m_DefaultTarget =
1153         getMetaAttr("meta:hyperlink-behaviour", "office:target-frame-name");
1154 
1155 
1156     std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
1157         m_metaList[OUString("meta:user-defined")];
1158     m_xUserDefined.clear(); // #i105826#: reset (may be re-initialization)
1159     if ( !vec.empty() )
1160     {
1161         createUserDefined();
1162     }
1163 
1164     // user-defined meta data: initialize PropertySet from DOM nodes
1165     for (auto const& elem : vec)
1166     {
1167         css::uno::Reference<css::xml::dom::XElement> xElem(elem,
1168             css::uno::UNO_QUERY_THROW);
1169         css::uno::Any any;
1170         OUString name = xElem->getAttributeNS(s_nsODFMeta, "name");
1171         OUString type = xElem->getAttributeNS(s_nsODFMeta, "value-type");
1172         OUString text = getNodeText(elem);
1173         if ( type == "float" ) {
1174             double d;
1175             if (::sax::Converter::convertDouble(d, text)) {
1176                 any <<= d;
1177             } else {
1178                 SAL_WARN("sfx.doc", "Invalid float: " << text);
1179                 continue;
1180             }
1181         } else if ( type == "date" ) {
1182             bool isDateTime;
1183             css::util::Date d;
1184             css::util::DateTime dt;
1185             std::optional<sal_Int16> nTimeZone;
1186             if (textToDateOrDateTime(d, dt, isDateTime, nTimeZone, text)) {
1187                 if (isDateTime) {
1188                     if (nTimeZone) {
1189                         any <<= css::util::DateTimeWithTimezone(dt,
1190                                     *nTimeZone);
1191                     } else {
1192                         any <<= dt;
1193                     }
1194                 } else {
1195                     if (nTimeZone) {
1196                         any <<= css::util::DateWithTimezone(d, *nTimeZone);
1197                     } else {
1198                         any <<= d;
1199                     }
1200                 }
1201             } else {
1202                 SAL_WARN("sfx.doc", "Invalid date: " << text);
1203                 continue;
1204             }
1205         } else if ( type == "time" ) {
1206             css::util::Duration ud;
1207             if (textToDuration(ud, text)) {
1208                 any <<= ud;
1209             } else {
1210                 SAL_WARN("sfx.doc", "Invalid time: " << text);
1211                 continue;
1212             }
1213         } else if ( type == "boolean" ) {
1214             bool b;
1215             if (::sax::Converter::convertBool(b, text)) {
1216                 any <<= b;
1217             } else {
1218                 SAL_WARN("sfx.doc", "Invalid boolean: " << text);
1219                 continue;
1220             }
1221         } else { // default
1222             any <<= text;
1223         }
1224         try {
1225             m_xUserDefined->addProperty(name,
1226                 css::beans::PropertyAttribute::REMOVABLE, any);
1227         } catch (const css::beans::PropertyExistException &) {
1228             SAL_WARN("sfx.doc", "Duplicate: " << name);
1229             // ignore; duplicate
1230         } catch (const css::beans::IllegalTypeException &) {
1231             SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal type: " << name);
1232         } catch (const css::lang::IllegalArgumentException &) {
1233             SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal arg: " << name);
1234         }
1235     }
1236 
1237     m_isModified = false;
1238     m_isInitialized = true;
1239 }
1240 
1241 
SfxDocumentMetaData(css::uno::Reference<css::uno::XComponentContext> const & context)1242 SfxDocumentMetaData::SfxDocumentMetaData(
1243         css::uno::Reference< css::uno::XComponentContext > const & context)
1244     : BaseMutex()
1245     , SfxDocumentMetaData_Base(m_aMutex)
1246     , m_xContext(context)
1247     , m_NotifyListeners(m_aMutex)
1248     , m_isInitialized(false)
1249     , m_isModified(false)
1250     , m_AutoloadSecs(0)
1251 {
1252     assert(context.is());
1253     assert(context->getServiceManager().is());
1254     init(createDOM());
1255 }
1256 
1257 // com.sun.star.uno.XServiceInfo:
1258 OUString SAL_CALL
getImplementationName()1259 SfxDocumentMetaData::getImplementationName()
1260 {
1261     return "SfxDocumentMetaData";
1262 }
1263 
1264 sal_Bool SAL_CALL
supportsService(OUString const & serviceName)1265 SfxDocumentMetaData::supportsService(OUString const & serviceName)
1266 {
1267     return cppu::supportsService(this, serviceName);
1268 }
1269 
1270 css::uno::Sequence< OUString > SAL_CALL
getSupportedServiceNames()1271 SfxDocumentMetaData::getSupportedServiceNames()
1272 {
1273     css::uno::Sequence< OUString > s { "com.sun.star.document.DocumentProperties" };
1274     return s;
1275 }
1276 
1277 
1278 // css::lang::XComponent:
dispose()1279 void SAL_CALL SfxDocumentMetaData::dispose()
1280 {
1281     ::osl::MutexGuard g(m_aMutex);
1282     if (!m_isInitialized) {
1283         return;
1284     }
1285     WeakComponentImplHelperBase::dispose(); // superclass
1286     m_NotifyListeners.disposeAndClear(css::lang::EventObject(
1287             static_cast< ::cppu::OWeakObject* >(this)));
1288     m_isInitialized = false;
1289     m_meta.clear();
1290     m_metaList.clear();
1291     m_xParent.clear();
1292     m_xDoc.clear();
1293     m_xUserDefined.clear();
1294 }
1295 
1296 
1297 // css::document::XDocumentProperties:
1298 OUString SAL_CALL
getAuthor()1299 SfxDocumentMetaData::getAuthor()
1300 {
1301     ::osl::MutexGuard g(m_aMutex);
1302     return getMetaText("meta:initial-creator");
1303 }
1304 
setAuthor(const OUString & the_value)1305 void SAL_CALL SfxDocumentMetaData::setAuthor(const OUString & the_value)
1306 {
1307     setMetaTextAndNotify("meta:initial-creator", the_value);
1308 }
1309 
1310 
1311 OUString SAL_CALL
getGenerator()1312 SfxDocumentMetaData::getGenerator()
1313 {
1314     ::osl::MutexGuard g(m_aMutex);
1315     return getMetaText("meta:generator");
1316 }
1317 
1318 void SAL_CALL
setGenerator(const OUString & the_value)1319 SfxDocumentMetaData::setGenerator(const OUString & the_value)
1320 {
1321     setMetaTextAndNotify("meta:generator", the_value);
1322 }
1323 
1324 css::util::DateTime SAL_CALL
getCreationDate()1325 SfxDocumentMetaData::getCreationDate()
1326 {
1327     ::osl::MutexGuard g(m_aMutex);
1328     return textToDateTimeDefault(getMetaText("meta:creation-date"));
1329 }
1330 
1331 void SAL_CALL
setCreationDate(const css::util::DateTime & the_value)1332 SfxDocumentMetaData::setCreationDate(const css::util::DateTime & the_value)
1333 {
1334     setMetaTextAndNotify("meta:creation-date", dateTimeToText(the_value));
1335 }
1336 
1337 OUString SAL_CALL
getTitle()1338 SfxDocumentMetaData::getTitle()
1339 {
1340     ::osl::MutexGuard g(m_aMutex);
1341     return getMetaText("dc:title");
1342 }
1343 
setTitle(const OUString & the_value)1344 void SAL_CALL SfxDocumentMetaData::setTitle(const OUString & the_value)
1345 {
1346     setMetaTextAndNotify("dc:title", the_value);
1347 }
1348 
1349 OUString SAL_CALL
getSubject()1350 SfxDocumentMetaData::getSubject()
1351 {
1352     ::osl::MutexGuard g(m_aMutex);
1353     return getMetaText("dc:subject");
1354 }
1355 
1356 void SAL_CALL
setSubject(const OUString & the_value)1357 SfxDocumentMetaData::setSubject(const OUString & the_value)
1358 {
1359     setMetaTextAndNotify("dc:subject", the_value);
1360 }
1361 
1362 OUString SAL_CALL
getDescription()1363 SfxDocumentMetaData::getDescription()
1364 {
1365     ::osl::MutexGuard g(m_aMutex);
1366     return getMetaText("dc:description");
1367 }
1368 
1369 void SAL_CALL
setDescription(const OUString & the_value)1370 SfxDocumentMetaData::setDescription(const OUString & the_value)
1371 {
1372     setMetaTextAndNotify("dc:description", the_value);
1373 }
1374 
1375 css::uno::Sequence< OUString >
getKeywords()1376 SAL_CALL SfxDocumentMetaData::getKeywords()
1377 {
1378     ::osl::MutexGuard g(m_aMutex);
1379     return getMetaList("meta:keyword");
1380 }
1381 
1382 void SAL_CALL
setKeywords(const css::uno::Sequence<OUString> & the_value)1383 SfxDocumentMetaData::setKeywords(
1384         const css::uno::Sequence< OUString > & the_value)
1385 {
1386     ::osl::ClearableMutexGuard g(m_aMutex);
1387     if (setMetaList("meta:keyword", the_value, nullptr)) {
1388         g.clear();
1389         setModified(true);
1390     }
1391 }
1392 
1393 css::lang::Locale SAL_CALL
getLanguage()1394         SfxDocumentMetaData::getLanguage()
1395 {
1396     ::osl::MutexGuard g(m_aMutex);
1397     css::lang::Locale loc( LanguageTag::convertToLocale( getMetaText("dc:language"), false));
1398     return loc;
1399 }
1400 
1401 void SAL_CALL
setLanguage(const css::lang::Locale & the_value)1402 SfxDocumentMetaData::setLanguage(const css::lang::Locale & the_value)
1403 {
1404     OUString text( LanguageTag::convertToBcp47( the_value, false));
1405     setMetaTextAndNotify("dc:language", text);
1406 }
1407 
1408 OUString SAL_CALL
getModifiedBy()1409 SfxDocumentMetaData::getModifiedBy()
1410 {
1411     ::osl::MutexGuard g(m_aMutex);
1412     return getMetaText("dc:creator");
1413 }
1414 
1415 void SAL_CALL
setModifiedBy(const OUString & the_value)1416 SfxDocumentMetaData::setModifiedBy(const OUString & the_value)
1417 {
1418     setMetaTextAndNotify("dc:creator", the_value);
1419 }
1420 
1421 css::util::DateTime SAL_CALL
getModificationDate()1422 SfxDocumentMetaData::getModificationDate()
1423 {
1424     ::osl::MutexGuard g(m_aMutex);
1425     return textToDateTimeDefault(getMetaText("dc:date"));
1426 }
1427 
1428 void SAL_CALL
setModificationDate(const css::util::DateTime & the_value)1429 SfxDocumentMetaData::setModificationDate(const css::util::DateTime & the_value)
1430 {
1431     setMetaTextAndNotify("dc:date", dateTimeToText(the_value));
1432 }
1433 
1434 OUString SAL_CALL
getPrintedBy()1435 SfxDocumentMetaData::getPrintedBy()
1436 {
1437     ::osl::MutexGuard g(m_aMutex);
1438     return getMetaText("meta:printed-by");
1439 }
1440 
1441 void SAL_CALL
setPrintedBy(const OUString & the_value)1442 SfxDocumentMetaData::setPrintedBy(const OUString & the_value)
1443 {
1444     setMetaTextAndNotify("meta:printed-by", the_value);
1445 }
1446 
1447 css::util::DateTime SAL_CALL
getPrintDate()1448 SfxDocumentMetaData::getPrintDate()
1449 {
1450     ::osl::MutexGuard g(m_aMutex);
1451     return textToDateTimeDefault(getMetaText("meta:print-date"));
1452 }
1453 
1454 void SAL_CALL
setPrintDate(const css::util::DateTime & the_value)1455 SfxDocumentMetaData::setPrintDate(const css::util::DateTime & the_value)
1456 {
1457     setMetaTextAndNotify("meta:print-date", dateTimeToText(the_value));
1458 }
1459 
1460 OUString SAL_CALL
getTemplateName()1461 SfxDocumentMetaData::getTemplateName()
1462 {
1463     ::osl::MutexGuard g(m_aMutex);
1464     checkInit();
1465     return m_TemplateName;
1466 }
1467 
1468 void SAL_CALL
setTemplateName(const OUString & the_value)1469 SfxDocumentMetaData::setTemplateName(const OUString & the_value)
1470 {
1471     ::osl::ClearableMutexGuard g(m_aMutex);
1472     checkInit();
1473     if (m_TemplateName != the_value) {
1474         m_TemplateName = the_value;
1475         g.clear();
1476         setModified(true);
1477     }
1478 }
1479 
1480 OUString SAL_CALL
getTemplateURL()1481 SfxDocumentMetaData::getTemplateURL()
1482 {
1483     ::osl::MutexGuard g(m_aMutex);
1484     checkInit();
1485     return m_TemplateURL;
1486 }
1487 
1488 void SAL_CALL
setTemplateURL(const OUString & the_value)1489 SfxDocumentMetaData::setTemplateURL(const OUString & the_value)
1490 {
1491     ::osl::ClearableMutexGuard g(m_aMutex);
1492     checkInit();
1493     if (m_TemplateURL != the_value) {
1494         m_TemplateURL = the_value;
1495         g.clear();
1496         setModified(true);
1497     }
1498 }
1499 
1500 css::util::DateTime SAL_CALL
getTemplateDate()1501 SfxDocumentMetaData::getTemplateDate()
1502 {
1503     ::osl::MutexGuard g(m_aMutex);
1504     checkInit();
1505     return m_TemplateDate;
1506 }
1507 
1508 void SAL_CALL
setTemplateDate(const css::util::DateTime & the_value)1509 SfxDocumentMetaData::setTemplateDate(const css::util::DateTime & the_value)
1510 {
1511     ::osl::ClearableMutexGuard g(m_aMutex);
1512     checkInit();
1513     if (m_TemplateDate != the_value) {
1514         m_TemplateDate = the_value;
1515         g.clear();
1516         setModified(true);
1517     }
1518 }
1519 
1520 OUString SAL_CALL
getAutoloadURL()1521 SfxDocumentMetaData::getAutoloadURL()
1522 {
1523     ::osl::MutexGuard g(m_aMutex);
1524     checkInit();
1525     return m_AutoloadURL;
1526 }
1527 
1528 void SAL_CALL
setAutoloadURL(const OUString & the_value)1529 SfxDocumentMetaData::setAutoloadURL(const OUString & the_value)
1530 {
1531     ::osl::ClearableMutexGuard g(m_aMutex);
1532     checkInit();
1533     if (m_AutoloadURL != the_value) {
1534         m_AutoloadURL = the_value;
1535         g.clear();
1536         setModified(true);
1537     }
1538 }
1539 
1540 ::sal_Int32 SAL_CALL
getAutoloadSecs()1541 SfxDocumentMetaData::getAutoloadSecs()
1542 {
1543     ::osl::MutexGuard g(m_aMutex);
1544     checkInit();
1545     return m_AutoloadSecs;
1546 }
1547 
1548 void SAL_CALL
setAutoloadSecs(::sal_Int32 the_value)1549 SfxDocumentMetaData::setAutoloadSecs(::sal_Int32 the_value)
1550 {
1551     if (the_value < 0)
1552         throw css::lang::IllegalArgumentException(
1553             "SfxDocumentMetaData::setAutoloadSecs: argument is negative",
1554             *this, 0);
1555     ::osl::ClearableMutexGuard g(m_aMutex);
1556     checkInit();
1557     if (m_AutoloadSecs != the_value) {
1558         m_AutoloadSecs = the_value;
1559         g.clear();
1560         setModified(true);
1561     }
1562 }
1563 
1564 OUString SAL_CALL
getDefaultTarget()1565 SfxDocumentMetaData::getDefaultTarget()
1566 {
1567     ::osl::MutexGuard g(m_aMutex);
1568     checkInit();
1569     return m_DefaultTarget;
1570 }
1571 
1572 void SAL_CALL
setDefaultTarget(const OUString & the_value)1573 SfxDocumentMetaData::setDefaultTarget(const OUString & the_value)
1574 {
1575     ::osl::ClearableMutexGuard g(m_aMutex);
1576     checkInit();
1577     if (m_DefaultTarget != the_value) {
1578         m_DefaultTarget = the_value;
1579         g.clear();
1580         setModified(true);
1581     }
1582 }
1583 
1584 css::uno::Sequence< css::beans::NamedValue > SAL_CALL
getDocumentStatistics()1585 SfxDocumentMetaData::getDocumentStatistics()
1586 {
1587     ::osl::MutexGuard g(m_aMutex);
1588     checkInit();
1589     ::std::vector<css::beans::NamedValue> stats;
1590     for (size_t i = 0; s_stdStats[i] != nullptr; ++i) {
1591         const char * aName = s_stdStatAttrs[i];
1592         OUString text = getMetaAttr("meta:document-statistic", aName);
1593         if (text.isEmpty()) continue;
1594         css::beans::NamedValue stat;
1595         stat.Name = OUString::createFromAscii(s_stdStats[i]);
1596         sal_Int32 val;
1597         css::uno::Any any;
1598         if (!::sax::Converter::convertNumber(val, text, 0) || (val < 0)) {
1599             val = 0;
1600             SAL_WARN("sfx.doc", "Invalid number: " << text);
1601         }
1602         any <<= val;
1603         stat.Value = any;
1604         stats.push_back(stat);
1605     }
1606 
1607     return ::comphelper::containerToSequence(stats);
1608 }
1609 
1610 void SAL_CALL
setDocumentStatistics(const css::uno::Sequence<css::beans::NamedValue> & the_value)1611 SfxDocumentMetaData::setDocumentStatistics(
1612         const css::uno::Sequence< css::beans::NamedValue > & the_value)
1613 {
1614     {
1615         osl::MutexGuard g(m_aMutex);
1616         checkInit();
1617         std::vector<std::pair<const char *, OUString> > attributes;
1618         for (const auto& rValue : the_value) {
1619             const OUString name = rValue.Name;
1620             // inefficiently search for matching attribute
1621             for (size_t j = 0; s_stdStats[j] != nullptr; ++j) {
1622                 if (name.equalsAscii(s_stdStats[j])) {
1623                     const css::uno::Any any = rValue.Value;
1624                     sal_Int32 val = 0;
1625                     if (any >>= val) {
1626                         attributes.emplace_back(s_stdStatAttrs[j],
1627                             OUString::number(val));
1628                     }
1629                     else {
1630                         SAL_WARN("sfx.doc", "Invalid statistic: " << name);
1631                     }
1632                     break;
1633                 }
1634             }
1635         }
1636         updateElement("meta:document-statistic", &attributes);
1637     }
1638     setModified(true);
1639 }
1640 
1641 ::sal_Int16 SAL_CALL
getEditingCycles()1642 SfxDocumentMetaData::getEditingCycles()
1643 {
1644     ::osl::MutexGuard g(m_aMutex);
1645     OUString text = getMetaText("meta:editing-cycles");
1646     sal_Int32 ret;
1647     if (::sax::Converter::convertNumber(ret, text,
1648             0, std::numeric_limits<sal_Int16>::max())) {
1649         return static_cast<sal_Int16>(ret);
1650     } else {
1651         return 0;
1652     }
1653 }
1654 
1655 void SAL_CALL
setEditingCycles(::sal_Int16 the_value)1656 SfxDocumentMetaData::setEditingCycles(::sal_Int16 the_value)
1657 {
1658     if (the_value < 0)
1659         throw css::lang::IllegalArgumentException(
1660             "SfxDocumentMetaData::setEditingCycles: argument is negative",
1661             *this, 0);
1662     setMetaTextAndNotify("meta:editing-cycles", OUString::number(the_value));
1663 }
1664 
1665 ::sal_Int32 SAL_CALL
getEditingDuration()1666 SfxDocumentMetaData::getEditingDuration()
1667 {
1668     ::osl::MutexGuard g(m_aMutex);
1669     return textToDuration(getMetaText("meta:editing-duration"));
1670 }
1671 
1672 void SAL_CALL
setEditingDuration(::sal_Int32 the_value)1673 SfxDocumentMetaData::setEditingDuration(::sal_Int32 the_value)
1674 {
1675     if (the_value < 0)
1676         throw css::lang::IllegalArgumentException(
1677             "SfxDocumentMetaData::setEditingDuration: argument is negative",
1678             *this, 0);
1679     setMetaTextAndNotify("meta:editing-duration", durationToText(the_value));
1680 }
1681 
1682 void SAL_CALL
resetUserData(const OUString & the_value)1683 SfxDocumentMetaData::resetUserData(const OUString & the_value)
1684 {
1685     ::osl::ClearableMutexGuard g(m_aMutex);
1686 
1687     bool bModified( false );
1688     bModified |= setMetaText("meta:initial-creator", the_value);
1689     ::DateTime now( ::DateTime::SYSTEM );
1690     css::util::DateTime uDT(now.GetUNODateTime());
1691     bModified |= setMetaText("meta:creation-date", dateTimeToText(uDT));
1692     bModified |= setMetaText("dc:creator", OUString());
1693     bModified |= setMetaText("meta:printed-by", OUString());
1694     bModified |= setMetaText("dc:date", dateTimeToText(css::util::DateTime()));
1695     bModified |= setMetaText("meta:print-date",
1696         dateTimeToText(css::util::DateTime()));
1697     bModified |= setMetaText("meta:editing-duration", durationToText(0));
1698     bModified |= setMetaText("meta:editing-cycles",
1699         "1");
1700 
1701     if (bModified) {
1702         g.clear();
1703         setModified(true);
1704     }
1705 }
1706 
1707 
1708 css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
getUserDefinedProperties()1709 SfxDocumentMetaData::getUserDefinedProperties()
1710 {
1711     ::osl::MutexGuard g(m_aMutex);
1712     checkInit();
1713     createUserDefined();
1714     return m_xUserDefined;
1715 }
1716 
1717 
1718 void SAL_CALL
loadFromStorage(const css::uno::Reference<css::embed::XStorage> & xStorage,const css::uno::Sequence<css::beans::PropertyValue> & Medium)1719 SfxDocumentMetaData::loadFromStorage(
1720         const css::uno::Reference< css::embed::XStorage > & xStorage,
1721         const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1722 {
1723     if (!xStorage.is())
1724         throw css::lang::IllegalArgumentException("SfxDocumentMetaData::loadFromStorage: argument is null", *this, 0);
1725     ::osl::MutexGuard g(m_aMutex);
1726 
1727     // open meta data file
1728     css::uno::Reference<css::io::XStream> xStream(
1729         xStorage->openStreamElement(
1730             s_meta,
1731             css::embed::ElementModes::READ) );
1732     if (!xStream.is()) throw css::uno::RuntimeException();
1733     css::uno::Reference<css::io::XInputStream> xInStream =
1734         xStream->getInputStream();
1735     if (!xInStream.is()) throw css::uno::RuntimeException();
1736 
1737     // create DOM parser service
1738     css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
1739         m_xContext->getServiceManager());
1740     css::xml::sax::InputSource input;
1741     input.aInputStream = xInStream;
1742 
1743     sal_uInt64 version = SotStorage::GetVersion( xStorage );
1744     // Oasis is also the default (0)
1745     bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
1746     const char *pServiceName = bOasis
1747         ? "com.sun.star.document.XMLOasisMetaImporter"
1748         : "com.sun.star.document.XMLMetaImporter";
1749 
1750     // set base URL
1751     css::uno::Reference<css::beans::XPropertySet> xPropArg =
1752         getURLProperties(Medium);
1753     try {
1754         xPropArg->getPropertyValue("BaseURI")
1755             >>= input.sSystemId;
1756         input.sSystemId += OUString::Concat("/") + s_meta;
1757     } catch (const css::uno::Exception &) {
1758         input.sSystemId = s_meta;
1759     }
1760     css::uno::Sequence< css::uno::Any > args(1);
1761     args[0] <<= xPropArg;
1762 
1763     // the underlying SvXMLImport implements XFastParser, XImporter, XFastDocumentHandler
1764     css::uno::Reference<XInterface> xFilter =
1765         xMsf->createInstanceWithArgumentsAndContext(
1766             OUString::createFromAscii(pServiceName), args, m_xContext);
1767     assert(xFilter);
1768     css::uno::Reference<css::xml::sax::XFastParser> xFastParser(xFilter, css::uno::UNO_QUERY);
1769     css::uno::Reference<css::document::XImporter> xImp(xFilter, css::uno::UNO_QUERY_THROW);
1770     xImp->setTargetDocument(css::uno::Reference<css::lang::XComponent>(this));
1771     try {
1772         if (xFastParser)
1773             xFastParser->parseStream(input);
1774         else
1775         {
1776             css::uno::Reference<css::xml::sax::XDocumentHandler> xDocHandler(xFilter, css::uno::UNO_QUERY_THROW);
1777             css::uno::Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(m_xContext);
1778             xParser->setDocumentHandler(xDocHandler);
1779             xParser->parseStream(input);
1780         }
1781     } catch (const css::xml::sax::SAXException &) {
1782         throw css::io::WrongFormatException(
1783                 "SfxDocumentMetaData::loadFromStorage:"
1784                 " XML parsing exception", *this);
1785     }
1786     // NB: the implementation of XMLOasisMetaImporter calls initialize
1787     checkInit();
1788 }
1789 
1790 void SAL_CALL
storeToStorage(const css::uno::Reference<css::embed::XStorage> & xStorage,const css::uno::Sequence<css::beans::PropertyValue> & Medium)1791 SfxDocumentMetaData::storeToStorage(
1792         const css::uno::Reference< css::embed::XStorage > & xStorage,
1793         const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1794 {
1795     if (!xStorage.is())
1796         throw css::lang::IllegalArgumentException(
1797             "SfxDocumentMetaData::storeToStorage: argument is null", *this, 0);
1798     ::osl::MutexGuard g(m_aMutex);
1799     checkInit();
1800 
1801     // update user-defined meta data in DOM tree
1802 //    updateUserDefinedAndAttributes(); // this will be done in serialize!
1803 
1804     // write into storage
1805     css::uno::Reference<css::io::XStream> xStream =
1806         xStorage->openStreamElement(s_meta,
1807             css::embed::ElementModes::WRITE
1808             | css::embed::ElementModes::TRUNCATE);
1809     if (!xStream.is()) throw css::uno::RuntimeException();
1810     css::uno::Reference< css::beans::XPropertySet > xStreamProps(xStream,
1811         css::uno::UNO_QUERY_THROW);
1812     xStreamProps->setPropertyValue(
1813         "MediaType",
1814         css::uno::makeAny(OUString("text/xml")));
1815     xStreamProps->setPropertyValue(
1816         "Compressed",
1817         css::uno::makeAny(false));
1818     xStreamProps->setPropertyValue(
1819         "UseCommonStoragePasswordEncryption",
1820         css::uno::makeAny(false));
1821     css::uno::Reference<css::io::XOutputStream> xOutStream =
1822         xStream->getOutputStream();
1823     if (!xOutStream.is()) throw css::uno::RuntimeException();
1824     css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
1825         m_xContext->getServiceManager());
1826     css::uno::Reference<css::xml::sax::XWriter> xSaxWriter(
1827         css::xml::sax::Writer::create(m_xContext));
1828     xSaxWriter->setOutputStream(xOutStream);
1829 
1830     const sal_uInt64 version = SotStorage::GetVersion( xStorage );
1831     // Oasis is also the default (0)
1832     const bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
1833     const char *pServiceName = bOasis
1834         ? "com.sun.star.document.XMLOasisMetaExporter"
1835         : "com.sun.star.document.XMLMetaExporter";
1836 
1837     // set base URL
1838     css::uno::Reference<css::beans::XPropertySet> xPropArg =
1839         getURLProperties(Medium);
1840     css::uno::Sequence< css::uno::Any > args(2);
1841     args[0] <<= xSaxWriter;
1842     args[1] <<= xPropArg;
1843 
1844     css::uno::Reference<css::document::XExporter> xExp(
1845         xMsf->createInstanceWithArgumentsAndContext(
1846             OUString::createFromAscii(pServiceName), args, m_xContext),
1847         css::uno::UNO_QUERY_THROW);
1848     xExp->setSourceDocument(css::uno::Reference<css::lang::XComponent>(this));
1849     css::uno::Reference<css::document::XFilter> xFilter(xExp,
1850         css::uno::UNO_QUERY_THROW);
1851     if (!xFilter->filter(css::uno::Sequence< css::beans::PropertyValue >())) {
1852         throw css::io::IOException(
1853                 "SfxDocumentMetaData::storeToStorage: cannot filter", *this);
1854     }
1855     css::uno::Reference<css::embed::XTransactedObject> xTransaction(
1856         xStorage, css::uno::UNO_QUERY);
1857     if (xTransaction.is()) {
1858         xTransaction->commit();
1859     }
1860 }
1861 
1862 void SAL_CALL
loadFromMedium(const OUString & URL,const css::uno::Sequence<css::beans::PropertyValue> & Medium)1863 SfxDocumentMetaData::loadFromMedium(const OUString & URL,
1864         const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1865 {
1866     css::uno::Reference<css::io::XInputStream> xIn;
1867     utl::MediaDescriptor md(Medium);
1868     // if we have a URL parameter, it replaces the one in the media descriptor
1869     if (!URL.isEmpty()) {
1870         md[ utl::MediaDescriptor::PROP_URL() ] <<= URL;
1871         md[ utl::MediaDescriptor::PROP_READONLY() ] <<= true;
1872     }
1873     if (md.addInputStream()) {
1874         md[ utl::MediaDescriptor::PROP_INPUTSTREAM() ] >>= xIn;
1875     }
1876     css::uno::Reference<css::embed::XStorage> xStorage;
1877     try {
1878         if (xIn.is()) {
1879             xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
1880                             xIn, m_xContext);
1881         } else { // fallback to url parameter
1882             xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
1883                             URL, css::embed::ElementModes::READ, m_xContext);
1884         }
1885     } catch (const css::uno::RuntimeException &) {
1886         throw;
1887     } catch (const css::io::IOException &) {
1888         throw;
1889     } catch (const css::uno::Exception &) {
1890         css::uno::Any anyEx = cppu::getCaughtException();
1891         throw css::lang::WrappedTargetException(
1892                 "SfxDocumentMetaData::loadFromMedium: exception",
1893                 css::uno::Reference<css::uno::XInterface>(*this),
1894                 anyEx);
1895     }
1896     if (!xStorage.is()) {
1897         throw css::uno::RuntimeException(
1898                 "SfxDocumentMetaData::loadFromMedium: cannot get Storage",
1899                 *this);
1900     }
1901     loadFromStorage(xStorage, md.getAsConstPropertyValueList());
1902 }
1903 
1904 void SAL_CALL
storeToMedium(const OUString & URL,const css::uno::Sequence<css::beans::PropertyValue> & Medium)1905 SfxDocumentMetaData::storeToMedium(const OUString & URL,
1906         const css::uno::Sequence< css::beans::PropertyValue > & Medium)
1907 {
1908     utl::MediaDescriptor md(Medium);
1909     if (!URL.isEmpty()) {
1910         md[ utl::MediaDescriptor::PROP_URL() ] <<= URL;
1911     }
1912     SfxMedium aMedium(md.getAsConstPropertyValueList());
1913     css::uno::Reference<css::embed::XStorage> xStorage
1914         = aMedium.GetOutputStorage();
1915 
1916 
1917     if (!xStorage.is()) {
1918         throw css::uno::RuntimeException(
1919                 "SfxDocumentMetaData::storeToMedium: cannot get Storage",
1920                 *this);
1921     }
1922     // set MIME type of the storage
1923     utl::MediaDescriptor::const_iterator iter
1924         = md.find(utl::MediaDescriptor::PROP_MEDIATYPE());
1925     if (iter != md.end()) {
1926         css::uno::Reference< css::beans::XPropertySet > xProps(xStorage,
1927             css::uno::UNO_QUERY_THROW);
1928         xProps->setPropertyValue(
1929             utl::MediaDescriptor::PROP_MEDIATYPE(),
1930             iter->second);
1931     }
1932     storeToStorage(xStorage, md.getAsConstPropertyValueList());
1933 
1934 
1935     const bool bOk = aMedium.Commit();
1936     aMedium.Close();
1937     if ( !bOk ) {
1938         ErrCode nError = aMedium.GetError();
1939         if ( nError == ERRCODE_NONE ) {
1940             nError = ERRCODE_IO_GENERAL;
1941         }
1942 
1943         throw css::task::ErrorCodeIOException(
1944             "SfxDocumentMetaData::storeToMedium <" + URL + "> Commit failed: " + nError.toHexString(),
1945             css::uno::Reference< css::uno::XInterface >(), sal_uInt32(nError));
1946 
1947     }
1948 }
1949 
1950 // css::lang::XInitialization:
initialize(const css::uno::Sequence<css::uno::Any> & aArguments)1951 void SAL_CALL SfxDocumentMetaData::initialize( const css::uno::Sequence< css::uno::Any > & aArguments)
1952 {
1953     // possible arguments:
1954     // - no argument: default initialization (empty DOM)
1955     // - 1 argument, XDocument: initialize with given DOM and empty base URL
1956     // NB: links in document must be absolute
1957 
1958     ::osl::MutexGuard g(m_aMutex);
1959     css::uno::Reference<css::xml::dom::XDocument> xDoc;
1960 
1961     for (sal_Int32 i = 0; i < aArguments.getLength(); ++i) {
1962         const css::uno::Any any = aArguments[i];
1963         if (!(any >>= xDoc)) {
1964             throw css::lang::IllegalArgumentException(
1965                 "SfxDocumentMetaData::initialize: argument must be XDocument",
1966                 *this, static_cast<sal_Int16>(i));
1967         }
1968         if (!xDoc.is()) {
1969             throw css::lang::IllegalArgumentException(
1970                 "SfxDocumentMetaData::initialize: argument is null",
1971                 *this, static_cast<sal_Int16>(i));
1972         }
1973     }
1974 
1975     if (!xDoc.is()) {
1976         // For a new document, we create a new DOM tree here.
1977         xDoc = createDOM();
1978     }
1979 
1980     init(xDoc);
1981 }
1982 
1983 // css::util::XCloneable:
1984 css::uno::Reference<css::util::XCloneable> SAL_CALL
createClone()1985 SfxDocumentMetaData::createClone()
1986 {
1987     ::osl::MutexGuard g(m_aMutex);
1988     checkInit();
1989 
1990     rtl::Reference<SfxDocumentMetaData> pNew = createMe(m_xContext);
1991 
1992     // NB: do not copy the modification listeners, only DOM
1993     css::uno::Reference<css::xml::dom::XDocument> xDoc = createDOM();
1994     try {
1995         updateUserDefinedAndAttributes();
1996         // deep copy of root node
1997         css::uno::Reference<css::xml::dom::XNode> xRoot(
1998             m_xDoc->getDocumentElement(), css::uno::UNO_QUERY_THROW);
1999         css::uno::Reference<css::xml::dom::XNode> xRootNew(
2000             xDoc->importNode(xRoot, true));
2001         xDoc->appendChild(xRootNew);
2002         pNew->init(xDoc);
2003     } catch (const css::uno::RuntimeException &) {
2004         throw;
2005     } catch (const css::uno::Exception &) {
2006         css::uno::Any anyEx = cppu::getCaughtException();
2007         throw css::lang::WrappedTargetRuntimeException(
2008                 "SfxDocumentMetaData::createClone: exception",
2009                 css::uno::Reference<css::uno::XInterface>(*this), anyEx);
2010     }
2011     return css::uno::Reference<css::util::XCloneable> (pNew);
2012 }
2013 
2014 // css::util::XModifiable:
isModified()2015 sal_Bool SAL_CALL SfxDocumentMetaData::isModified(  )
2016 {
2017     ::osl::MutexGuard g(m_aMutex);
2018     checkInit();
2019     css::uno::Reference<css::util::XModifiable> xMB(m_xUserDefined,
2020         css::uno::UNO_QUERY);
2021     return m_isModified || (xMB.is() && xMB->isModified());
2022 }
2023 
setModified(sal_Bool bModified)2024 void SAL_CALL SfxDocumentMetaData::setModified( sal_Bool bModified )
2025 {
2026     css::uno::Reference<css::util::XModifiable> xMB;
2027     { // do not lock mutex while notifying (#i93514#) to prevent deadlock
2028         ::osl::MutexGuard g(m_aMutex);
2029         checkInit();
2030         m_isModified = bModified;
2031         if ( !bModified && m_xUserDefined.is() )
2032         {
2033             xMB.set(m_xUserDefined, css::uno::UNO_QUERY);
2034             assert(xMB.is() &&
2035                 "SfxDocumentMetaData::setModified: PropertyBag not Modifiable?");
2036         }
2037     }
2038     if (bModified) {
2039         try {
2040             css::uno::Reference<css::uno::XInterface> xThis(*this);
2041             css::lang::EventObject event(xThis);
2042             m_NotifyListeners.notifyEach(&css::util::XModifyListener::modified,
2043                 event);
2044         } catch (const css::uno::RuntimeException &) {
2045             throw;
2046         } catch (const css::uno::Exception &) {
2047             // ignore
2048             TOOLS_WARN_EXCEPTION("sfx.doc", "setModified");
2049         }
2050     } else {
2051         if (xMB.is()) {
2052             xMB->setModified(false);
2053         }
2054     }
2055 }
2056 
2057 // css::util::XModifyBroadcaster:
addModifyListener(const css::uno::Reference<css::util::XModifyListener> & xListener)2058 void SAL_CALL SfxDocumentMetaData::addModifyListener(
2059         const css::uno::Reference< css::util::XModifyListener > & xListener)
2060 {
2061     ::osl::MutexGuard g(m_aMutex);
2062     checkInit();
2063     m_NotifyListeners.addInterface(xListener);
2064     css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
2065         css::uno::UNO_QUERY);
2066     if (xMB.is()) {
2067         xMB->addModifyListener(xListener);
2068     }
2069 }
2070 
removeModifyListener(const css::uno::Reference<css::util::XModifyListener> & xListener)2071 void SAL_CALL SfxDocumentMetaData::removeModifyListener(
2072         const css::uno::Reference< css::util::XModifyListener > & xListener)
2073 {
2074     ::osl::MutexGuard g(m_aMutex);
2075     checkInit();
2076     m_NotifyListeners.removeInterface(xListener);
2077     css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
2078         css::uno::UNO_QUERY);
2079     if (xMB.is()) {
2080         xMB->removeModifyListener(xListener);
2081     }
2082 }
2083 
2084 // css::xml::sax::XSAXSerializable
serialize(const css::uno::Reference<css::xml::sax::XDocumentHandler> & i_xHandler,const css::uno::Sequence<css::beans::StringPair> & i_rNamespaces)2085 void SAL_CALL SfxDocumentMetaData::serialize(
2086     const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
2087     const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces)
2088 {
2089     ::osl::MutexGuard g(m_aMutex);
2090     checkInit();
2091     updateUserDefinedAndAttributes();
2092     css::uno::Reference<css::xml::sax::XSAXSerializable> xSAXable(m_xDoc,
2093         css::uno::UNO_QUERY_THROW);
2094     xSAXable->serialize(i_xHandler, i_rNamespaces);
2095 }
2096 
createUserDefined()2097 void SfxDocumentMetaData::createUserDefined()
2098 {
2099     // user-defined meta data: create PropertyBag which only accepts property
2100     // values of allowed types
2101     if ( m_xUserDefined.is() )
2102         return;
2103 
2104     css::uno::Sequence<css::uno::Type> types(13);
2105     types[ 0] = ::cppu::UnoType<bool>::get();
2106     types[ 1] = ::cppu::UnoType< OUString>::get();
2107     types[ 2] = ::cppu::UnoType<css::util::DateTime>::get();
2108     types[ 3] = ::cppu::UnoType<css::util::Date>::get();
2109     types[ 4] = ::cppu::UnoType<css::util::DateTimeWithTimezone>::get();
2110     types[ 5] = ::cppu::UnoType<css::util::DateWithTimezone>::get();
2111     types[ 6] = ::cppu::UnoType<css::util::Duration>::get();
2112     types[ 7] = ::cppu::UnoType<float>::get();
2113     types[ 8] = ::cppu::UnoType<double>::get();
2114     types[ 9] = ::cppu::UnoType<sal_Int16>::get();
2115     types[10] = ::cppu::UnoType<sal_Int32>::get();
2116     types[11] = ::cppu::UnoType<sal_Int64>::get();
2117     // Time is supported for backward compatibility with OOo 3.x, x<=2
2118     types[12] = ::cppu::UnoType<css::util::Time>::get();
2119     // #i94175#:  ODF allows empty user-defined property names!
2120     m_xUserDefined.set(
2121         css::beans::PropertyBag::createWithTypes( m_xContext, types, true/*AllowEmptyPropertyName*/, false/*AutomaticAddition*/ ),
2122         css::uno::UNO_QUERY_THROW);
2123 
2124     const css::uno::Reference<css::util::XModifyBroadcaster> xMB(
2125         m_xUserDefined, css::uno::UNO_QUERY);
2126     if (xMB.is())
2127     {
2128         const std::vector<css::uno::Reference<css::uno::XInterface> >
2129             listeners(m_NotifyListeners.getElements());
2130         for (const auto& l : listeners) {
2131             xMB->addModifyListener(
2132                 css::uno::Reference< css::util::XModifyListener >(l, css::uno::UNO_QUERY) );
2133         }
2134     }
2135 }
2136 
2137 } // closing anonymous implementation namespace
2138 
2139 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
CompatWriterDocPropsImpl_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)2140 CompatWriterDocPropsImpl_get_implementation(
2141     css::uno::XComponentContext *context,
2142     css::uno::Sequence<css::uno::Any> const &)
2143 {
2144     return cppu::acquire(new CompatWriterDocPropsImpl(context));
2145 }
2146 
2147 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
SfxDocumentMetaData_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)2148 SfxDocumentMetaData_get_implementation(
2149     css::uno::XComponentContext *context,
2150     css::uno::Sequence<css::uno::Any> const &)
2151 {
2152     return cppu::acquire(new SfxDocumentMetaData(context));
2153 }
2154 
2155 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2156