/* -*- 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 <osl/diagnose.h>
#include <sfx2/Metadatable.hxx>
#include <sfx2/XmlIdRegistry.hxx>

#include <utility>
#include <vcl/svapp.hxx>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <comphelper/random.hxx>
#include <comphelper/diagnose_ex.hxx>

#include <algorithm>
#include <memory>
#include <string_view>
#include <unordered_map>
#if OSL_DEBUG_LEVEL > 0
#include <typeinfo>
#endif


/** XML ID handling.

    There is an abstract base class <type>XmlIdRegistry</type>, with
    2 subclasses <type>XmlIdRegistryDocument</type> for "normal" documents,
    and <type>XmlIdRegistryClipboard</type> for clipboard documents.
    These classes are responsible for managing XML IDs for all elements
    of the model. Only the implementation of the <type>Metadatable</type>
    base class needs to know the registries, so they are not in the header.

    The handling of XML IDs differs between clipboard and non-clipboard
    documents in several aspects. Most importantly, non-clipboard documents
    can have several elements associated with one XML ID.
    This is necessary because of the weird undo implementation:
    deleting a text node moves the deleted node to the undo array, but
    executing undo will then create a <em>copy</em> of that node in the
    document array. These 2 nodes must have the same XML ID, because
    we cannot know whether the user will do a redo next, or something else.

    Because we need to have a mechanism for several objects per XML ID anyway,
    we use that also to enable some usability features:
    The document registry has a list of Metadatables per XML ID.
    This list is sorted by priority, i.e., the first element has highest
    priority. When inserting copies, care must be taken that they are inserted
    at the right position: either before or after the source.
    This is done by <method>Metadatable::RegisterAsCopyOf</method>.
    When a text node is split, then both resulting text nodes are inserted
    into the list. If the user then deletes one text node, the other one
    will have the XML ID.
    Also, when a Metadatable is copied to the clipboard and then pasted,
    the copy is inserted into the list. If the user then deletes the source,
    the XML ID is not lost.
    The goal is that it should be hard to lose an XML ID by accident, which
    is especially important as long as we do not have an UI that displays them.

    There are two subclasses of <type>Metadatable</type>:
    <ul><li><type>MetadatableClipboard</type>: for copies in the clipboard</li>
        <li><type>MetadatableUndo</type>: for undo, because a Metadatable
        may be destroyed on delete and a new one created on undo.</li></ul>
    These serve only to track the position in an XML ID list in a document
    registry, so that future actions can insert objects at the right position.
    Unfortunately, inserting dummy objects seems to be necessary:
    <ul><li>it is not sufficient to just remember the saved id, because then
            the relative priorities might change when executing the undo</li>
        <li>it is not sufficient to record the position as an integer, because
            if we delete a text node and then undo, the node will be copied(!),
            and we will have one more node in the list.<li>
        <li>it is not sufficient to record the pointer of the previous/next
            Metadatable, because if we delete a text node, undo, and then
            do something to clear the redo array, the original text node is
            destroyed, and is replaced by the copy created by undo</li></ul>

    If content from a non-clipboard document is copied into a clipboard
    document, a dummy <type>MetadatableClipboard</type> is inserted into the
    non-clipboard document registry in order to track the position of the
    source element.  When the clipboard content is pasted back into the source
    document, this dummy object is used to associate the pasted element with
    that same XML ID.

    If a <type>Metadatable</type> is deleted or merged,
    <method>Metadatable::CreateUndo</method> is called, and returns a
    <type>MetadatableUndo<type> instance, which can be used to undo the action
    by passing it to <method>Metadatable::RestoreMetadata</method>.

 */


using namespace ::com::sun::star;

using ::sfx2::isValidXmlId;


namespace sfx2 {

constexpr OUString s_content = u"content.xml"_ustr;
constexpr OUString s_styles  = u"styles.xml"_ustr;

static bool isContentFile(std::u16string_view i_rPath)
{
    return i_rPath == s_content;
}

static bool isStylesFile (std::u16string_view i_rPath)
{
    return i_rPath == s_styles;
}


// XML ID handling ---------------------------------------------------

/** handles registration of XMetadatable.

    This class is responsible for guaranteeing that XMetadatable objects
    always have XML IDs that are unique within a stream.

    This is an abstract base class; see subclasses XmlIdRegistryDocument and
    XmlIdRegistryClipboard.

    @see SwDoc::GetXmlIdRegistry
    @see SwDocShell::GetXmlIdRegistry
 */
class XmlIdRegistry : public sfx2::IXmlIdRegistry
{

public:
    XmlIdRegistry();

    /** get the ODF element with the given metadata reference. */
    virtual css::uno::Reference< css::rdf::XMetadatable >
        GetElementByMetadataReference(
            const css::beans::StringPair & i_rReference) const
        override;

    /** register an ODF element at a newly generated, unique metadata reference.

        <p>
        Find a fresh XML ID, and register it for the element.
        The generated ID does not occur in any stream of the document.
        </p>
     */
    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) = 0;

    /** try to register an ODF element at a given XML ID, or update its
        registration to a different XML ID.

        <p>
        If the given new metadata reference is not already occupied in the
        document, unregister the element at its old metadata reference if
        it has one, and register the new metadata reference for the element.
        Note that this method only ensures that XML IDs are unique per stream,
        so using the same XML ID in both content.xml and styles.xml is allowed.
        </p>

        @returns
            true iff the element has successfully been registered
     */
    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
        OUString const& i_rStreamName, OUString const& i_rIdref)
        = 0;

    /** unregister an ODF element.

        <p>
        Unregister the element at its metadata reference.
        Does not remove the metadata reference from the element.
        </p>

        @see RemoveXmlIdForElement
     */
    virtual void UnregisterMetadatable(Metadatable const&) = 0;

    /** get the metadata reference for the given element. */
    css::beans::StringPair
        GetXmlIdForElement(Metadatable const&) const;

    /** remove the metadata reference for the given element. */
    virtual void RemoveXmlIdForElement(Metadatable const&) = 0;

protected:

    virtual bool LookupXmlId(const Metadatable& i_xObject,
        OUString & o_rStream, OUString & o_rIdref) const = 0;

    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
        const OUString & i_rIdref) const = 0;
};

// XmlIdRegistryDocument ---------------------------------------------

namespace {

/** non-clipboard documents */
class XmlIdRegistryDocument : public XmlIdRegistry
{

public:
    XmlIdRegistryDocument();

    virtual ~XmlIdRegistryDocument() override;

    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;

    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
        OUString const& i_rStreamName, OUString const& i_rIdref) override;

    virtual void UnregisterMetadatable(Metadatable const&) override;

    virtual void RemoveXmlIdForElement(Metadatable const&) override;

    /** register i_rCopy as a copy of i_rSource,
        with precedence iff i_bCopyPrecedesSource is true */
    void RegisterCopy(Metadatable const& i_rSource, Metadatable & i_rCopy,
        const bool i_bCopyPrecedesSource);

    /** create a Undo Metadatable for i_rObject. */
    static std::shared_ptr<MetadatableUndo> CreateUndo(
        Metadatable const& i_rObject);

    /** merge i_rMerged and i_rOther into i_rMerged. */
    void JoinMetadatables(Metadatable & i_rMerged, Metadatable const& i_rOther);

    // unfortunately public, Metadatable::RegisterAsCopyOf needs this
    virtual bool LookupXmlId(const Metadatable& i_xObject,
        OUString & o_rStream, OUString & o_rIdref) const override;

private:

    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
        const OUString & i_rIdref) const override;

    struct XmlIdRegistry_Impl;
    ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
};

}

// MetadatableUndo ---------------------------------------------------

/** the horrible Undo Metadatable: is inserted into lists to track position */
class MetadatableUndo : public Metadatable
{
    /// as determined by the stream of the source in original document
    const bool m_isInContent;
public:
    explicit MetadatableUndo(const bool i_isInContent)
        : m_isInContent(i_isInContent) { }
    virtual ::sfx2::XmlIdRegistry& GetRegistry() override
    {
        // N.B. for Undo, m_pReg is initialized by registering this as copy in
        // CreateUndo; it is never cleared
        OSL_ENSURE(m_pReg, "no m_pReg in MetadatableUndo ?");
        return *m_pReg;
    }
    virtual bool IsInClipboard() const override { return false; }
    virtual bool IsInUndo() const override { return true; }
    virtual bool IsInContent() const override { return m_isInContent; }
    virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
    { OSL_FAIL("MetadatableUndo::MakeUnoObject"); throw; }
};

// MetadatableClipboard ----------------------------------------------

/** the horrible Clipboard Metadatable: inserted into lists to track position */
class MetadatableClipboard : public Metadatable
{
    /// as determined by the stream of the source in original document
    const bool m_isInContent;
public:
    explicit MetadatableClipboard(const bool i_isInContent)
        : m_isInContent(i_isInContent) { }
    virtual ::sfx2::XmlIdRegistry& GetRegistry() override
    {
        // N.B. for Clipboard, m_pReg is initialized by registering this as copy in
        // RegisterAsCopyOf; it is only cleared by OriginNoLongerInBusinessAnymore
        assert(m_pReg && "no m_pReg in MetadatableClipboard ?");
        return *m_pReg;
    }
    virtual bool IsInClipboard() const override { return true; }
    virtual bool IsInUndo() const override { return false; }
    virtual bool IsInContent() const override { return m_isInContent; }
    virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
    { OSL_FAIL("MetadatableClipboard::MakeUnoObject"); throw; }
    void OriginNoLongerInBusinessAnymore() { m_pReg = nullptr; }
};

// XmlIdRegistryClipboard --------------------------------------------

namespace {

class XmlIdRegistryClipboard : public XmlIdRegistry
{

public:
    XmlIdRegistryClipboard();

    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;

    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
        OUString const& i_rStreamName, OUString const& i_rIdref) override;

    virtual void UnregisterMetadatable(Metadatable const&) override;

    virtual void RemoveXmlIdForElement(Metadatable const&) override;

    /** register i_rCopy as a copy of i_rSource */
    MetadatableClipboard & RegisterCopyClipboard(Metadatable & i_rCopy,
        beans::StringPair const & i_rReference,
        const bool i_isLatent);

    /** get the Metadatable that links i_rObject to its origin registry */
    MetadatableClipboard const* SourceLink(Metadatable const& i_rObject);

private:
    virtual bool LookupXmlId(const Metadatable& i_xObject,
        OUString & o_rStream, OUString & o_rIdref) const override;

    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
        const OUString & i_rIdref) const override;

    /** create a Clipboard Metadatable for i_rObject. */
    static std::shared_ptr<MetadatableClipboard> CreateClipboard(
        const bool i_isInContent);

    struct XmlIdRegistry_Impl;
    ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
};

}

// XmlIdRegistry

::sfx2::IXmlIdRegistry * createXmlIdRegistry(const bool i_DocIsClipboard)
{
    return i_DocIsClipboard
        ? static_cast<XmlIdRegistry*>( new XmlIdRegistryClipboard )
        : static_cast<XmlIdRegistry*>( new XmlIdRegistryDocument );
}

XmlIdRegistry::XmlIdRegistry()
{
}

css::uno::Reference< css::rdf::XMetadatable >
XmlIdRegistry::GetElementByMetadataReference(
    const beans::StringPair & i_rReference) const
{
    Metadatable* pObject( LookupElement(i_rReference.First,
        i_rReference.Second) );
    return pObject ? pObject->MakeUnoObject() : nullptr;
}

beans::StringPair
XmlIdRegistry::GetXmlIdForElement(const Metadatable& i_rObject) const
{
    OUString path;
    OUString idref;
    if (LookupXmlId(i_rObject, path, idref))
    {
        if (LookupElement(path, idref) == &i_rObject)
        {
            return beans::StringPair(path, idref);
        }
    }
    return beans::StringPair();
}


/// generate unique xml:id
template< typename T >
static OUString create_id(const
    std::unordered_map< OUString, T > & i_rXmlIdMap)
{
    static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
    static const char prefix[] = "id";  // prefix for generated xml:id
    typename std::unordered_map< OUString, T >
        ::const_iterator iter;
    OUString id;

    if (bHack)
    {
        static sal_Int64 nIdCounter = SAL_CONST_INT64(4000000000);
        do
        {
            id = prefix + OUString::number(nIdCounter++);
            iter = i_rXmlIdMap.find(id);
        }
        while (iter != i_rXmlIdMap.end());
    }
    else
    {
        do
        {
            unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
                                    std::numeric_limits<unsigned int>::max()));
            id = prefix + OUString::number(n);
            iter = i_rXmlIdMap.find(id);
        }
        while (iter != i_rXmlIdMap.end());
    }
    return id;
}


// Document XML ID Registry (_Impl)

/// element list
typedef ::std::vector< Metadatable* > XmlIdVector_t;

/// Idref -> (content.xml element list, styles.xml element list)
typedef std::unordered_map< OUString,
    ::std::pair< XmlIdVector_t, XmlIdVector_t > > XmlIdMap_t;

namespace {

/// pointer hash template
template<typename T> struct PtrHash
{
    size_t operator() (T const * i_pT) const
    {
        return reinterpret_cast<size_t>(i_pT);
    }
};

}

/// element -> (stream name, idref)
typedef std::unordered_map< const Metadatable*,
    ::std::pair< OUString, OUString>, PtrHash<Metadatable> >
    XmlIdReverseMap_t;

struct XmlIdRegistryDocument::XmlIdRegistry_Impl
{
    XmlIdRegistry_Impl() {}

    bool TryInsertMetadatable(Metadatable& i_xObject,
        std::u16string_view i_rStream, const OUString & i_rIdref);

    bool LookupXmlId(const Metadatable& i_xObject,
        OUString & o_rStream, OUString & o_rIdref) const;

    Metadatable* LookupElement(std::u16string_view i_rStreamName,
        const OUString & i_rIdref) const;

    const XmlIdVector_t * LookupElementVector(
        std::u16string_view i_rStreamName,
        const OUString & i_rIdref) const;

          XmlIdVector_t * LookupElementVector(
        std::u16string_view i_rStreamName,
        const OUString & i_rIdref)
    {
        return const_cast<XmlIdVector_t*>(
            std::as_const(*this).LookupElementVector(i_rStreamName, i_rIdref));
    }

    XmlIdMap_t m_XmlIdMap;
    XmlIdReverseMap_t m_XmlIdReverseMap;
};


static void
rmIter(XmlIdMap_t & i_rXmlIdMap, XmlIdMap_t::iterator const& i_rIter,
    std::u16string_view i_rStream, Metadatable const& i_rObject)
{
    if (i_rIter != i_rXmlIdMap.end())
    {
        XmlIdVector_t & rVector( isContentFile(i_rStream)
            ? i_rIter->second.first : i_rIter->second.second );
        std::erase(rVector, &const_cast<Metadatable&>(i_rObject));
        if (i_rIter->second.first.empty() && i_rIter->second.second.empty())
        {
            i_rXmlIdMap.erase(i_rIter);
        }
    }
}


const XmlIdVector_t *
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElementVector(
    std::u16string_view i_rStreamName,
    const OUString & i_rIdref) const
{
    const XmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
    if (iter != m_XmlIdMap.end())
    {
        OSL_ENSURE(!iter->second.first.empty() || !iter->second.second.empty(),
            "null entry in m_XmlIdMap");
        return (isContentFile(i_rStreamName))
            ?  &iter->second.first
            :  &iter->second.second;
    }
    else
    {
        return nullptr;
    }
}

Metadatable*
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElement(
    std::u16string_view i_rStreamName,
    const OUString & i_rIdref) const
{
    if (!isValidXmlId(i_rStreamName, i_rIdref))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
    }

    const XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
    if (pList)
    {
        const XmlIdVector_t::const_iterator iter(
            ::std::find_if(pList->begin(), pList->end(),
                [](Metadatable* item)->bool {
                    return !(item->IsInUndo() || item->IsInClipboard());
                    } ) ) ;
        if (iter != pList->end())
        {
            return *iter;
        }
    }
    return nullptr;
}

bool
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupXmlId(
    const Metadatable& i_rObject,
    OUString & o_rStream, OUString & o_rIdref) const
{
    const XmlIdReverseMap_t::const_iterator iter(
        m_XmlIdReverseMap.find(&i_rObject) );
    if (iter != m_XmlIdReverseMap.end())
    {
        OSL_ENSURE(!iter->second.first.isEmpty(),
            "null stream in m_XmlIdReverseMap");
        OSL_ENSURE(!iter->second.second.isEmpty(),
            "null id in m_XmlIdReverseMap");
        o_rStream = iter->second.first;
        o_rIdref  = iter->second.second;
        return true;
    }
    else
    {
        return false;
    }
}

bool
XmlIdRegistryDocument::XmlIdRegistry_Impl::TryInsertMetadatable(
    Metadatable & i_rObject,
    std::u16string_view i_rStreamName, const OUString & i_rIdref)
{
    const bool bContent( isContentFile(i_rStreamName) );
    OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
        "invalid stream");

    XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
    if (pList)
    {
        if (pList->empty())
        {
            pList->push_back( &i_rObject );
            return true;
        }
        else
        {
            // this is only called from TryRegister now, so check
            // if all elements in the list are deleted (in undo) or
            // placeholders, then "steal" the id from them
            if ( std::none_of(pList->begin(), pList->end(),
                [](Metadatable* item)->bool {
                    return !(item->IsInUndo() || item->IsInClipboard());
                    } ) )
            {
                pList->insert(pList->begin(), &i_rObject );
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    else
    {
        m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
            ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
            : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
        return true;
    }
}


// Document XML ID Registry


XmlIdRegistryDocument::XmlIdRegistryDocument()
    :   m_pImpl( new XmlIdRegistry_Impl )
{
}

static void
removeLink(Metadatable* i_pObject)
{
    OSL_ENSURE(i_pObject, "null in list ???");
    if (!i_pObject) return;
    if (i_pObject->IsInClipboard())
    {
        MetadatableClipboard* pLink(
            dynamic_cast<MetadatableClipboard*>( i_pObject ) );
        OSL_ENSURE(pLink, "IsInClipboard, but no MetadatableClipboard ?");
        if (pLink)
        {
            pLink->OriginNoLongerInBusinessAnymore();
        }
    }
}

XmlIdRegistryDocument::~XmlIdRegistryDocument()
{
    // notify all list elements that are actually in the clipboard
    for (const auto& aXmlId : m_pImpl->m_XmlIdMap) {
        for (auto aLink : aXmlId.second.first)
            removeLink(aLink);
        for (auto aLink : aXmlId.second.second)
            removeLink(aLink);
    }
}

bool
XmlIdRegistryDocument::LookupXmlId(
    const Metadatable& i_rObject,
    OUString & o_rStream, OUString & o_rIdref) const
{
    return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref);
}

Metadatable*
XmlIdRegistryDocument::LookupElement(
    const OUString & i_rStreamName,
    const OUString & i_rIdref) const
{
    return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
}

bool
XmlIdRegistryDocument::TryRegisterMetadatable(Metadatable & i_rObject,
    OUString const& i_rStreamName, OUString const& i_rIdref)
{
    SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref << ")");

    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
        "TryRegisterMetadatable called for MetadatableUndo?");
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
        "TryRegisterMetadatable called for MetadatableClipboard?");

    if (!isValidXmlId(i_rStreamName, i_rIdref))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
    }
    if (i_rObject.IsInContent()
        ?   !isContentFile(i_rStreamName)
        :   !isStylesFile(i_rStreamName))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId: wrong stream"_ustr, nullptr, 0);
    }

    OUString old_path;
    OUString old_idref;
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);
    if (old_path  == i_rStreamName && old_idref == i_rIdref)
    {
        return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
    }
    XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
    if (!old_idref.isEmpty())
    {
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
    }
    if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
    {
        rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
        m_pImpl->m_XmlIdReverseMap[&i_rObject] =
            ::std::make_pair(i_rStreamName, i_rIdref);
        return true;
    }
    else
    {
        return false;
    }
}

void
XmlIdRegistryDocument::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
{
    SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);

    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
        "RegisterMetadatableAndCreateID called for MetadatableUndo?");
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
        "RegisterMetadatableAndCreateID called for MetadatableClipboard?");

    const bool isInContent( i_rObject.IsInContent() );
    const OUString stream(
        isInContent ? s_content : s_styles );
    // check if we have a latent xmlid, and if yes, remove it
    OUString old_path;
    OUString old_idref;
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);

    XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
    if (!old_idref.isEmpty())
    {
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
        if (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject)
        {
            return;
        }
        else
        {
            // remove latent xmlid
            rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
        }
    }

    // create id
    const OUString id( create_id(m_pImpl->m_XmlIdMap) );
    OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
        "created id is in use");
    m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
        ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
        : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
    m_pImpl->m_XmlIdReverseMap[&i_rObject] = ::std::make_pair(stream, id);
}

void XmlIdRegistryDocument::UnregisterMetadatable(const Metadatable& i_rObject)
{
    SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);

    OUString path;
    OUString idref;
    if (!m_pImpl->LookupXmlId(i_rObject, path, idref))
    {
        OSL_FAIL("unregister: no xml id?");
        return;
    }
    const XmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
    if (iter != m_pImpl->m_XmlIdMap.end())
    {
        rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
    }
}

void XmlIdRegistryDocument::RemoveXmlIdForElement(const Metadatable& i_rObject)
{
    SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);

    const XmlIdReverseMap_t::iterator iter(
        m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
    if (iter != m_pImpl->m_XmlIdReverseMap.end())
    {
        OSL_ENSURE(!iter->second.second.isEmpty(),
            "null id in m_XmlIdReverseMap");
        m_pImpl->m_XmlIdReverseMap.erase(iter);
    }
}


void XmlIdRegistryDocument::RegisterCopy(Metadatable const& i_rSource,
    Metadatable & i_rCopy, const bool i_bCopyPrecedesSource)
{
    SAL_INFO("sfx", "RegisterCopy: " << &i_rSource << " -> " << &i_rCopy << " (" << i_bCopyPrecedesSource << ")");

    // potential sources: clipboard, undo array, splitNode
    // assumption: stream change can only happen via clipboard, and is handled
    // by Metadatable::RegisterAsCopyOf
    OSL_ENSURE(i_rSource.IsInUndo() || i_rCopy.IsInUndo() ||
        (i_rSource.IsInContent() == i_rCopy.IsInContent()),
        "RegisterCopy: not in same stream?");

    OUString path;
    OUString idref;
    if (!m_pImpl->LookupXmlId( i_rSource, path, idref ))
    {
        OSL_FAIL("no xml id?");
        return;
    }
    XmlIdVector_t * pList ( m_pImpl->LookupElementVector(path, idref) );
    OSL_ENSURE( ::std::find( pList->begin(), pList->end(), &i_rCopy )
        == pList->end(), "copy already registered???");
    XmlIdVector_t::iterator srcpos(
        ::std::find( pList->begin(), pList->end(), &i_rSource ) );
    OSL_ENSURE(srcpos != pList->end(), "source not in list???");
    if (srcpos == pList->end())
    {
        return;
    }
    if (i_bCopyPrecedesSource)
    {
        pList->insert( srcpos, &i_rCopy );
    }
    else
    {
        // for undo push_back does not work! must insert right after source
        pList->insert( ++srcpos, &i_rCopy );
    }
    m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
        ::std::make_pair(path, idref)));
}

std::shared_ptr<MetadatableUndo>
XmlIdRegistryDocument::CreateUndo(Metadatable const& i_rObject)
{
    SAL_INFO("sfx", "CreateUndo: " << &i_rObject);

    return std::make_shared<MetadatableUndo>(
                i_rObject.IsInContent() );
}

/*
i_rMerged is both a source and the target node of the merge
i_rOther is the other source, and will be deleted after the merge

dimensions: none|latent|actual empty|nonempty
i_rMerged(1)    i_rOther(2)        result
     *|empty         *|empty    => 1|2 (arbitrary)
     *|empty         *|nonempty => 2
     *|nonempty      *|empty    => 1
  none|nonempty   none|nonempty => none
  none|nonempty latent|nonempty => 2
latent|nonempty   none|nonempty => 1
latent|nonempty latent|nonempty => 1|2
     *|nonempty actual|nonempty => 2
actual|nonempty      *|nonempty => 1
actual|nonempty actual|nonempty => 1|2
*/
void
XmlIdRegistryDocument::JoinMetadatables(
    Metadatable & i_rMerged, Metadatable const & i_rOther)
{
    SAL_INFO("sfx", "JoinMetadatables: " << &i_rMerged << " <- " << &i_rOther);

    bool mergedOwnsRef;
    OUString path;
    OUString idref;
    if (m_pImpl->LookupXmlId(i_rMerged, path, idref))
    {
        mergedOwnsRef = (m_pImpl->LookupElement(path, idref) == &i_rMerged);
    }
    else
    {
        OSL_FAIL("JoinMetadatables: no xmlid?");
        return;
    }
    if (!mergedOwnsRef)
    {
        i_rMerged.RemoveMetadataReference();
        i_rMerged.RegisterAsCopyOf(i_rOther, true);
        return;
    }
    // other cases: merged has actual ref and is nonempty,
    // other has latent/actual ref and is nonempty: other loses => nothing to do
}


// Clipboard XML ID Registry (_Impl)

namespace {

struct RMapEntry
{
    RMapEntry() {}
    RMapEntry(OUString i_aStream,
            OUString i_aXmlId,
            std::shared_ptr<MetadatableClipboard>  i_pLink
                = std::shared_ptr<MetadatableClipboard>())
        :   m_Stream(std::move(i_aStream)), m_XmlId(std::move(i_aXmlId)), m_xLink(std::move(i_pLink))
        {}
    OUString m_Stream;
    OUString m_XmlId;
    // this would have been an auto_ptr, if only that would have compiled...
    std::shared_ptr<MetadatableClipboard> m_xLink;
};

}

/// element -> (stream name, idref, source)
typedef std::unordered_map< const Metadatable*,
    struct RMapEntry,
    PtrHash<Metadatable> >
    ClipboardXmlIdReverseMap_t;

/// Idref -> (content.xml element, styles.xml element)
typedef std::unordered_map< OUString,
    ::std::pair< Metadatable*, Metadatable* > >
    ClipboardXmlIdMap_t;

struct XmlIdRegistryClipboard::XmlIdRegistry_Impl
{
    XmlIdRegistry_Impl() {}

    bool TryInsertMetadatable(Metadatable& i_xObject,
        std::u16string_view i_rStream, const OUString & i_rIdref);

    bool LookupXmlId(const Metadatable& i_xObject,
        OUString & o_rStream, OUString & o_rIdref,
        MetadatableClipboard const* &o_rpLink) const;

    Metadatable* LookupElement(std::u16string_view i_rStreamName,
        const OUString & i_rIdref) const;

    Metadatable* const* LookupEntry(std::u16string_view i_rStreamName,
        const OUString & i_rIdref) const;

    ClipboardXmlIdMap_t m_XmlIdMap;
    ClipboardXmlIdReverseMap_t m_XmlIdReverseMap;
};


static void
rmIter(ClipboardXmlIdMap_t & i_rXmlIdMap,
    ClipboardXmlIdMap_t::iterator const& i_rIter,
    std::u16string_view i_rStream, Metadatable const& i_rObject)
{
    if (i_rIter == i_rXmlIdMap.end())
        return;

    Metadatable *& rMeta = isContentFile(i_rStream)
        ? i_rIter->second.first : i_rIter->second.second;
    if (rMeta == &i_rObject)
    {
        rMeta = nullptr;
    }
    if (!i_rIter->second.first && !i_rIter->second.second)
    {
        i_rXmlIdMap.erase(i_rIter);
    }
}


Metadatable* const*
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupEntry(
    std::u16string_view i_rStreamName,
    const OUString & i_rIdref) const
{
    if (!isValidXmlId(i_rStreamName, i_rIdref))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
    }

    const ClipboardXmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
    if (iter != m_XmlIdMap.end())
    {
        OSL_ENSURE(iter->second.first || iter->second.second,
            "null entry in m_XmlIdMap");
        return (isContentFile(i_rStreamName))
            ?  &iter->second.first
            :  &iter->second.second;
    }
    else
    {
        return nullptr;
    }
}

Metadatable*
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupElement(
    std::u16string_view i_rStreamName,
    const OUString & i_rIdref) const
{
    Metadatable * const * ppEntry = LookupEntry(i_rStreamName, i_rIdref);
    return ppEntry ? *ppEntry : nullptr;
}

bool
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupXmlId(
    const Metadatable& i_rObject,
    OUString & o_rStream, OUString & o_rIdref,
    MetadatableClipboard const* &o_rpLink) const
{
    const ClipboardXmlIdReverseMap_t::const_iterator iter(
        m_XmlIdReverseMap.find(&i_rObject) );
    if (iter != m_XmlIdReverseMap.end())
    {
        OSL_ENSURE(!iter->second.m_Stream.isEmpty(),
            "null stream in m_XmlIdReverseMap");
        OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
            "null id in m_XmlIdReverseMap");
        o_rStream = iter->second.m_Stream;
        o_rIdref  = iter->second.m_XmlId;
        o_rpLink  = iter->second.m_xLink.get();
        return true;
    }
    else
    {
        return false;
    }
}

bool
XmlIdRegistryClipboard::XmlIdRegistry_Impl::TryInsertMetadatable(
    Metadatable & i_rObject,
    std::u16string_view i_rStreamName, const OUString & i_rIdref)
{
    bool bContent( isContentFile(i_rStreamName) );
    OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
        "invalid stream");

    Metadatable ** ppEntry = const_cast<Metadatable**>(LookupEntry(i_rStreamName, i_rIdref));
    if (ppEntry)
    {
        if (*ppEntry)
        {
            return false;
        }
        else
        {
            *ppEntry = &i_rObject;
            return true;
        }
    }
    else
    {
        m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
            ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
            : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
        return true;
    }
}


// Clipboard XML ID Registry


XmlIdRegistryClipboard::XmlIdRegistryClipboard()
    :   m_pImpl( new XmlIdRegistry_Impl )
{
}

bool
XmlIdRegistryClipboard::LookupXmlId(
    const Metadatable& i_rObject,
    OUString & o_rStream, OUString & o_rIdref) const
{
    const MetadatableClipboard * pLink;
    return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref, pLink);
}

Metadatable*
XmlIdRegistryClipboard::LookupElement(
    const OUString & i_rStreamName,
    const OUString & i_rIdref) const
{
    return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
}

bool
XmlIdRegistryClipboard::TryRegisterMetadatable(Metadatable & i_rObject,
    OUString const& i_rStreamName, OUString const& i_rIdref)
{
    SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref <<")");

    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
        "TryRegisterMetadatable called for MetadatableUndo?");
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
        "TryRegisterMetadatable called for MetadatableClipboard?");

    if (!isValidXmlId(i_rStreamName, i_rIdref))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
    }
    if (i_rObject.IsInContent()
        ?   !isContentFile(i_rStreamName)
        :   !isStylesFile(i_rStreamName))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId: wrong stream"_ustr, nullptr, 0);
    }

    OUString old_path;
    OUString old_idref;
    const MetadatableClipboard * pLink;
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref, pLink);
    if (old_path  == i_rStreamName && old_idref == i_rIdref)
    {
        return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
    }
    ClipboardXmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
    if (!old_idref.isEmpty())
    {
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
    }
    if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
    {
        rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
        m_pImpl->m_XmlIdReverseMap[&i_rObject] =
            RMapEntry(i_rStreamName, i_rIdref);
        return true;
    }
    else
    {
        return false;
    }
}

void
XmlIdRegistryClipboard::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
{
    SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);

    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
        "RegisterMetadatableAndCreateID called for MetadatableUndo?");
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
        "RegisterMetadatableAndCreateID called for MetadatableClipboard?");

    bool isInContent( i_rObject.IsInContent() );
    OUString stream(
        isInContent ? s_content : s_styles );

    OUString old_path;
    OUString old_idref;
    LookupXmlId(i_rObject, old_path, old_idref);
    if (!old_idref.isEmpty() &&
        (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject))
    {
        return;
    }

    // create id
    const OUString id( create_id(m_pImpl->m_XmlIdMap) );
    OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
        "created id is in use");
    m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
        ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
        : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
    // N.B.: if i_rObject had a latent XmlId, then we implicitly delete the
    // MetadatableClipboard and thus the latent XmlId here
    m_pImpl->m_XmlIdReverseMap[&i_rObject] = RMapEntry(stream, id);
}

void XmlIdRegistryClipboard::UnregisterMetadatable(const Metadatable& i_rObject)
{
    SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);

    OUString path;
    OUString idref;
    const MetadatableClipboard * pLink;
    if (!m_pImpl->LookupXmlId(i_rObject, path, idref, pLink))
    {
        OSL_FAIL("unregister: no xml id?");
        return;
    }
    const ClipboardXmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
    if (iter != m_pImpl->m_XmlIdMap.end())
    {
        rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
    }
}


void XmlIdRegistryClipboard::RemoveXmlIdForElement(const Metadatable& i_rObject)
{
    SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);

    ClipboardXmlIdReverseMap_t::iterator iter(
        m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
    if (iter != m_pImpl->m_XmlIdReverseMap.end())
    {
        OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
            "null id in m_XmlIdReverseMap");
        m_pImpl->m_XmlIdReverseMap.erase(iter);
    }
}


std::shared_ptr<MetadatableClipboard>
XmlIdRegistryClipboard::CreateClipboard(const bool i_isInContent)
{
    SAL_INFO("sfx", "CreateClipboard:");

    return std::make_shared<MetadatableClipboard>(
        i_isInContent );
}

MetadatableClipboard &
XmlIdRegistryClipboard::RegisterCopyClipboard(Metadatable & i_rCopy,
    beans::StringPair const & i_rReference,
    const bool i_isLatent)
{
    SAL_INFO("sfx", "RegisterCopyClipboard: " << &i_rCopy
              << " -> (" << i_rReference.First << "#" << i_rReference.Second << ") (" << i_isLatent << ")");

    // N.B.: when copying to the clipboard, the selection is always inserted
    //       into the body, even if the source is a header/footer!
    //       so we do not check whether the stream is right in this function

    if (!isValidXmlId(i_rReference.First, i_rReference.Second))
    {
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
    }

    if (!i_isLatent)
    {
        // this should succeed assuming clipboard has a single source document
        const bool success( m_pImpl->TryInsertMetadatable(i_rCopy,
                i_rReference.First, i_rReference.Second) );
        OSL_ENSURE(success, "RegisterCopyClipboard: TryInsert failed?");
    }
    const std::shared_ptr<MetadatableClipboard> xLink(
        CreateClipboard( isContentFile(i_rReference.First)) );
    m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
        RMapEntry(i_rReference.First, i_rReference.Second, xLink)));
    return *xLink;
}

MetadatableClipboard const*
XmlIdRegistryClipboard::SourceLink(Metadatable const& i_rObject)
{
    OUString path;
    OUString idref;
    const MetadatableClipboard * pLink( nullptr );
    m_pImpl->LookupXmlId(i_rObject, path, idref, pLink);
    return pLink;
}


// Metadatable mixin


Metadatable::~Metadatable()
{
    RemoveMetadataReference();
}

void Metadatable::RemoveMetadataReference()
{
    try
    {
        if (m_pReg)
        {
            m_pReg->UnregisterMetadatable( *this );
            m_pReg->RemoveXmlIdForElement( *this );
            m_pReg = nullptr;
        }
    }
    catch (const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RemoveMetadataReference");
    }
}

// css::rdf::XMetadatable:
beans::StringPair
Metadatable::GetMetadataReference() const
{
    if (m_pReg)
    {
        return m_pReg->GetXmlIdForElement(*this);
    }
    return beans::StringPair();
}

void Metadatable::SetMetadataReference( const css::beans::StringPair & i_rReference)
{
    if (i_rReference.Second.isEmpty())
    {
        RemoveMetadataReference();
    }
    else
    {
        OUString streamName( i_rReference.First );
        if (streamName.isEmpty())
        {
            // handle empty stream name as auto-detect.
            // necessary for importing flat file format.
            streamName = IsInContent() ? s_content : s_styles;
        }
        XmlIdRegistry & rReg( dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
        if (!rReg.TryRegisterMetadatable(*this, streamName, i_rReference.Second))
        {
            throw lang::IllegalArgumentException(
                u"Metadatable::SetMetadataReference: argument is invalid"_ustr, /*this*/nullptr, 0);
        }

        m_pReg = &rReg;
    }
}

void Metadatable::EnsureMetadataReference()
{
    XmlIdRegistry& rReg(
        m_pReg ? *m_pReg : dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
    rReg.RegisterMetadatableAndCreateID( *this );
    m_pReg = &rReg;
}

static const ::sfx2::IXmlIdRegistry& GetRegistryConst(Metadatable const& i_rObject)
{
    return const_cast< Metadatable& >( i_rObject ).GetRegistry();
}

void
Metadatable::RegisterAsCopyOf(Metadatable const & i_rSource,
    const bool i_bCopyPrecedesSource)
{
    OSL_ENSURE(typeid(*this) == typeid(i_rSource)
        || typeid(i_rSource) == typeid(MetadatableUndo)
        || typeid(*this)     == typeid(MetadatableUndo)
        || typeid(i_rSource) == typeid(MetadatableClipboard)
        || typeid(*this)     == typeid(MetadatableClipboard),
        "RegisterAsCopyOf element with different class?");
    OSL_ENSURE(!m_pReg, "RegisterAsCopyOf called on element with XmlId?");

    if (m_pReg)
    {
        RemoveMetadataReference();
    }

    try
    {
        if (i_rSource.m_pReg)
        {
            XmlIdRegistry & rReg(
                dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
            if (i_rSource.m_pReg == &rReg)
            {
                OSL_ENSURE(!IsInClipboard(),
                    "RegisterAsCopy: both in clipboard?");
                if (!IsInClipboard())
                {
                    XmlIdRegistryDocument & rRegDoc(
                        dynamic_cast<XmlIdRegistryDocument&>( rReg ) );
                    rRegDoc.RegisterCopy(i_rSource, *this,
                        i_bCopyPrecedesSource);
                    m_pReg = &rRegDoc;
                }
                return;
            }
            // source is in different document
            XmlIdRegistryDocument  * pRegDoc(
                dynamic_cast<XmlIdRegistryDocument *>(&rReg) );
            XmlIdRegistryClipboard * pRegClp(
                dynamic_cast<XmlIdRegistryClipboard*>(&rReg) );

            if (pRegClp)
            {
                beans::StringPair SourceRef(
                    i_rSource.m_pReg->GetXmlIdForElement(i_rSource) );
                bool isLatent( SourceRef.Second.isEmpty() );
                XmlIdRegistryDocument * pSourceRegDoc(
                    dynamic_cast<XmlIdRegistryDocument*>(i_rSource.m_pReg) );
                OSL_ENSURE(pSourceRegDoc, "RegisterAsCopyOf: 2 clipboards?");
                if (!pSourceRegDoc) return;
                // this is a copy _to_ the clipboard
                if (isLatent)
                {
                    pSourceRegDoc->LookupXmlId(i_rSource,
                        SourceRef.First, SourceRef.Second);
                }
                Metadatable & rLink(
                    pRegClp->RegisterCopyClipboard(*this, SourceRef, isLatent));
                m_pReg = pRegClp;
                // register as copy in the non-clipboard registry
                pSourceRegDoc->RegisterCopy(i_rSource, rLink,
                    false); // i_bCopyPrecedesSource);
                rLink.m_pReg = pSourceRegDoc;
            }
            else if (pRegDoc)
            {
                XmlIdRegistryClipboard * pSourceRegClp(
                    dynamic_cast<XmlIdRegistryClipboard*>(i_rSource.m_pReg) );
                OSL_ENSURE(pSourceRegClp,
                    "RegisterAsCopyOf: 2 non-clipboards?");
                if (!pSourceRegClp) return;
                const MetadatableClipboard * pLink(
                    pSourceRegClp->SourceLink(i_rSource) );
                // may happen if src got its id via UNO call
                if (!pLink) return;
                // only register copy if clipboard content is from this SwDoc!
                if (&GetRegistryConst(*pLink) == pRegDoc)
                {
                    // this is a copy _from_ the clipboard; check if the
                    // element is still in the same stream
                    // N.B.: we check the stream of pLink, not of i_rSource!
                    bool srcInContent( pLink->IsInContent() );
                    bool tgtInContent( IsInContent() );
                    if (srcInContent == tgtInContent)
                    {
                        pRegDoc->RegisterCopy(*pLink, *this,
                            true); // i_bCopyPrecedesSource);
                        m_pReg = pRegDoc;
                    }
                    // otherwise: stream change! do not register!
                }
            }
            else
            {
                OSL_FAIL("neither RegDoc nor RegClp cannot happen");
            }
        }
    }
    catch (const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RegisterAsCopyOf");
    }
}

std::shared_ptr<MetadatableUndo> Metadatable::CreateUndo() const
{
    OSL_ENSURE(!IsInUndo(), "CreateUndo called for object in undo?");
    OSL_ENSURE(!IsInClipboard(), "CreateUndo called for object in clipboard?");
    try
    {
        if (!IsInClipboard() && !IsInUndo() && m_pReg)
        {
            XmlIdRegistryDocument * pRegDoc(
                dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
            assert(pRegDoc);
            std::shared_ptr<MetadatableUndo> xUndo(
                sfx2::XmlIdRegistryDocument::CreateUndo(*this) );
            pRegDoc->RegisterCopy(*this, *xUndo, false);
            xUndo->m_pReg = pRegDoc;
            return xUndo;
        }
    }
    catch (const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::CreateUndo");
    }
    return std::shared_ptr<MetadatableUndo>();
}

std::shared_ptr<MetadatableUndo> Metadatable::CreateUndoForDelete()
{
    std::shared_ptr<MetadatableUndo> xUndo(CreateUndo());
    RemoveMetadataReference();
    return xUndo;
}

void Metadatable::RestoreMetadata(
    std::shared_ptr<MetadatableUndo> const& i_pUndo)
{
    OSL_ENSURE(!IsInUndo(), "RestoreMetadata called for object in undo?");
    OSL_ENSURE(!IsInClipboard(),
        "RestoreMetadata called for object in clipboard?");
    if (IsInClipboard() || IsInUndo()) return;
    RemoveMetadataReference();
    if (i_pUndo)
    {
        RegisterAsCopyOf(*i_pUndo, true);
    }
}

void
Metadatable::JoinMetadatable(Metadatable const & i_rOther,
    const bool i_isMergedEmpty, const bool i_isOtherEmpty)
{
    OSL_ENSURE(!IsInUndo(), "JoinMetadatables called for object in undo?");
    OSL_ENSURE(!IsInClipboard(),
        "JoinMetadatables called for object in clipboard?");
    if (IsInClipboard() || IsInUndo()) return;

    if (i_isOtherEmpty && !i_isMergedEmpty)
    {
        // other is empty, thus loses => nothing to do
        return;
    }
    if (i_isMergedEmpty && !i_isOtherEmpty)
    {
        RemoveMetadataReference();
        RegisterAsCopyOf(i_rOther, true);
        return;
    }

    if (!i_rOther.m_pReg)
    {
        // other doesn't have xmlid, thus loses => nothing to do
        return;
    }
    if (!m_pReg)
    {
        RegisterAsCopyOf(i_rOther, true);
        // assumption: i_rOther will be deleted, so don't unregister it here
        return;
    }
    try
    {
        XmlIdRegistryDocument * pRegDoc(
            dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
        OSL_ENSURE(pRegDoc, "JoinMetadatable: no pRegDoc?");
        if (pRegDoc)
        {
            pRegDoc->JoinMetadatables(*this, i_rOther);
        }
    }
    catch (const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::JoinMetadatable");
    }
}


// XMetadatable mixin

// css::rdf::XNode:
OUString SAL_CALL MetadatableMixin::getStringValue()
{
    return getNamespace() + getLocalName();
}

// css::rdf::XURI:
OUString SAL_CALL MetadatableMixin::getLocalName()
{
    SolarMutexGuard aGuard;
    beans::StringPair mdref( getMetadataReference() );
    if (mdref.Second.isEmpty())
    {
        ensureMetadataReference(); // N.B.: side effect!
        mdref = getMetadataReference();
    }
    return mdref.First + "#" + mdref.Second;
}

OUString SAL_CALL MetadatableMixin::getNamespace()
{
    SolarMutexGuard aGuard;
    const uno::Reference< frame::XModel > xModel( GetModel() );
    const uno::Reference< rdf::XURI > xDMA( xModel, uno::UNO_QUERY_THROW );
    return xDMA->getStringValue();
}

// css::rdf::XMetadatable:
beans::StringPair SAL_CALL
MetadatableMixin::getMetadataReference()
{
    SolarMutexGuard aGuard;

    Metadatable *const pObject( GetCoreObject() );
    if (!pObject)
    {
        throw uno::RuntimeException(
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
            *this);
    }
    return pObject->GetMetadataReference();
}

void SAL_CALL
MetadatableMixin::setMetadataReference(
    const beans::StringPair & i_rReference)
{
    SolarMutexGuard aGuard;

    Metadatable *const pObject( GetCoreObject() );
    if (!pObject)
    {
        throw uno::RuntimeException(
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
            *this);
    }
    return pObject->SetMetadataReference(i_rReference);
}

void SAL_CALL MetadatableMixin::ensureMetadataReference()
{
    SolarMutexGuard aGuard;

    Metadatable *const pObject( GetCoreObject() );
    if (!pObject)
    {
        throw uno::RuntimeException(
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
            *this);
    }
    return pObject->EnsureMetadataReference();
}

} // namespace sfx2

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