/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>
#include <sal/log.hxx>

#include <cppuhelper/compbase.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/util/XCloneable.hpp>
#include <com/sun/star/util/XModifiable.hpp>
#include <com/sun/star/xml/sax/SAXException.hpp>
#include <com/sun/star/xml/sax/XSAXSerializable.hpp>

#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/lang/EventObject.hpp>
#include <com/sun/star/beans/IllegalTypeException.hpp>
#include <com/sun/star/beans/PropertyExistException.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/task/ErrorCodeIOException.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/io/WrongFormatException.hpp>
#include <com/sun/star/io/XStream.hpp>
#include <com/sun/star/document/XImporter.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/document/XFilter.hpp>
#include <com/sun/star/xml/sax/Parser.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <com/sun/star/xml/sax/XFastParser.hpp>
#include <com/sun/star/xml/dom/DOMException.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/dom/XElement.hpp>
#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
#include <com/sun/star/xml/dom/NodeType.hpp>
#include <com/sun/star/xml/xpath/XPathAPI.hpp>
#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/Time.hpp>
#include <com/sun/star/util/DateWithTimezone.hpp>
#include <com/sun/star/util/DateTimeWithTimezone.hpp>
#include <com/sun/star/util/Duration.hpp>

#include <rtl/ustrbuf.hxx>
#include <tools/datetime.hxx>
#include <tools/diagnose_ex.h>
#include <osl/mutex.hxx>
#include <comphelper/fileformat.h>
#include <cppuhelper/basemutex.hxx>
#include <comphelper/interfacecontainer2.hxx>
#include <comphelper/storagehelper.hxx>
#include <unotools/mediadescriptor.hxx>
#include <comphelper/sequence.hxx>
#include <sot/storage.hxx>
#include <sfx2/docfile.hxx>
#include <sax/tools/converter.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <optional>

#include <utility>
#include <vector>
#include <map>
#include <cstring>
#include <limits>


#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <com/sun/star/document/XCompatWriterDocProperties.hpp>
#include <com/sun/star/beans/PropertyBag.hpp>

/**
 * This file contains the implementation of the service
 * com.sun.star.document.DocumentProperties.
 * This service enables access to the meta-data stored in documents.
 * Currently, this service only handles documents in ODF format.
 *
 * The implementation uses an XML DOM to store the properties.
 * This approach was taken because it allows for preserving arbitrary XML data
 * in loaded documents, which will be stored unmodified when saving the
 * document again.
 *
 * Upon access, some properties are directly read from and updated in the DOM.
 * Exception: it seems impossible to get notified upon addition of a property
 * to a com.sun.star.beans.PropertyBag, which is used for storing user-defined
 * properties; because of this, user-defined properties are updated in the
 * XML DOM only when storing the document.
 * Exception 2: when setting certain properties which correspond to attributes
 * in the XML DOM, we want to remove the corresponding XML element. Detecting
 * this condition can get messy, so we store all such properties as members,
 * and update the DOM tree only when storing the document (in
 * <method>updateUserDefinedAndAttributes</method>).
 *
 */

/// anonymous implementation namespace
namespace {

/// a list of attribute-lists, where attribute means name and content
typedef std::vector<std::vector<std::pair<const char*, OUString> > >
        AttrVector;

typedef ::cppu::WeakComponentImplHelper<
            css::lang::XServiceInfo,
            css::document::XDocumentProperties,
            css::lang::XInitialization,
            css::util::XCloneable,
            css::util::XModifiable,
            css::xml::sax::XSAXSerializable>
    SfxDocumentMetaData_Base;

class SfxDocumentMetaData:
    private ::cppu::BaseMutex,
    public SfxDocumentMetaData_Base
{
public:
    explicit SfxDocumentMetaData(
        css::uno::Reference< css::uno::XComponentContext > const & context);
    SfxDocumentMetaData(const SfxDocumentMetaData&) = delete;
    SfxDocumentMetaData& operator=(const SfxDocumentMetaData&) = delete;

    // css::lang::XServiceInfo:
    virtual OUString SAL_CALL getImplementationName() override;
    virtual sal_Bool SAL_CALL supportsService(
        const OUString & ServiceName) override;
    virtual css::uno::Sequence< OUString > SAL_CALL
        getSupportedServiceNames() override;

    // css::lang::XComponent:
    virtual void SAL_CALL dispose() override;

    // css::document::XDocumentProperties:
    virtual OUString SAL_CALL getAuthor() override;
    virtual void SAL_CALL setAuthor(const OUString & the_value) override;
    virtual OUString SAL_CALL getGenerator() override;
    virtual void SAL_CALL setGenerator(const OUString & the_value) override;
    virtual css::util::DateTime SAL_CALL getCreationDate() override;
    virtual void SAL_CALL setCreationDate(const css::util::DateTime & the_value) override;
    virtual OUString SAL_CALL getTitle() override;
    virtual void SAL_CALL setTitle(const OUString & the_value) override;
    virtual OUString SAL_CALL getSubject() override;
    virtual void SAL_CALL setSubject(const OUString & the_value) override;
    virtual OUString SAL_CALL getDescription() override;
    virtual void SAL_CALL setDescription(const OUString & the_value) override;
    virtual css::uno::Sequence< OUString > SAL_CALL getKeywords() override;
    virtual void SAL_CALL setKeywords(
        const css::uno::Sequence< OUString > & the_value) override;
    virtual css::lang::Locale SAL_CALL getLanguage() override;
    virtual void SAL_CALL setLanguage(const css::lang::Locale & the_value) override;
    virtual OUString SAL_CALL getModifiedBy() override;
    virtual void SAL_CALL setModifiedBy(const OUString & the_value) override;
    virtual css::util::DateTime SAL_CALL getModificationDate() override;
    virtual void SAL_CALL setModificationDate(
            const css::util::DateTime & the_value) override;
    virtual OUString SAL_CALL getPrintedBy() override;
    virtual void SAL_CALL setPrintedBy(const OUString & the_value) override;
    virtual css::util::DateTime SAL_CALL getPrintDate() override;
    virtual void SAL_CALL setPrintDate(const css::util::DateTime & the_value) override;
    virtual OUString SAL_CALL getTemplateName() override;
    virtual void SAL_CALL setTemplateName(const OUString & the_value) override;
    virtual OUString SAL_CALL getTemplateURL() override;
    virtual void SAL_CALL setTemplateURL(const OUString & the_value) override;
    virtual css::util::DateTime SAL_CALL getTemplateDate() override;
    virtual void SAL_CALL setTemplateDate(const css::util::DateTime & the_value) override;
    virtual OUString SAL_CALL getAutoloadURL() override;
    virtual void SAL_CALL setAutoloadURL(const OUString & the_value) override;
    virtual ::sal_Int32 SAL_CALL getAutoloadSecs() override;
    virtual void SAL_CALL setAutoloadSecs(::sal_Int32 the_value) override;
    virtual OUString SAL_CALL getDefaultTarget() override;
    virtual void SAL_CALL setDefaultTarget(const OUString & the_value) override;
    virtual css::uno::Sequence< css::beans::NamedValue > SAL_CALL
        getDocumentStatistics() override;
    virtual void SAL_CALL setDocumentStatistics(
        const css::uno::Sequence< css::beans::NamedValue > & the_value) override;
    virtual ::sal_Int16 SAL_CALL getEditingCycles() override;
    virtual void SAL_CALL setEditingCycles(::sal_Int16 the_value) override;
    virtual ::sal_Int32 SAL_CALL getEditingDuration() override;
    virtual void SAL_CALL setEditingDuration(::sal_Int32 the_value) override;
    virtual void SAL_CALL resetUserData(const OUString & the_value) override;
    virtual css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
        getUserDefinedProperties() override;
    virtual void SAL_CALL loadFromStorage(
        const css::uno::Reference< css::embed::XStorage > & Storage,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
    virtual void SAL_CALL loadFromMedium(const OUString & URL,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
    virtual void SAL_CALL storeToStorage(
        const css::uno::Reference< css::embed::XStorage > & Storage,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
    virtual void SAL_CALL storeToMedium(const OUString & URL,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;

    // css::lang::XInitialization:
    virtual void SAL_CALL initialize(
        const css::uno::Sequence< css::uno::Any > & aArguments) override;

    // css::util::XCloneable:
    virtual css::uno::Reference<css::util::XCloneable> SAL_CALL createClone() override;

    // css::util::XModifiable:
    virtual sal_Bool SAL_CALL isModified(  ) override;
    virtual void SAL_CALL setModified( sal_Bool bModified ) override;

    // css::util::XModifyBroadcaster:
    virtual void SAL_CALL addModifyListener(
        const css::uno::Reference< css::util::XModifyListener > & xListener) override;
    virtual void SAL_CALL removeModifyListener(
        const css::uno::Reference< css::util::XModifyListener > & xListener) override;

    // css::xml::sax::XSAXSerializable
    virtual void SAL_CALL serialize(
        const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
        const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces) override;

protected:
    virtual ~SfxDocumentMetaData() override {}
    virtual SfxDocumentMetaData* createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) { return new SfxDocumentMetaData( context ); };
    const css::uno::Reference< css::uno::XComponentContext > m_xContext;

    /// for notification
    ::comphelper::OInterfaceContainerHelper2 m_NotifyListeners;
    /// flag: false means not initialized yet, or disposed
    bool m_isInitialized;
    /// flag
    bool m_isModified;
    /// meta-data DOM tree
    css::uno::Reference< css::xml::dom::XDocument > m_xDoc;
    /// meta-data super node in the meta-data DOM tree
    css::uno::Reference< css::xml::dom::XNode> m_xParent;
    /// standard meta data (single occurrence)
    std::map< OUString, css::uno::Reference<css::xml::dom::XNode> >
        m_meta;
    /// standard meta data (multiple occurrences)
    std::map< OUString,
        std::vector<css::uno::Reference<css::xml::dom::XNode> > > m_metaList;
    /// user-defined meta data (meta:user-defined) @ATTENTION may be null!
    css::uno::Reference<css::beans::XPropertyContainer> m_xUserDefined;
    // now for some meta-data attributes; these are not updated directly in the
    // DOM because updates (detecting "empty" elements) would be quite messy
    OUString m_TemplateName;
    OUString m_TemplateURL;
    css::util::DateTime m_TemplateDate;
    OUString m_AutoloadURL;
    sal_Int32 m_AutoloadSecs;
    OUString m_DefaultTarget;

    /// check if we are initialized properly
    void checkInit() const;
    /// initialize state from given DOM tree
    void init(const css::uno::Reference<css::xml::dom::XDocument>& i_xDom);
    /// update element in DOM tree
    void updateElement(const char *i_name,
        std::vector<std::pair<const char *, OUString> >* i_pAttrs = nullptr);
    /// update user-defined meta data and attributes in DOM tree
    void updateUserDefinedAndAttributes();
    /// create empty DOM tree (XDocument)
    css::uno::Reference<css::xml::dom::XDocument> createDOM() const;
    /// extract base URL (necessary for converting relative links)
    css::uno::Reference<css::beans::XPropertySet> getURLProperties(
        const css::uno::Sequence<css::beans::PropertyValue> & i_rMedium) const;
    /// get text of standard meta data element
    OUString getMetaText(const char* i_name) const;
    /// set text of standard meta data element iff not equal to existing text
    bool setMetaText(const char* i_name,
        const OUString & i_rValue);
    /// set text of standard meta data element iff not equal to existing text
    void setMetaTextAndNotify(const char* i_name,
        const OUString & i_rValue);
    /// get text of standard meta data element's attribute
    OUString getMetaAttr(const char* i_name,
        const char* i_attr) const;
    /// get text of a list of standard meta data elements (multiple occ.)
    css::uno::Sequence< OUString > getMetaList(
        const char* i_name) const;
    /// set text of a list of standard meta data elements (multiple occ.)
    bool setMetaList(const char* i_name,
        const css::uno::Sequence< OUString > & i_rValue,
        AttrVector const*);
    void createUserDefined();
};

typedef ::cppu::ImplInheritanceHelper< SfxDocumentMetaData, css::document::XCompatWriterDocProperties > CompatWriterDocPropsImpl_BASE;

class CompatWriterDocPropsImpl : public CompatWriterDocPropsImpl_BASE
{
    OUString msManager;
    OUString msCategory;
    OUString msCompany;
protected:
    virtual SfxDocumentMetaData* createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) override { return new CompatWriterDocPropsImpl( context ); };
public:
    explicit CompatWriterDocPropsImpl( css::uno::Reference< css::uno::XComponentContext > const & context) : CompatWriterDocPropsImpl_BASE( context ) {}

// XCompatWriterDocPropsImpl
    virtual OUString SAL_CALL getManager() override { return msManager; }
    virtual void SAL_CALL setManager( const OUString& _manager ) override { msManager = _manager; }
    virtual OUString SAL_CALL getCategory() override { return msCategory; }
    virtual void SAL_CALL setCategory( const OUString& _category ) override { msCategory = _category; }
    virtual OUString SAL_CALL getCompany() override { return msCompany; }
    virtual void SAL_CALL setCompany( const OUString& _company ) override { msCompany = _company; }

// XServiceInfo
    virtual OUString SAL_CALL getImplementationName(  ) override
    {
        return "CompatWriterDocPropsImpl";
    }

    virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override
    {
        return cppu::supportsService(this, ServiceName);
    }

    virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames(  ) override
    {
        css::uno::Sequence<OUString> aServiceNames { "com.sun.star.writer.DocumentProperties" };
        return aServiceNames;
    }
};

// NB: keep these two arrays in sync!
const char* s_stdStatAttrs[] = {
    "meta:page-count",
    "meta:table-count",
    "meta:draw-count",
    "meta:image-count",
    "meta:object-count",
    "meta:ole-object-count",
    "meta:paragraph-count",
    "meta:word-count",
    "meta:character-count",
    "meta:row-count",
    "meta:frame-count",
    "meta:sentence-count",
    "meta:syllable-count",
    "meta:non-whitespace-character-count",
    "meta:cell-count",
    nullptr
};

// NB: keep these two arrays in sync!
const char* s_stdStats[] = {
    "PageCount",
    "TableCount",
    "DrawCount",
    "ImageCount",
    "ObjectCount",
    "OLEObjectCount",
    "ParagraphCount",
    "WordCount",
    "CharacterCount",
    "RowCount",
    "FrameCount",
    "SentenceCount",
    "SyllableCount",
    "NonWhitespaceCharacterCount",
    "CellCount",
    nullptr
};

const char* s_stdMeta[] = {
    "meta:generator",           // string
    "dc:title",                 // string
    "dc:description",           // string
    "dc:subject",               // string
    "meta:initial-creator",     // string
    "dc:creator",               // string
    "meta:printed-by",          // string
    "meta:creation-date",       // dateTime
    "dc:date",                  // dateTime
    "meta:print-date",          // dateTime
    "meta:template",            // XLink
    "meta:auto-reload",
    "meta:hyperlink-behaviour",
    "dc:language",              // language
    "meta:editing-cycles",      // nonNegativeInteger
    "meta:editing-duration",    // duration
    "meta:document-statistic",  // ... // note: statistic is singular, no s!
    nullptr
};

const char* s_stdMetaList[] = {
    "meta:keyword",             // string*
    "meta:user-defined",        // ...*
    nullptr
};

const char s_nsXLink[] = "http://www.w3.org/1999/xlink";
const char s_nsDC[] = "http://purl.org/dc/elements/1.1/";
const char s_nsODF[] = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
const char s_nsODFMeta[] = "urn:oasis:names:tc:opendocument:xmlns:meta:1.0";
// const char* s_nsOOo     = "http://openoffice.org/2004/office"; // not used (yet?)

static const char s_meta    [] = "meta.xml";

bool isValidDate(const css::util::Date & i_rDate)
{
    return i_rDate.Month > 0;
}

bool isValidDateTime(const css::util::DateTime & i_rDateTime)
{
    return i_rDateTime.Month > 0;
}

std::pair< OUString, OUString >
getQualifier(const char* i_name) {
    OUString nm = OUString::createFromAscii(i_name);
    sal_Int32 ix = nm.indexOf(u':');
    if (ix == -1) {
        return std::make_pair(OUString(), nm);
    } else {
        return std::make_pair(nm.copy(0,ix), nm.copy(ix+1));
    }
}

// get namespace for standard qualified names
// NB: only call this with statically known strings!
OUString getNameSpace(const char* i_qname) throw ()
{
    assert(i_qname);
    const char * ns = "";
    OUString n = getQualifier(i_qname).first;
    if ( n == "xlink" ) ns = s_nsXLink;
    if ( n == "dc" ) ns = s_nsDC;
    if ( n == "office" ) ns = s_nsODF;
    if ( n == "meta" ) ns = s_nsODFMeta;
    assert(*ns);
    return OUString::createFromAscii(ns);
}

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) throw ()
{
    if (::sax::Converter::parseDateOrDateTime(
                &io_rd, io_rdt, o_rIsDateTime, &o_rTimeZone, i_text)) {
        return true;
    } else {
        SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
        return false;
    }
}

// convert string to date/time
bool
textToDateTime(css::util::DateTime & io_rdt, const OUString& i_text) throw ()
{
    if (::sax::Converter::parseDateTime(io_rdt, i_text)) {
        return true;
    } else {
        SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
        return false;
    }
}

// convert string to date/time with default return value
css::util::DateTime
textToDateTimeDefault(const OUString& i_text) throw ()
{
    css::util::DateTime dt;
    static_cast<void> (textToDateTime(dt, i_text));
    // on conversion error: return default value (unchanged)
    return dt;
}

// convert date to string
OUString
dateToText(css::util::Date const& i_rd,
           sal_Int16 const*const pTimeZone) throw ()
{
    if (isValidDate(i_rd)) {
        OUStringBuffer buf;
        ::sax::Converter::convertDate(buf, i_rd, pTimeZone);
        return buf.makeStringAndClear();
    } else {
        return OUString();
    }
}


// convert date/time to string
OUString
dateTimeToText(css::util::DateTime const& i_rdt,
               sal_Int16 const*const pTimeZone = nullptr) throw ()
{
    if (isValidDateTime(i_rdt)) {
        OUStringBuffer buf(32);
        ::sax::Converter::convertDateTime(buf, i_rdt, pTimeZone, true);
        return buf.makeStringAndClear();
    } else {
        return OUString();
    }
}

// convert string to duration
bool
textToDuration(css::util::Duration& io_rDur, OUString const& i_rText)
throw ()
{
    if (::sax::Converter::convertDuration(io_rDur, i_rText)) {
        return true;
    } else {
        SAL_WARN_IF(!i_rText.isEmpty(), "sfx.doc", "Invalid duration: " << i_rText);
        return false;
    }
}

sal_Int32 textToDuration(OUString const& i_rText) throw ()
{
    css::util::Duration d;
    if (textToDuration(d, i_rText)) {
        // #i107372#: approximate years/months
        const sal_Int32 days( (d.Years * 365) + (d.Months * 30) + d.Days );
        return  (days * (24*3600))
                + (d.Hours * 3600) + (d.Minutes * 60) + d.Seconds;
    } else {
        return 0; // default
    }
}

// convert duration to string
OUString durationToText(css::util::Duration const& i_rDur) throw ()
{
    OUStringBuffer buf;
    ::sax::Converter::convertDuration(buf, i_rDur);
    return buf.makeStringAndClear();
}

// convert duration to string
OUString durationToText(sal_Int32 i_value) throw ()
{
    css::util::Duration ud;
    ud.Days    = static_cast<sal_Int16>(i_value / (24 * 3600));
    ud.Hours   = static_cast<sal_Int16>((i_value % (24 * 3600)) / 3600);
    ud.Minutes = static_cast<sal_Int16>((i_value % 3600) / 60);
    ud.Seconds = static_cast<sal_Int16>(i_value % 60);
    ud.NanoSeconds = 0;
    return durationToText(ud);
}

// extract base URL (necessary for converting relative links)
css::uno::Reference< css::beans::XPropertySet >
SfxDocumentMetaData::getURLProperties(
    const css::uno::Sequence< css::beans::PropertyValue > & i_rMedium) const
{
    css::uno::Reference< css::beans::XPropertyBag> xPropArg = css::beans::PropertyBag::createDefault( m_xContext );
    try {
        css::uno::Any baseUri;
        for (const auto& rProp : i_rMedium) {
            if (rProp.Name == "DocumentBaseURL") {
                baseUri = rProp.Value;
            } else if (rProp.Name == "URL") {
                if (!baseUri.hasValue()) {
                    baseUri = rProp.Value;
                }
            } else if (rProp.Name == "HierarchicalDocumentName") {
                xPropArg->addProperty(
                    "StreamRelPath",
                    css::beans::PropertyAttribute::MAYBEVOID,
                    rProp.Value);
            }
        }
        if (baseUri.hasValue()) {
            xPropArg->addProperty(
                "BaseURI", css::beans::PropertyAttribute::MAYBEVOID,
                baseUri);
        }
        xPropArg->addProperty("StreamName",
                css::beans::PropertyAttribute::MAYBEVOID,
                css::uno::makeAny(OUString(s_meta)));
    } catch (const css::uno::Exception &) {
        // ignore
    }
    return css::uno::Reference< css::beans::XPropertySet>(xPropArg,
                css::uno::UNO_QUERY_THROW);
}

// return the text of the (hopefully unique, i.e., normalize first!) text
// node _below_ the given node
/// @throws css::uno::RuntimeException
OUString
getNodeText(const css::uno::Reference<css::xml::dom::XNode>& i_xNode)
{
    if (!i_xNode.is())
        throw css::uno::RuntimeException("SfxDocumentMetaData::getNodeText: argument is null", i_xNode);
    for (css::uno::Reference<css::xml::dom::XNode> c = i_xNode->getFirstChild();
            c.is();
            c = c->getNextSibling()) {
        if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
            try {
                return c->getNodeValue();
            } catch (const css::xml::dom::DOMException &) { // too big?
                return OUString();
            }
        }
    }
    return OUString();
}

OUString
SfxDocumentMetaData::getMetaText(const char* i_name) const
//        throw (css::uno::RuntimeException)
{
    checkInit();

    const OUString name( OUString::createFromAscii(i_name) );
    assert(m_meta.find(name) != m_meta.end());
    css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
    return (xNode.is()) ? getNodeText(xNode) : OUString();
}

bool
SfxDocumentMetaData::setMetaText(const char* i_name,
        const OUString & i_rValue)
    // throw (css::uno::RuntimeException)
{
    checkInit();

    const OUString name( OUString::createFromAscii(i_name) );
    assert(m_meta.find(name) != m_meta.end());
    css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;

    try {
        if (i_rValue.isEmpty()) {
            if (xNode.is()) { // delete
                m_xParent->removeChild(xNode);
                xNode.clear();
                m_meta[name] = xNode;
                return true;
            } else {
                return false;
            }
        } else {
            if (xNode.is()) { // update
                for (css::uno::Reference<css::xml::dom::XNode> c =
                            xNode->getFirstChild();
                        c.is();
                        c = c->getNextSibling()) {
                    if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
                        if (c->getNodeValue() != i_rValue) {
                            c->setNodeValue(i_rValue);
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
            } else { // insert
                xNode.set(m_xDoc->createElementNS(getNameSpace(i_name), name),
                            css::uno::UNO_QUERY_THROW);
                m_xParent->appendChild(xNode);
                m_meta[name] = xNode;
            }
            css::uno::Reference<css::xml::dom::XNode> xTextNode(
                m_xDoc->createTextNode(i_rValue), css::uno::UNO_QUERY_THROW);
            xNode->appendChild(xTextNode);
            return true;
        }
    } catch (const css::xml::dom::DOMException &) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(
                "SfxDocumentMetaData::setMetaText: DOM exception",
                css::uno::Reference<css::uno::XInterface>(*this), anyEx);
    }
}

void
SfxDocumentMetaData::setMetaTextAndNotify(const char* i_name,
        const OUString & i_rValue)
    // throw (css::uno::RuntimeException)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    if (setMetaText(i_name, i_rValue)) {
        g.clear();
        setModified(true);
    }
}

OUString
SfxDocumentMetaData::getMetaAttr(const char* i_name, const char* i_attr) const
//        throw (css::uno::RuntimeException)
{
    OUString name = OUString::createFromAscii(i_name);
    assert(m_meta.find(name) != m_meta.end());
    css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
    if (xNode.is()) {
        css::uno::Reference<css::xml::dom::XElement> xElem(xNode,
            css::uno::UNO_QUERY_THROW);
        return xElem->getAttributeNS(getNameSpace(i_attr),
                    getQualifier(i_attr).second);
    } else {
        return OUString();
    }
}

css::uno::Sequence< OUString>
SfxDocumentMetaData::getMetaList(const char* i_name) const
//        throw (css::uno::RuntimeException)
{
    checkInit();
    OUString name = OUString::createFromAscii(i_name);
    assert(m_metaList.find(name) != m_metaList.end());
    std::vector<css::uno::Reference<css::xml::dom::XNode> > const & vec =
        m_metaList.find(name)->second;
    css::uno::Sequence< OUString> ret(vec.size());
    for (size_t i = 0; i < vec.size(); ++i) {
        ret[i] = getNodeText(vec.at(i));
    }
    return ret;
}

bool
SfxDocumentMetaData::setMetaList(const char* i_name,
        const css::uno::Sequence<OUString> & i_rValue,
        AttrVector const* i_pAttrs)
    // throw (css::uno::RuntimeException)
{
    checkInit();
    assert((i_pAttrs == nullptr) ||
           (static_cast<size_t>(i_rValue.getLength()) == i_pAttrs->size()));

    try {
        OUString name = OUString::createFromAscii(i_name);
        assert(m_metaList.find(name) != m_metaList.end());
        std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
            m_metaList[name];

        // if nothing changed, do nothing
        // alas, this does not check for permutations, or attributes...
        if (nullptr == i_pAttrs) {
            if (static_cast<size_t>(i_rValue.getLength()) == vec.size()) {
                bool isEqual(true);
                for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
                    css::uno::Reference<css::xml::dom::XNode> xNode(vec.at(i));
                    if (xNode.is()) {
                        OUString val = getNodeText(xNode);
                        if (val != i_rValue[i]) {
                            isEqual = false;
                            break;
                        }
                    }
                }
                if (isEqual) return false;
            }
        }

        // remove old meta data nodes
        {
            std::vector<css::uno::Reference<css::xml::dom::XNode> >
                ::reverse_iterator it(vec.rbegin());
            try {
                for ( ;it != vec.rend(); ++it)
                {
                    m_xParent->removeChild(*it);
                }
            }
            catch (...)
            {
                // Clean up already removed nodes
                vec.erase(it.base(), vec.end());
                throw;
            }
            vec.clear();
        }

        // insert new meta data nodes into DOM tree
        for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
            css::uno::Reference<css::xml::dom::XElement> xElem(
                m_xDoc->createElementNS(getNameSpace(i_name), name),
                css::uno::UNO_SET_THROW);
            css::uno::Reference<css::xml::dom::XNode> xNode(xElem,
                css::uno::UNO_QUERY_THROW);
            css::uno::Reference<css::xml::dom::XNode> xTextNode(
                m_xDoc->createTextNode(i_rValue[i]), css::uno::UNO_QUERY_THROW);
            // set attributes
            if (i_pAttrs != nullptr) {
                for (auto const& elem : (*i_pAttrs)[i])
                {
                    xElem->setAttributeNS(getNameSpace(elem.first),
                        OUString::createFromAscii(elem.first),
                        elem.second);
                }
            }
            xNode->appendChild(xTextNode);
            m_xParent->appendChild(xNode);
            vec.push_back(xNode);
        }

        return true;
    } catch (const css::xml::dom::DOMException &) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(
                "SfxDocumentMetaData::setMetaList: DOM exception",
                css::uno::Reference<css::uno::XInterface>(*this), anyEx);
    }
}

// convert property list to string list and attribute list
std::pair<css::uno::Sequence< OUString>, AttrVector>
propsToStrings(css::uno::Reference<css::beans::XPropertySet> const & i_xPropSet)
{
    ::std::vector< OUString > values;
    AttrVector attrs;

    css::uno::Reference<css::beans::XPropertySetInfo> xSetInfo
        = i_xPropSet->getPropertySetInfo();
    css::uno::Sequence<css::beans::Property> props = xSetInfo->getProperties();

    for (sal_Int32 i = 0; i < props.getLength(); ++i) {
        if (props[i].Attributes & css::beans::PropertyAttribute::TRANSIENT) {
            continue;
        }
        const OUString name = props[i].Name;
        css::uno::Any any;
        try {
            any = i_xPropSet->getPropertyValue(name);
        } catch (const css::uno::Exception &) {
            // ignore
        }
        const css::uno::Type & type = any.getValueType();
        std::vector<std::pair<const char*, OUString> > as;
        as.emplace_back("meta:name", name);
        const char* vt = "meta:value-type";

        // convert according to type
        if (type == ::cppu::UnoType<bool>::get()) {
            bool b = false;
            any >>= b;
            OUStringBuffer buf;
            ::sax::Converter::convertBool(buf, b);
            values.push_back(buf.makeStringAndClear());
            as.emplace_back(vt, OUString("boolean"));
        } else if (type == ::cppu::UnoType< OUString>::get()) {
            OUString s;
            any >>= s;
            values.push_back(s);
// #i90847# OOo 2.x does stupid things if value-type="string";
// fortunately string is default anyway, so we can just omit it
// #i107502#: however, OOo 2.x only reads 4 user-defined without @value-type
// => best backward compatibility: first 4 without @value-type, rest with
            if (4 <= i)
            {
                as.emplace_back(vt, OUString("string"));
            }
        } else if (type == ::cppu::UnoType<css::util::DateTime>::get()) {
            css::util::DateTime dt;
            any >>= dt;
            values.push_back(dateTimeToText(dt));
            as.emplace_back(vt, OUString("date"));
        } else if (type == ::cppu::UnoType<css::util::Date>::get()) {
            css::util::Date d;
            any >>= d;
            values.push_back(dateToText(d, nullptr));
            as.emplace_back(vt,OUString("date"));
        } else if (type == ::cppu::UnoType<css::util::DateTimeWithTimezone>::get()) {
            css::util::DateTimeWithTimezone dttz;
            any >>= dttz;
            values.push_back(dateTimeToText(dttz.DateTimeInTZ, &dttz.Timezone));
            as.emplace_back(vt, OUString("date"));
        } else if (type == ::cppu::UnoType<css::util::DateWithTimezone>::get()) {
            css::util::DateWithTimezone dtz;
            any >>= dtz;
            values.push_back(dateToText(dtz.DateInTZ, &dtz.Timezone));
            as.emplace_back(vt, OUString("date"));
        } else if (type == ::cppu::UnoType<css::util::Time>::get()) {
            // #i97029#: replaced by Duration
            // Time is supported for backward compatibility with OOo 3.x, x<=2
            css::util::Time ut;
            any >>= ut;
            css::util::Duration ud;
            ud.Hours   = ut.Hours;
            ud.Minutes = ut.Minutes;
            ud.Seconds = ut.Seconds;
            ud.NanoSeconds = ut.NanoSeconds;
            values.push_back(durationToText(ud));
            as.emplace_back(vt, OUString("time"));
        } else if (type == ::cppu::UnoType<css::util::Duration>::get()) {
            css::util::Duration ud;
            any >>= ud;
            values.push_back(durationToText(ud));
            as.emplace_back(vt, OUString("time"));
        } else if (::cppu::UnoType<double>::get().isAssignableFrom(type)) {
            // support not just double, but anything that can be converted
            double d = 0;
            any >>= d;
            OUStringBuffer buf;
            ::sax::Converter::convertDouble(buf, d);
            values.push_back(buf.makeStringAndClear());
            as.emplace_back(vt, OUString("float"));
        } else {
            SAL_WARN("sfx.doc", "Unsupported property type: " << any.getValueTypeName() );
            continue;
        }
        attrs.push_back(as);
    }

    return std::make_pair(comphelper::containerToSequence(values), attrs);
}

// remove the given element from the DOM, and iff i_pAttrs != 0 insert new one
void
SfxDocumentMetaData::updateElement(const char *i_name,
        std::vector<std::pair<const char *, OUString> >* i_pAttrs)
{
    OUString name = OUString::createFromAscii(i_name);
    try {
        // remove old element
        css::uno::Reference<css::xml::dom::XNode> xNode =
            m_meta.find(name)->second;
        if (xNode.is()) {
            m_xParent->removeChild(xNode);
            xNode.clear();
        }
        // add new element
        if (nullptr != i_pAttrs) {
            css::uno::Reference<css::xml::dom::XElement> xElem(
                m_xDoc->createElementNS(getNameSpace(i_name), name),
                    css::uno::UNO_SET_THROW);
            xNode.set(xElem, css::uno::UNO_QUERY_THROW);
            // set attributes
            for (auto const& elem : *i_pAttrs)
            {
                xElem->setAttributeNS(getNameSpace(elem.first),
                    OUString::createFromAscii(elem.first), elem.second);
            }
            m_xParent->appendChild(xNode);
        }
        m_meta[name] = xNode;
    } catch (const css::xml::dom::DOMException &) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(
                "SfxDocumentMetaData::updateElement: DOM exception",
                css::uno::Reference<css::uno::XInterface>(*this), anyEx);
    }
}

// update user-defined meta data in DOM tree
void SfxDocumentMetaData::updateUserDefinedAndAttributes()
{
    createUserDefined();
    const css::uno::Reference<css::beans::XPropertySet> xPSet(m_xUserDefined,
            css::uno::UNO_QUERY_THROW);
    const std::pair<css::uno::Sequence< OUString>, AttrVector>
        udStringsAttrs( propsToStrings(xPSet) );
    (void) setMetaList("meta:user-defined", udStringsAttrs.first,
            &udStringsAttrs.second);

    // update elements with attributes
    std::vector<std::pair<const char *, OUString> > attributes;
    if (!m_TemplateName.isEmpty() || !m_TemplateURL.isEmpty()
            || isValidDateTime(m_TemplateDate)) {
        attributes.emplace_back("xlink:type", OUString("simple"));
        attributes.emplace_back("xlink:actuate", OUString("onRequest"));
        attributes.emplace_back("xlink:title", m_TemplateName);
        attributes.emplace_back("xlink:href", m_TemplateURL );
        if (isValidDateTime(m_TemplateDate)) {
            attributes.emplace_back(
                "meta:date", dateTimeToText(m_TemplateDate));
        }
        updateElement("meta:template", &attributes);
    } else {
        updateElement("meta:template");
    }
    attributes.clear();

    if (!m_AutoloadURL.isEmpty() || (0 != m_AutoloadSecs)) {
        attributes.emplace_back("xlink:href", m_AutoloadURL );
        attributes.emplace_back("meta:delay",
                durationToText(m_AutoloadSecs));
        updateElement("meta:auto-reload", &attributes);
    } else {
        updateElement("meta:auto-reload");
    }
    attributes.clear();

    if (!m_DefaultTarget.isEmpty()) {
        attributes.emplace_back(
                "office:target-frame-name",
                m_DefaultTarget);
        // xlink:show: _blank -> new, any other value -> replace
        const char* show = m_DefaultTarget == "_blank" ? "new" : "replace";
        attributes.emplace_back(
                "xlink:show",
                OUString::createFromAscii(show));
        updateElement("meta:hyperlink-behaviour", &attributes);
    } else {
        updateElement("meta:hyperlink-behaviour");
    }
    attributes.clear();
}

// create empty DOM tree (XDocument)
css::uno::Reference<css::xml::dom::XDocument>
SfxDocumentMetaData::createDOM() const // throw (css::uno::RuntimeException)
{
    css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder( css::xml::dom::DocumentBuilder::create(m_xContext) );
    css::uno::Reference<css::xml::dom::XDocument> xDoc = xBuilder->newDocument();
    if (!xDoc.is())
        throw css::uno::RuntimeException(
                "SfxDocumentMetaData::createDOM: cannot create new document",
                *const_cast<SfxDocumentMetaData*>(this));
    return xDoc;
}

void
SfxDocumentMetaData::checkInit() const // throw (css::uno::RuntimeException)
{
    if (!m_isInitialized) {
        throw css::uno::RuntimeException(
                "SfxDocumentMetaData::checkInit: not initialized",
                *const_cast<SfxDocumentMetaData*>(this));
    }
    assert(m_xDoc.is() && m_xParent.is());
}

// initialize state from DOM tree
void SfxDocumentMetaData::init(
        const css::uno::Reference<css::xml::dom::XDocument>& i_xDoc)
{
    if (!i_xDoc.is())
        throw css::uno::RuntimeException("SfxDocumentMetaData::init: no DOM tree given", *this);

    css::uno::Reference<css::xml::xpath::XXPathAPI> xPath = css::xml::xpath::XPathAPI::create(m_xContext);

    m_isInitialized = false;
    m_xDoc = i_xDoc;

    // select nodes for standard meta data stuff
    xPath->registerNS("xlink", s_nsXLink);
    xPath->registerNS("dc", s_nsDC);
    xPath->registerNS("office", s_nsODF);
    xPath->registerNS("meta", s_nsODFMeta);
    // NB: we do not handle the single-XML-file ODF variant, which would
    //     have the root element office:document.
    //     The root of such documents must be converted in the importer!
    OUString const prefix(
        "/child::office:document-meta/child::office:meta");
    css::uno::Reference<css::xml::dom::XNode> xDocNode(
        m_xDoc, css::uno::UNO_QUERY_THROW);
    m_xParent.clear();
    try {
        m_xParent = xPath->selectSingleNode(xDocNode, prefix);
    } catch (const css::uno::Exception &) {
    }

    if (!m_xParent.is()) {
        // all this create/append stuff may throw DOMException
        try {
            css::uno::Reference<css::xml::dom::XElement> xRElem;
            css::uno::Reference<css::xml::dom::XNode> xNode(
                i_xDoc->getFirstChild());
            while (xNode.is()) {
                if (css::xml::dom::NodeType_ELEMENT_NODE ==xNode->getNodeType())
                {
                    if ( xNode->getNamespaceURI() == s_nsODF && xNode->getLocalName() == "document-meta" )
                    {
                        xRElem.set(xNode, css::uno::UNO_QUERY_THROW);
                        break;
                    }
                    else
                    {
                        SAL_INFO("sfx.doc", "SfxDocumentMetaData::init(): "
                                "deleting unexpected root element: "
                                << xNode->getLocalName());
                        i_xDoc->removeChild(xNode);
                        xNode = i_xDoc->getFirstChild(); // start over
                    }
                } else {
                    xNode = xNode->getNextSibling();
                }
            }
            if (!xRElem.is()) {
                xRElem = i_xDoc->createElementNS(
                    s_nsODF, "office:document-meta");
                css::uno::Reference<css::xml::dom::XNode> xRNode(xRElem,
                    css::uno::UNO_QUERY_THROW);
                i_xDoc->appendChild(xRNode);
            }
            xRElem->setAttributeNS(s_nsODF, "office:version", "1.0");
            // does not exist, otherwise m_xParent would not be null
            css::uno::Reference<css::xml::dom::XNode> xParent (
                i_xDoc->createElementNS(s_nsODF, "office:meta"),
            css::uno::UNO_QUERY_THROW);
            xRElem->appendChild(xParent);
            m_xParent = xParent;
        } catch (const css::xml::dom::DOMException &) {
            css::uno::Any anyEx = cppu::getCaughtException();
            throw css::lang::WrappedTargetRuntimeException(
                    "SfxDocumentMetaData::init: DOM exception",
                    css::uno::Reference<css::uno::XInterface>(*this), anyEx);
        }
    }


    // select nodes for elements of which we only handle one occurrence
    for (const char **pName = s_stdMeta; *pName != nullptr; ++pName) {
        OUString name = OUString::createFromAscii(*pName);
        // NB: If a document contains more than one occurrence of a
        // meta-data element, we arbitrarily pick one of them here.
        // We do not remove the others, i.e., when we write the
        // document, it will contain the duplicates unchanged.
        // The ODF spec says that handling multiple occurrences is
        // application-specific.
        css::uno::Reference<css::xml::dom::XNode> xNode =
            xPath->selectSingleNode(m_xParent, "child::" + name);
        // Do not create an empty element if it is missing;
        // for certain elements, such as dateTime, this would be invalid
        m_meta[name] = xNode;
    }

    // select nodes for elements of which we handle all occurrences
    for (const char **pName = s_stdMetaList; *pName != nullptr; ++pName) {
        OUString name = OUString::createFromAscii(*pName);
        css::uno::Reference<css::xml::dom::XNodeList> nodes =
            xPath->selectNodeList(m_xParent, "child::" + name);
        std::vector<css::uno::Reference<css::xml::dom::XNode> > v;
        v.reserve(nodes->getLength());
        for (sal_Int32 i = 0; i < nodes->getLength(); ++i)
        {
            v.push_back(nodes->item(i));
        }
        m_metaList[name] = v;
    }

    // initialize members corresponding to attributes from DOM nodes
    m_TemplateName  = getMetaAttr("meta:template", "xlink:title");
    m_TemplateURL   = getMetaAttr("meta:template", "xlink:href");
    m_TemplateDate  =
        textToDateTimeDefault(getMetaAttr("meta:template", "meta:date"));
    m_AutoloadURL   = getMetaAttr("meta:auto-reload", "xlink:href");
    m_AutoloadSecs  =
        textToDuration(getMetaAttr("meta:auto-reload", "meta:delay"));
    m_DefaultTarget =
        getMetaAttr("meta:hyperlink-behaviour", "office:target-frame-name");


    std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
        m_metaList[OUString("meta:user-defined")];
    m_xUserDefined.clear(); // #i105826#: reset (may be re-initialization)
    if ( !vec.empty() )
    {
        createUserDefined();
    }

    // user-defined meta data: initialize PropertySet from DOM nodes
    for (auto const& elem : vec)
    {
        css::uno::Reference<css::xml::dom::XElement> xElem(elem,
            css::uno::UNO_QUERY_THROW);
        css::uno::Any any;
        OUString name = xElem->getAttributeNS(s_nsODFMeta, "name");
        OUString type = xElem->getAttributeNS(s_nsODFMeta, "value-type");
        OUString text = getNodeText(elem);
        if ( type == "float" ) {
            double d;
            if (::sax::Converter::convertDouble(d, text)) {
                any <<= d;
            } else {
                SAL_WARN("sfx.doc", "Invalid float: " << text);
                continue;
            }
        } else if ( type == "date" ) {
            bool isDateTime;
            css::util::Date d;
            css::util::DateTime dt;
            std::optional<sal_Int16> nTimeZone;
            if (textToDateOrDateTime(d, dt, isDateTime, nTimeZone, text)) {
                if (isDateTime) {
                    if (nTimeZone) {
                        any <<= css::util::DateTimeWithTimezone(dt,
                                    *nTimeZone);
                    } else {
                        any <<= dt;
                    }
                } else {
                    if (nTimeZone) {
                        any <<= css::util::DateWithTimezone(d, *nTimeZone);
                    } else {
                        any <<= d;
                    }
                }
            } else {
                SAL_WARN("sfx.doc", "Invalid date: " << text);
                continue;
            }
        } else if ( type == "time" ) {
            css::util::Duration ud;
            if (textToDuration(ud, text)) {
                any <<= ud;
            } else {
                SAL_WARN("sfx.doc", "Invalid time: " << text);
                continue;
            }
        } else if ( type == "boolean" ) {
            bool b;
            if (::sax::Converter::convertBool(b, text)) {
                any <<= b;
            } else {
                SAL_WARN("sfx.doc", "Invalid boolean: " << text);
                continue;
            }
        } else { // default
            any <<= text;
        }
        try {
            m_xUserDefined->addProperty(name,
                css::beans::PropertyAttribute::REMOVABLE, any);
        } catch (const css::beans::PropertyExistException &) {
            SAL_WARN("sfx.doc", "Duplicate: " << name);
            // ignore; duplicate
        } catch (const css::beans::IllegalTypeException &) {
            SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal type: " << name);
        } catch (const css::lang::IllegalArgumentException &) {
            SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal arg: " << name);
        }
    }

    m_isModified = false;
    m_isInitialized = true;
}


SfxDocumentMetaData::SfxDocumentMetaData(
        css::uno::Reference< css::uno::XComponentContext > const & context)
    : BaseMutex()
    , SfxDocumentMetaData_Base(m_aMutex)
    , m_xContext(context)
    , m_NotifyListeners(m_aMutex)
    , m_isInitialized(false)
    , m_isModified(false)
    , m_AutoloadSecs(0)
{
    assert(context.is());
    assert(context->getServiceManager().is());
    init(createDOM());
}

// com.sun.star.uno.XServiceInfo:
OUString SAL_CALL
SfxDocumentMetaData::getImplementationName()
{
    return "SfxDocumentMetaData";
}

sal_Bool SAL_CALL
SfxDocumentMetaData::supportsService(OUString const & serviceName)
{
    return cppu::supportsService(this, serviceName);
}

css::uno::Sequence< OUString > SAL_CALL
SfxDocumentMetaData::getSupportedServiceNames()
{
    css::uno::Sequence< OUString > s { "com.sun.star.document.DocumentProperties" };
    return s;
}


// css::lang::XComponent:
void SAL_CALL SfxDocumentMetaData::dispose()
{
    ::osl::MutexGuard g(m_aMutex);
    if (!m_isInitialized) {
        return;
    }
    WeakComponentImplHelperBase::dispose(); // superclass
    m_NotifyListeners.disposeAndClear(css::lang::EventObject(
            static_cast< ::cppu::OWeakObject* >(this)));
    m_isInitialized = false;
    m_meta.clear();
    m_metaList.clear();
    m_xParent.clear();
    m_xDoc.clear();
    m_xUserDefined.clear();
}


// css::document::XDocumentProperties:
OUString SAL_CALL
SfxDocumentMetaData::getAuthor()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("meta:initial-creator");
}

void SAL_CALL SfxDocumentMetaData::setAuthor(const OUString & the_value)
{
    setMetaTextAndNotify("meta:initial-creator", the_value);
}


OUString SAL_CALL
SfxDocumentMetaData::getGenerator()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("meta:generator");
}

void SAL_CALL
SfxDocumentMetaData::setGenerator(const OUString & the_value)
{
    setMetaTextAndNotify("meta:generator", the_value);
}

css::util::DateTime SAL_CALL
SfxDocumentMetaData::getCreationDate()
{
    ::osl::MutexGuard g(m_aMutex);
    return textToDateTimeDefault(getMetaText("meta:creation-date"));
}

void SAL_CALL
SfxDocumentMetaData::setCreationDate(const css::util::DateTime & the_value)
{
    setMetaTextAndNotify("meta:creation-date", dateTimeToText(the_value));
}

OUString SAL_CALL
SfxDocumentMetaData::getTitle()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("dc:title");
}

void SAL_CALL SfxDocumentMetaData::setTitle(const OUString & the_value)
{
    setMetaTextAndNotify("dc:title", the_value);
}

OUString SAL_CALL
SfxDocumentMetaData::getSubject()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("dc:subject");
}

void SAL_CALL
SfxDocumentMetaData::setSubject(const OUString & the_value)
{
    setMetaTextAndNotify("dc:subject", the_value);
}

OUString SAL_CALL
SfxDocumentMetaData::getDescription()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("dc:description");
}

void SAL_CALL
SfxDocumentMetaData::setDescription(const OUString & the_value)
{
    setMetaTextAndNotify("dc:description", the_value);
}

css::uno::Sequence< OUString >
SAL_CALL SfxDocumentMetaData::getKeywords()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaList("meta:keyword");
}

void SAL_CALL
SfxDocumentMetaData::setKeywords(
        const css::uno::Sequence< OUString > & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    if (setMetaList("meta:keyword", the_value, nullptr)) {
        g.clear();
        setModified(true);
    }
}

css::lang::Locale SAL_CALL
        SfxDocumentMetaData::getLanguage()
{
    ::osl::MutexGuard g(m_aMutex);
    css::lang::Locale loc( LanguageTag::convertToLocale( getMetaText("dc:language"), false));
    return loc;
}

void SAL_CALL
SfxDocumentMetaData::setLanguage(const css::lang::Locale & the_value)
{
    OUString text( LanguageTag::convertToBcp47( the_value, false));
    setMetaTextAndNotify("dc:language", text);
}

OUString SAL_CALL
SfxDocumentMetaData::getModifiedBy()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("dc:creator");
}

void SAL_CALL
SfxDocumentMetaData::setModifiedBy(const OUString & the_value)
{
    setMetaTextAndNotify("dc:creator", the_value);
}

css::util::DateTime SAL_CALL
SfxDocumentMetaData::getModificationDate()
{
    ::osl::MutexGuard g(m_aMutex);
    return textToDateTimeDefault(getMetaText("dc:date"));
}

void SAL_CALL
SfxDocumentMetaData::setModificationDate(const css::util::DateTime & the_value)
{
    setMetaTextAndNotify("dc:date", dateTimeToText(the_value));
}

OUString SAL_CALL
SfxDocumentMetaData::getPrintedBy()
{
    ::osl::MutexGuard g(m_aMutex);
    return getMetaText("meta:printed-by");
}

void SAL_CALL
SfxDocumentMetaData::setPrintedBy(const OUString & the_value)
{
    setMetaTextAndNotify("meta:printed-by", the_value);
}

css::util::DateTime SAL_CALL
SfxDocumentMetaData::getPrintDate()
{
    ::osl::MutexGuard g(m_aMutex);
    return textToDateTimeDefault(getMetaText("meta:print-date"));
}

void SAL_CALL
SfxDocumentMetaData::setPrintDate(const css::util::DateTime & the_value)
{
    setMetaTextAndNotify("meta:print-date", dateTimeToText(the_value));
}

OUString SAL_CALL
SfxDocumentMetaData::getTemplateName()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_TemplateName;
}

void SAL_CALL
SfxDocumentMetaData::setTemplateName(const OUString & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_TemplateName != the_value) {
        m_TemplateName = the_value;
        g.clear();
        setModified(true);
    }
}

OUString SAL_CALL
SfxDocumentMetaData::getTemplateURL()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_TemplateURL;
}

void SAL_CALL
SfxDocumentMetaData::setTemplateURL(const OUString & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_TemplateURL != the_value) {
        m_TemplateURL = the_value;
        g.clear();
        setModified(true);
    }
}

css::util::DateTime SAL_CALL
SfxDocumentMetaData::getTemplateDate()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_TemplateDate;
}

void SAL_CALL
SfxDocumentMetaData::setTemplateDate(const css::util::DateTime & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_TemplateDate != the_value) {
        m_TemplateDate = the_value;
        g.clear();
        setModified(true);
    }
}

OUString SAL_CALL
SfxDocumentMetaData::getAutoloadURL()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_AutoloadURL;
}

void SAL_CALL
SfxDocumentMetaData::setAutoloadURL(const OUString & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_AutoloadURL != the_value) {
        m_AutoloadURL = the_value;
        g.clear();
        setModified(true);
    }
}

::sal_Int32 SAL_CALL
SfxDocumentMetaData::getAutoloadSecs()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_AutoloadSecs;
}

void SAL_CALL
SfxDocumentMetaData::setAutoloadSecs(::sal_Int32 the_value)
{
    if (the_value < 0)
        throw css::lang::IllegalArgumentException(
            "SfxDocumentMetaData::setAutoloadSecs: argument is negative",
            *this, 0);
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_AutoloadSecs != the_value) {
        m_AutoloadSecs = the_value;
        g.clear();
        setModified(true);
    }
}

OUString SAL_CALL
SfxDocumentMetaData::getDefaultTarget()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    return m_DefaultTarget;
}

void SAL_CALL
SfxDocumentMetaData::setDefaultTarget(const OUString & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);
    checkInit();
    if (m_DefaultTarget != the_value) {
        m_DefaultTarget = the_value;
        g.clear();
        setModified(true);
    }
}

css::uno::Sequence< css::beans::NamedValue > SAL_CALL
SfxDocumentMetaData::getDocumentStatistics()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    ::std::vector<css::beans::NamedValue> stats;
    for (size_t i = 0; s_stdStats[i] != nullptr; ++i) {
        const char * aName = s_stdStatAttrs[i];
        OUString text = getMetaAttr("meta:document-statistic", aName);
        if (text.isEmpty()) continue;
        css::beans::NamedValue stat;
        stat.Name = OUString::createFromAscii(s_stdStats[i]);
        sal_Int32 val;
        css::uno::Any any;
        if (!::sax::Converter::convertNumber(val, text, 0) || (val < 0)) {
            val = 0;
            SAL_WARN("sfx.doc", "Invalid number: " << text);
        }
        any <<= val;
        stat.Value = any;
        stats.push_back(stat);
    }

    return ::comphelper::containerToSequence(stats);
}

void SAL_CALL
SfxDocumentMetaData::setDocumentStatistics(
        const css::uno::Sequence< css::beans::NamedValue > & the_value)
{
    {
        osl::MutexGuard g(m_aMutex);
        checkInit();
        std::vector<std::pair<const char *, OUString> > attributes;
        for (const auto& rValue : the_value) {
            const OUString name = rValue.Name;
            // inefficiently search for matching attribute
            for (size_t j = 0; s_stdStats[j] != nullptr; ++j) {
                if (name.equalsAscii(s_stdStats[j])) {
                    const css::uno::Any any = rValue.Value;
                    sal_Int32 val = 0;
                    if (any >>= val) {
                        attributes.emplace_back(s_stdStatAttrs[j],
                            OUString::number(val));
                    }
                    else {
                        SAL_WARN("sfx.doc", "Invalid statistic: " << name);
                    }
                    break;
                }
            }
        }
        updateElement("meta:document-statistic", &attributes);
    }
    setModified(true);
}

::sal_Int16 SAL_CALL
SfxDocumentMetaData::getEditingCycles()
{
    ::osl::MutexGuard g(m_aMutex);
    OUString text = getMetaText("meta:editing-cycles");
    sal_Int32 ret;
    if (::sax::Converter::convertNumber(ret, text,
            0, std::numeric_limits<sal_Int16>::max())) {
        return static_cast<sal_Int16>(ret);
    } else {
        return 0;
    }
}

void SAL_CALL
SfxDocumentMetaData::setEditingCycles(::sal_Int16 the_value)
{
    if (the_value < 0)
        throw css::lang::IllegalArgumentException(
            "SfxDocumentMetaData::setEditingCycles: argument is negative",
            *this, 0);
    setMetaTextAndNotify("meta:editing-cycles", OUString::number(the_value));
}

::sal_Int32 SAL_CALL
SfxDocumentMetaData::getEditingDuration()
{
    ::osl::MutexGuard g(m_aMutex);
    return textToDuration(getMetaText("meta:editing-duration"));
}

void SAL_CALL
SfxDocumentMetaData::setEditingDuration(::sal_Int32 the_value)
{
    if (the_value < 0)
        throw css::lang::IllegalArgumentException(
            "SfxDocumentMetaData::setEditingDuration: argument is negative",
            *this, 0);
    setMetaTextAndNotify("meta:editing-duration", durationToText(the_value));
}

void SAL_CALL
SfxDocumentMetaData::resetUserData(const OUString & the_value)
{
    ::osl::ClearableMutexGuard g(m_aMutex);

    bool bModified( false );
    bModified |= setMetaText("meta:initial-creator", the_value);
    ::DateTime now( ::DateTime::SYSTEM );
    css::util::DateTime uDT(now.GetUNODateTime());
    bModified |= setMetaText("meta:creation-date", dateTimeToText(uDT));
    bModified |= setMetaText("dc:creator", OUString());
    bModified |= setMetaText("meta:printed-by", OUString());
    bModified |= setMetaText("dc:date", dateTimeToText(css::util::DateTime()));
    bModified |= setMetaText("meta:print-date",
        dateTimeToText(css::util::DateTime()));
    bModified |= setMetaText("meta:editing-duration", durationToText(0));
    bModified |= setMetaText("meta:editing-cycles",
        "1");

    if (bModified) {
        g.clear();
        setModified(true);
    }
}


css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
SfxDocumentMetaData::getUserDefinedProperties()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    createUserDefined();
    return m_xUserDefined;
}


void SAL_CALL
SfxDocumentMetaData::loadFromStorage(
        const css::uno::Reference< css::embed::XStorage > & xStorage,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium)
{
    if (!xStorage.is())
        throw css::lang::IllegalArgumentException("SfxDocumentMetaData::loadFromStorage: argument is null", *this, 0);
    ::osl::MutexGuard g(m_aMutex);

    // open meta data file
    css::uno::Reference<css::io::XStream> xStream(
        xStorage->openStreamElement(
            s_meta,
            css::embed::ElementModes::READ) );
    if (!xStream.is()) throw css::uno::RuntimeException();
    css::uno::Reference<css::io::XInputStream> xInStream =
        xStream->getInputStream();
    if (!xInStream.is()) throw css::uno::RuntimeException();

    // create DOM parser service
    css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
        m_xContext->getServiceManager());
    css::uno::Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(m_xContext);
    css::xml::sax::InputSource input;
    input.aInputStream = xInStream;

    sal_uInt64 version = SotStorage::GetVersion( xStorage );
    // Oasis is also the default (0)
    bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
    const char *pServiceName = bOasis
        ? "com.sun.star.document.XMLOasisMetaImporter"
        : "com.sun.star.document.XMLMetaImporter";

    // set base URL
    css::uno::Reference<css::beans::XPropertySet> xPropArg =
        getURLProperties(Medium);
    try {
        xPropArg->getPropertyValue("BaseURI")
            >>= input.sSystemId;
        input.sSystemId += OUStringLiteral("/") + s_meta;
    } catch (const css::uno::Exception &) {
        input.sSystemId = s_meta;
    }
    css::uno::Sequence< css::uno::Any > args(1);
    args[0] <<= xPropArg;

    css::uno::Reference<css::xml::sax::XDocumentHandler> xDocHandler (
        xMsf->createInstanceWithArgumentsAndContext(
            OUString::createFromAscii(pServiceName), args, m_xContext),
        css::uno::UNO_QUERY_THROW);
    css::uno::Reference<css::document::XImporter> xImp (xDocHandler,
        css::uno::UNO_QUERY_THROW);
    xImp->setTargetDocument(css::uno::Reference<css::lang::XComponent>(this));
    xParser->setDocumentHandler(xDocHandler);
    css::uno::Reference< css::xml::sax::XFastParser > xFastParser = dynamic_cast<
                            css::xml::sax::XFastParser* >( xDocHandler.get() );
    try {
        if( xFastParser.is() )
            xFastParser->parseStream(input);
        else
            xParser->parseStream(input);
    } catch (const css::xml::sax::SAXException &) {
        throw css::io::WrongFormatException(
                "SfxDocumentMetaData::loadFromStorage:"
                " XML parsing exception", *this);
    }
    // NB: the implementation of XMLOasisMetaImporter calls initialize
    checkInit();
}

void SAL_CALL
SfxDocumentMetaData::storeToStorage(
        const css::uno::Reference< css::embed::XStorage > & xStorage,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium)
{
    if (!xStorage.is())
        throw css::lang::IllegalArgumentException(
            "SfxDocumentMetaData::storeToStorage: argument is null", *this, 0);
    ::osl::MutexGuard g(m_aMutex);
    checkInit();

    // update user-defined meta data in DOM tree
//    updateUserDefinedAndAttributes(); // this will be done in serialize!

    // write into storage
    css::uno::Reference<css::io::XStream> xStream =
        xStorage->openStreamElement(s_meta,
            css::embed::ElementModes::WRITE
            | css::embed::ElementModes::TRUNCATE);
    if (!xStream.is()) throw css::uno::RuntimeException();
    css::uno::Reference< css::beans::XPropertySet > xStreamProps(xStream,
        css::uno::UNO_QUERY_THROW);
    xStreamProps->setPropertyValue(
        "MediaType",
        css::uno::makeAny(OUString("text/xml")));
    xStreamProps->setPropertyValue(
        "Compressed",
        css::uno::makeAny(false));
    xStreamProps->setPropertyValue(
        "UseCommonStoragePasswordEncryption",
        css::uno::makeAny(false));
    css::uno::Reference<css::io::XOutputStream> xOutStream =
        xStream->getOutputStream();
    if (!xOutStream.is()) throw css::uno::RuntimeException();
    css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
        m_xContext->getServiceManager());
    css::uno::Reference<css::xml::sax::XWriter> xSaxWriter(
        css::xml::sax::Writer::create(m_xContext));
    xSaxWriter->setOutputStream(xOutStream);

    const sal_uInt64 version = SotStorage::GetVersion( xStorage );
    // Oasis is also the default (0)
    const bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
    const char *pServiceName = bOasis
        ? "com.sun.star.document.XMLOasisMetaExporter"
        : "com.sun.star.document.XMLMetaExporter";

    // set base URL
    css::uno::Reference<css::beans::XPropertySet> xPropArg =
        getURLProperties(Medium);
    css::uno::Sequence< css::uno::Any > args(2);
    args[0] <<= xSaxWriter;
    args[1] <<= xPropArg;

    css::uno::Reference<css::document::XExporter> xExp(
        xMsf->createInstanceWithArgumentsAndContext(
            OUString::createFromAscii(pServiceName), args, m_xContext),
        css::uno::UNO_QUERY_THROW);
    xExp->setSourceDocument(css::uno::Reference<css::lang::XComponent>(this));
    css::uno::Reference<css::document::XFilter> xFilter(xExp,
        css::uno::UNO_QUERY_THROW);
    if (!xFilter->filter(css::uno::Sequence< css::beans::PropertyValue >())) {
        throw css::io::IOException(
                "SfxDocumentMetaData::storeToStorage: cannot filter", *this);
    }
    css::uno::Reference<css::embed::XTransactedObject> xTransaction(
        xStorage, css::uno::UNO_QUERY);
    if (xTransaction.is()) {
        xTransaction->commit();
    }
}

void SAL_CALL
SfxDocumentMetaData::loadFromMedium(const OUString & URL,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium)
{
    css::uno::Reference<css::io::XInputStream> xIn;
    utl::MediaDescriptor md(Medium);
    // if we have a URL parameter, it replaces the one in the media descriptor
    if (!URL.isEmpty()) {
        md[ utl::MediaDescriptor::PROP_URL() ] <<= URL;
        md[ utl::MediaDescriptor::PROP_READONLY() ] <<= true;
    }
    if (md.addInputStream()) {
        md[ utl::MediaDescriptor::PROP_INPUTSTREAM() ] >>= xIn;
    }
    css::uno::Reference<css::embed::XStorage> xStorage;
    try {
        if (xIn.is()) {
            xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
                            xIn, m_xContext);
        } else { // fallback to url parameter
            xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
                            URL, css::embed::ElementModes::READ, m_xContext);
        }
    } catch (const css::uno::RuntimeException &) {
        throw;
    } catch (const css::io::IOException &) {
        throw;
    } catch (const css::uno::Exception &) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetException(
                "SfxDocumentMetaData::loadFromMedium: exception",
                css::uno::Reference<css::uno::XInterface>(*this),
                anyEx);
    }
    if (!xStorage.is()) {
        throw css::uno::RuntimeException(
                "SfxDocumentMetaData::loadFromMedium: cannot get Storage",
                *this);
    }
    loadFromStorage(xStorage, md.getAsConstPropertyValueList());
}

void SAL_CALL
SfxDocumentMetaData::storeToMedium(const OUString & URL,
        const css::uno::Sequence< css::beans::PropertyValue > & Medium)
{
    utl::MediaDescriptor md(Medium);
    if (!URL.isEmpty()) {
        md[ utl::MediaDescriptor::PROP_URL() ] <<= URL;
    }
    SfxMedium aMedium(md.getAsConstPropertyValueList());
    css::uno::Reference<css::embed::XStorage> xStorage
        = aMedium.GetOutputStorage();


    if (!xStorage.is()) {
        throw css::uno::RuntimeException(
                "SfxDocumentMetaData::storeToMedium: cannot get Storage",
                *this);
    }
    // set MIME type of the storage
    utl::MediaDescriptor::const_iterator iter
        = md.find(utl::MediaDescriptor::PROP_MEDIATYPE());
    if (iter != md.end()) {
        css::uno::Reference< css::beans::XPropertySet > xProps(xStorage,
            css::uno::UNO_QUERY_THROW);
        xProps->setPropertyValue(
            utl::MediaDescriptor::PROP_MEDIATYPE(),
            iter->second);
    }
    storeToStorage(xStorage, md.getAsConstPropertyValueList());


    const bool bOk = aMedium.Commit();
    aMedium.Close();
    if ( !bOk ) {
        ErrCode nError = aMedium.GetError();
        if ( nError == ERRCODE_NONE ) {
            nError = ERRCODE_IO_GENERAL;
        }

        throw css::task::ErrorCodeIOException(
            "SfxDocumentMetaData::storeToMedium <" + URL + "> Commit failed: " + nError.toHexString(),
            css::uno::Reference< css::uno::XInterface >(), sal_uInt32(nError));

    }
}

// css::lang::XInitialization:
void SAL_CALL SfxDocumentMetaData::initialize( const css::uno::Sequence< css::uno::Any > & aArguments)
{
    // possible arguments:
    // - no argument: default initialization (empty DOM)
    // - 1 argument, XDocument: initialize with given DOM and empty base URL
    // NB: links in document must be absolute

    ::osl::MutexGuard g(m_aMutex);
    css::uno::Reference<css::xml::dom::XDocument> xDoc;

    for (sal_Int32 i = 0; i < aArguments.getLength(); ++i) {
        const css::uno::Any any = aArguments[i];
        if (!(any >>= xDoc)) {
            throw css::lang::IllegalArgumentException(
                "SfxDocumentMetaData::initialize: argument must be XDocument",
                *this, static_cast<sal_Int16>(i));
        }
        if (!xDoc.is()) {
            throw css::lang::IllegalArgumentException(
                "SfxDocumentMetaData::initialize: argument is null",
                *this, static_cast<sal_Int16>(i));
        }
    }

    if (!xDoc.is()) {
        // For a new document, we create a new DOM tree here.
        xDoc = createDOM();
    }

    init(xDoc);
}

// css::util::XCloneable:
css::uno::Reference<css::util::XCloneable> SAL_CALL
SfxDocumentMetaData::createClone()
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();

    SfxDocumentMetaData *pNew = createMe(m_xContext);

    // NB: do not copy the modification listeners, only DOM
    css::uno::Reference<css::xml::dom::XDocument> xDoc = createDOM();
    try {
        updateUserDefinedAndAttributes();
        // deep copy of root node
        css::uno::Reference<css::xml::dom::XNode> xRoot(
            m_xDoc->getDocumentElement(), css::uno::UNO_QUERY_THROW);
        css::uno::Reference<css::xml::dom::XNode> xRootNew(
            xDoc->importNode(xRoot, true));
        xDoc->appendChild(xRootNew);
        pNew->init(xDoc);
    } catch (const css::uno::RuntimeException &) {
        throw;
    } catch (const css::uno::Exception &) {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(
                "SfxDocumentMetaData::createClone: exception",
                css::uno::Reference<css::uno::XInterface>(*this), anyEx);
    }
    return css::uno::Reference<css::util::XCloneable> (pNew);
}

// css::util::XModifiable:
sal_Bool SAL_CALL SfxDocumentMetaData::isModified(  )
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    css::uno::Reference<css::util::XModifiable> xMB(m_xUserDefined,
        css::uno::UNO_QUERY);
    return m_isModified || (xMB.is() && xMB->isModified());
}

void SAL_CALL SfxDocumentMetaData::setModified( sal_Bool bModified )
{
    css::uno::Reference<css::util::XModifiable> xMB;
    { // do not lock mutex while notifying (#i93514#) to prevent deadlock
        ::osl::MutexGuard g(m_aMutex);
        checkInit();
        m_isModified = bModified;
        if ( !bModified && m_xUserDefined.is() )
        {
            xMB.set(m_xUserDefined, css::uno::UNO_QUERY);
            assert(xMB.is() &&
                "SfxDocumentMetaData::setModified: PropertyBag not Modifiable?");
        }
    }
    if (bModified) {
        try {
            css::uno::Reference<css::uno::XInterface> xThis(*this);
            css::lang::EventObject event(xThis);
            m_NotifyListeners.notifyEach(&css::util::XModifyListener::modified,
                event);
        } catch (const css::uno::RuntimeException &) {
            throw;
        } catch (const css::uno::Exception &) {
            // ignore
            TOOLS_WARN_EXCEPTION("sfx.doc", "setModified");
        }
    } else {
        if (xMB.is()) {
            xMB->setModified(false);
        }
    }
}

// css::util::XModifyBroadcaster:
void SAL_CALL SfxDocumentMetaData::addModifyListener(
        const css::uno::Reference< css::util::XModifyListener > & xListener)
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    m_NotifyListeners.addInterface(xListener);
    css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
        css::uno::UNO_QUERY);
    if (xMB.is()) {
        xMB->addModifyListener(xListener);
    }
}

void SAL_CALL SfxDocumentMetaData::removeModifyListener(
        const css::uno::Reference< css::util::XModifyListener > & xListener)
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    m_NotifyListeners.removeInterface(xListener);
    css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
        css::uno::UNO_QUERY);
    if (xMB.is()) {
        xMB->removeModifyListener(xListener);
    }
}

// css::xml::sax::XSAXSerializable
void SAL_CALL SfxDocumentMetaData::serialize(
    const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
    const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces)
{
    ::osl::MutexGuard g(m_aMutex);
    checkInit();
    updateUserDefinedAndAttributes();
    css::uno::Reference<css::xml::sax::XSAXSerializable> xSAXable(m_xDoc,
        css::uno::UNO_QUERY_THROW);
    xSAXable->serialize(i_xHandler, i_rNamespaces);
}

void SfxDocumentMetaData::createUserDefined()
{
    // user-defined meta data: create PropertyBag which only accepts property
    // values of allowed types
    if ( m_xUserDefined.is() )
        return;

    css::uno::Sequence<css::uno::Type> types(13);
    types[ 0] = ::cppu::UnoType<bool>::get();
    types[ 1] = ::cppu::UnoType< OUString>::get();
    types[ 2] = ::cppu::UnoType<css::util::DateTime>::get();
    types[ 3] = ::cppu::UnoType<css::util::Date>::get();
    types[ 4] = ::cppu::UnoType<css::util::DateTimeWithTimezone>::get();
    types[ 5] = ::cppu::UnoType<css::util::DateWithTimezone>::get();
    types[ 6] = ::cppu::UnoType<css::util::Duration>::get();
    types[ 7] = ::cppu::UnoType<float>::get();
    types[ 8] = ::cppu::UnoType<double>::get();
    types[ 9] = ::cppu::UnoType<sal_Int16>::get();
    types[10] = ::cppu::UnoType<sal_Int32>::get();
    types[11] = ::cppu::UnoType<sal_Int64>::get();
    // Time is supported for backward compatibility with OOo 3.x, x<=2
    types[12] = ::cppu::UnoType<css::util::Time>::get();
    // #i94175#:  ODF allows empty user-defined property names!
    m_xUserDefined.set(
        css::beans::PropertyBag::createWithTypes( m_xContext, types, true/*AllowEmptyPropertyName*/, false/*AutomaticAddition*/ ),
        css::uno::UNO_QUERY_THROW);

    const css::uno::Reference<css::util::XModifyBroadcaster> xMB(
        m_xUserDefined, css::uno::UNO_QUERY);
    if (xMB.is())
    {
        const std::vector<css::uno::Reference<css::uno::XInterface> >
            listeners(m_NotifyListeners.getElements());
        for (const auto& l : listeners) {
            xMB->addModifyListener(
                css::uno::Reference< css::util::XModifyListener >(l, css::uno::UNO_QUERY) );
        }
    }
}

} // closing anonymous implementation namespace

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
CompatWriterDocPropsImpl_get_implementation(
    css::uno::XComponentContext *context,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new CompatWriterDocPropsImpl(context));
}

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
SfxDocumentMetaData_get_implementation(
    css::uno::XComponentContext *context,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new SfxDocumentMetaData(context));
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
