1603 lines
50 KiB
C++
1603 lines
50 KiB
C++
/* -*- 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: */
|