/* -*- 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*>(
const_cast<const XmlIdRegistry_Impl*>(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> const 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: */
↑ V522 There might be dereferencing of a potential null pointer 'pRegDoc'.