Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/doc/Metadatable.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
#include <sal/log.hxx>
22
23
#include <osl/diagnose.h>
24
#include <sfx2/Metadatable.hxx>
25
#include <sfx2/XmlIdRegistry.hxx>
26
27
#include <utility>
28
#include <vcl/svapp.hxx>
29
#include <com/sun/star/frame/XModel.hpp>
30
#include <com/sun/star/lang/IllegalArgumentException.hpp>
31
#include <comphelper/random.hxx>
32
#include <comphelper/diagnose_ex.hxx>
33
34
#include <algorithm>
35
#include <memory>
36
#include <string_view>
37
#include <unordered_map>
38
#if OSL_DEBUG_LEVEL > 0
39
#include <typeinfo>
40
#endif
41
42
43
/** XML ID handling.
44
45
    There is an abstract base class <type>XmlIdRegistry</type>, with
46
    2 subclasses <type>XmlIdRegistryDocument</type> for "normal" documents,
47
    and <type>XmlIdRegistryClipboard</type> for clipboard documents.
48
    These classes are responsible for managing XML IDs for all elements
49
    of the model. Only the implementation of the <type>Metadatable</type>
50
    base class needs to know the registries, so they are not in the header.
51
52
    The handling of XML IDs differs between clipboard and non-clipboard
53
    documents in several aspects. Most importantly, non-clipboard documents
54
    can have several elements associated with one XML ID.
55
    This is necessary because of the weird undo implementation:
56
    deleting a text node moves the deleted node to the undo array, but
57
    executing undo will then create a <em>copy</em> of that node in the
58
    document array. These 2 nodes must have the same XML ID, because
59
    we cannot know whether the user will do a redo next, or something else.
60
61
    Because we need to have a mechanism for several objects per XML ID anyway,
62
    we use that also to enable some usability features:
63
    The document registry has a list of Metadatables per XML ID.
64
    This list is sorted by priority, i.e., the first element has highest
65
    priority. When inserting copies, care must be taken that they are inserted
66
    at the right position: either before or after the source.
67
    This is done by <method>Metadatable::RegisterAsCopyOf</method>.
68
    When a text node is split, then both resulting text nodes are inserted
69
    into the list. If the user then deletes one text node, the other one
70
    will have the XML ID.
71
    Also, when a Metadatable is copied to the clipboard and then pasted,
72
    the copy is inserted into the list. If the user then deletes the source,
73
    the XML ID is not lost.
74
    The goal is that it should be hard to lose an XML ID by accident, which
75
    is especially important as long as we do not have an UI that displays them.
76
77
    There are two subclasses of <type>Metadatable</type>:
78
    <ul><li><type>MetadatableClipboard</type>: for copies in the clipboard</li>
79
        <li><type>MetadatableUndo</type>: for undo, because a Metadatable
80
        may be destroyed on delete and a new one created on undo.</li></ul>
81
    These serve only to track the position in an XML ID list in a document
82
    registry, so that future actions can insert objects at the right position.
83
    Unfortunately, inserting dummy objects seems to be necessary:
84
    <ul><li>it is not sufficient to just remember the saved id, because then
85
            the relative priorities might change when executing the undo</li>
86
        <li>it is not sufficient to record the position as an integer, because
87
            if we delete a text node and then undo, the node will be copied(!),
88
            and we will have one more node in the list.<li>
89
        <li>it is not sufficient to record the pointer of the previous/next
90
            Metadatable, because if we delete a text node, undo, and then
91
            do something to clear the redo array, the original text node is
92
            destroyed, and is replaced by the copy created by undo</li></ul>
93
94
    If content from a non-clipboard document is copied into a clipboard
95
    document, a dummy <type>MetadatableClipboard</type> is inserted into the
96
    non-clipboard document registry in order to track the position of the
97
    source element.  When the clipboard content is pasted back into the source
98
    document, this dummy object is used to associate the pasted element with
99
    that same XML ID.
100
101
    If a <type>Metadatable</type> is deleted or merged,
102
    <method>Metadatable::CreateUndo</method> is called, and returns a
103
    <type>MetadatableUndo<type> instance, which can be used to undo the action
104
    by passing it to <method>Metadatable::RestoreMetadata</method>.
105
106
 */
107
108
109
using namespace ::com::sun::star;
110
111
using ::sfx2::isValidXmlId;
112
113
114
namespace sfx2 {
115
116
constexpr OUString s_content = u"content.xml"_ustr;
117
constexpr OUString s_styles  = u"styles.xml"_ustr;
118
119
static bool isContentFile(std::u16string_view i_rPath)
120
12.8k
{
121
12.8k
    return i_rPath == s_content;
122
12.8k
}
123
124
static bool isStylesFile (std::u16string_view i_rPath)
125
0
{
126
0
    return i_rPath == s_styles;
127
0
}
128
129
130
// XML ID handling ---------------------------------------------------
131
132
/** handles registration of XMetadatable.
133
134
    This class is responsible for guaranteeing that XMetadatable objects
135
    always have XML IDs that are unique within a stream.
136
137
    This is an abstract base class; see subclasses XmlIdRegistryDocument and
138
    XmlIdRegistryClipboard.
139
140
    @see SwDoc::GetXmlIdRegistry
141
    @see SwDocShell::GetXmlIdRegistry
142
 */
143
class XmlIdRegistry : public sfx2::IXmlIdRegistry
144
{
145
146
public:
147
    XmlIdRegistry();
148
149
    /** get the ODF element with the given metadata reference. */
150
    virtual css::uno::Reference< css::rdf::XMetadatable >
151
        GetElementByMetadataReference(
152
            const css::beans::StringPair & i_rReference) const
153
        override;
154
155
    /** register an ODF element at a newly generated, unique metadata reference.
156
157
        <p>
158
        Find a fresh XML ID, and register it for the element.
159
        The generated ID does not occur in any stream of the document.
160
        </p>
161
     */
162
    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) = 0;
163
164
    /** try to register an ODF element at a given XML ID, or update its
165
        registration to a different XML ID.
166
167
        <p>
168
        If the given new metadata reference is not already occupied in the
169
        document, unregister the element at its old metadata reference if
170
        it has one, and register the new metadata reference for the element.
171
        Note that this method only ensures that XML IDs are unique per stream,
172
        so using the same XML ID in both content.xml and styles.xml is allowed.
173
        </p>
174
175
        @returns
176
            true iff the element has successfully been registered
177
     */
178
    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
179
        OUString const& i_rStreamName, OUString const& i_rIdref)
180
        = 0;
181
182
    /** unregister an ODF element.
183
184
        <p>
185
        Unregister the element at its metadata reference.
186
        Does not remove the metadata reference from the element.
187
        </p>
188
189
        @see RemoveXmlIdForElement
190
     */
191
    virtual void UnregisterMetadatable(Metadatable const&) = 0;
192
193
    /** get the metadata reference for the given element. */
194
    css::beans::StringPair
195
        GetXmlIdForElement(Metadatable const&) const;
196
197
    /** remove the metadata reference for the given element. */
198
    virtual void RemoveXmlIdForElement(Metadatable const&) = 0;
199
200
protected:
201
202
    virtual bool LookupXmlId(const Metadatable& i_xObject,
203
        OUString & o_rStream, OUString & o_rIdref) const = 0;
204
205
    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
206
        const OUString & i_rIdref) const = 0;
207
};
208
209
// XmlIdRegistryDocument ---------------------------------------------
210
211
namespace {
212
213
/** non-clipboard documents */
214
class XmlIdRegistryDocument : public XmlIdRegistry
215
{
216
217
public:
218
    XmlIdRegistryDocument();
219
220
    virtual ~XmlIdRegistryDocument() override;
221
222
    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;
223
224
    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
225
        OUString const& i_rStreamName, OUString const& i_rIdref) override;
226
227
    virtual void UnregisterMetadatable(Metadatable const&) override;
228
229
    virtual void RemoveXmlIdForElement(Metadatable const&) override;
230
231
    /** register i_rCopy as a copy of i_rSource,
232
        with precedence iff i_bCopyPrecedesSource is true */
233
    void RegisterCopy(Metadatable const& i_rSource, Metadatable & i_rCopy,
234
        const bool i_bCopyPrecedesSource);
235
236
    /** create a Undo Metadatable for i_rObject. */
237
    static std::shared_ptr<MetadatableUndo> CreateUndo(
238
        Metadatable const& i_rObject);
239
240
    /** merge i_rMerged and i_rOther into i_rMerged. */
241
    void JoinMetadatables(Metadatable & i_rMerged, Metadatable const& i_rOther);
242
243
    // unfortunately public, Metadatable::RegisterAsCopyOf needs this
244
    virtual bool LookupXmlId(const Metadatable& i_xObject,
245
        OUString & o_rStream, OUString & o_rIdref) const override;
246
247
private:
248
249
    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
250
        const OUString & i_rIdref) const override;
251
252
    struct XmlIdRegistry_Impl;
253
    ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
254
};
255
256
}
257
258
// MetadatableUndo ---------------------------------------------------
259
260
/** the horrible Undo Metadatable: is inserted into lists to track position */
261
class MetadatableUndo : public Metadatable
262
{
263
    /// as determined by the stream of the source in original document
264
    const bool m_isInContent;
265
public:
266
    explicit MetadatableUndo(const bool i_isInContent)
267
0
        : m_isInContent(i_isInContent) { }
268
    virtual ::sfx2::XmlIdRegistry& GetRegistry() override
269
0
    {
270
        // N.B. for Undo, m_pReg is initialized by registering this as copy in
271
        // CreateUndo; it is never cleared
272
0
        OSL_ENSURE(m_pReg, "no m_pReg in MetadatableUndo ?");
273
0
        return *m_pReg;
274
0
    }
275
0
    virtual bool IsInClipboard() const override { return false; }
276
0
    virtual bool IsInUndo() const override { return true; }
277
0
    virtual bool IsInContent() const override { return m_isInContent; }
278
    virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
279
0
    { OSL_FAIL("MetadatableUndo::MakeUnoObject"); throw; }
280
};
281
282
// MetadatableClipboard ----------------------------------------------
283
284
/** the horrible Clipboard Metadatable: inserted into lists to track position */
285
class MetadatableClipboard : public Metadatable
286
{
287
    /// as determined by the stream of the source in original document
288
    const bool m_isInContent;
289
public:
290
    explicit MetadatableClipboard(const bool i_isInContent)
291
0
        : m_isInContent(i_isInContent) { }
292
    virtual ::sfx2::XmlIdRegistry& GetRegistry() override
293
0
    {
294
        // N.B. for Clipboard, m_pReg is initialized by registering this as copy in
295
        // RegisterAsCopyOf; it is only cleared by OriginNoLongerInBusinessAnymore
296
0
        assert(m_pReg && "no m_pReg in MetadatableClipboard ?");
297
0
        return *m_pReg;
298
0
    }
299
0
    virtual bool IsInClipboard() const override { return true; }
300
0
    virtual bool IsInUndo() const override { return false; }
301
0
    virtual bool IsInContent() const override { return m_isInContent; }
302
    virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
303
0
    { OSL_FAIL("MetadatableClipboard::MakeUnoObject"); throw; }
304
0
    void OriginNoLongerInBusinessAnymore() { m_pReg = nullptr; }
305
};
306
307
// XmlIdRegistryClipboard --------------------------------------------
308
309
namespace {
310
311
class XmlIdRegistryClipboard : public XmlIdRegistry
312
{
313
314
public:
315
    XmlIdRegistryClipboard();
316
317
    virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;
318
319
    virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
320
        OUString const& i_rStreamName, OUString const& i_rIdref) override;
321
322
    virtual void UnregisterMetadatable(Metadatable const&) override;
323
324
    virtual void RemoveXmlIdForElement(Metadatable const&) override;
325
326
    /** register i_rCopy as a copy of i_rSource */
327
    MetadatableClipboard & RegisterCopyClipboard(Metadatable & i_rCopy,
328
        beans::StringPair const & i_rReference,
329
        const bool i_isLatent);
330
331
    /** get the Metadatable that links i_rObject to its origin registry */
332
    MetadatableClipboard const* SourceLink(Metadatable const& i_rObject);
333
334
private:
335
    virtual bool LookupXmlId(const Metadatable& i_xObject,
336
        OUString & o_rStream, OUString & o_rIdref) const override;
337
338
    virtual Metadatable* LookupElement(const OUString & i_rStreamName,
339
        const OUString & i_rIdref) const override;
340
341
    /** create a Clipboard Metadatable for i_rObject. */
342
    static std::shared_ptr<MetadatableClipboard> CreateClipboard(
343
        const bool i_isInContent);
344
345
    struct XmlIdRegistry_Impl;
346
    ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
347
};
348
349
}
350
351
// XmlIdRegistry
352
353
::sfx2::IXmlIdRegistry * createXmlIdRegistry(const bool i_DocIsClipboard)
354
604
{
355
604
    return i_DocIsClipboard
356
604
        ? static_cast<XmlIdRegistry*>( new XmlIdRegistryClipboard )
357
604
        : static_cast<XmlIdRegistry*>( new XmlIdRegistryDocument );
358
604
}
359
360
XmlIdRegistry::XmlIdRegistry()
361
604
{
362
604
}
363
364
css::uno::Reference< css::rdf::XMetadatable >
365
XmlIdRegistry::GetElementByMetadataReference(
366
    const beans::StringPair & i_rReference) const
367
0
{
368
0
    Metadatable* pObject( LookupElement(i_rReference.First,
369
0
        i_rReference.Second) );
370
0
    return pObject ? pObject->MakeUnoObject() : nullptr;
371
0
}
372
373
beans::StringPair
374
XmlIdRegistry::GetXmlIdForElement(const Metadatable& i_rObject) const
375
0
{
376
0
    OUString path;
377
0
    OUString idref;
378
0
    if (LookupXmlId(i_rObject, path, idref))
379
0
    {
380
0
        if (LookupElement(path, idref) == &i_rObject)
381
0
        {
382
0
            return beans::StringPair(path, idref);
383
0
        }
384
0
    }
385
0
    return beans::StringPair();
386
0
}
387
388
389
/// generate unique xml:id
390
template< typename T >
391
static OUString create_id(const
392
    std::unordered_map< OUString, T > & i_rXmlIdMap)
393
0
{
394
0
    static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
395
0
    static const char prefix[] = "id";  // prefix for generated xml:id
396
0
    typename std::unordered_map< OUString, T >
397
0
        ::const_iterator iter;
398
0
    OUString id;
399
400
0
    if (bHack)
401
0
    {
402
0
        static sal_Int64 nIdCounter = SAL_CONST_INT64(4000000000);
403
0
        do
404
0
        {
405
0
            id = prefix + OUString::number(nIdCounter++);
406
0
            iter = i_rXmlIdMap.find(id);
407
0
        }
408
0
        while (iter != i_rXmlIdMap.end());
409
0
    }
410
0
    else
411
0
    {
412
0
        do
413
0
        {
414
0
            unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
415
0
                                    std::numeric_limits<unsigned int>::max()));
416
0
            id = prefix + OUString::number(n);
417
0
            iter = i_rXmlIdMap.find(id);
418
0
        }
419
0
        while (iter != i_rXmlIdMap.end());
420
0
    }
421
0
    return id;
422
0
}
Unexecuted instantiation: Metadatable.cxx:rtl::OUString sfx2::create_id<std::__1::pair<std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> >, std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> > > >(std::__1::unordered_map<rtl::OUString, std::__1::pair<std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> >, std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> > >, std::__1::hash<rtl::OUString>, std::__1::equal_to<rtl::OUString>, std::__1::allocator<std::__1::pair<rtl::OUString const, std::__1::pair<std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> >, std::__1::vector<sfx2::Metadatable*, std::__1::allocator<sfx2::Metadatable*> > > > > > const&)
Unexecuted instantiation: Metadatable.cxx:rtl::OUString sfx2::create_id<std::__1::pair<sfx2::Metadatable*, sfx2::Metadatable*> >(std::__1::unordered_map<rtl::OUString, std::__1::pair<sfx2::Metadatable*, sfx2::Metadatable*>, std::__1::hash<rtl::OUString>, std::__1::equal_to<rtl::OUString>, std::__1::allocator<std::__1::pair<rtl::OUString const, std::__1::pair<sfx2::Metadatable*, sfx2::Metadatable*> > > > const&)
423
424
425
// Document XML ID Registry (_Impl)
426
427
/// element list
428
typedef ::std::vector< Metadatable* > XmlIdVector_t;
429
430
/// Idref -> (content.xml element list, styles.xml element list)
431
typedef std::unordered_map< OUString,
432
    ::std::pair< XmlIdVector_t, XmlIdVector_t > > XmlIdMap_t;
433
434
namespace {
435
436
/// pointer hash template
437
template<typename T> struct PtrHash
438
{
439
    size_t operator() (T const * i_pT) const
440
9.92k
    {
441
9.92k
        return reinterpret_cast<size_t>(i_pT);
442
9.92k
    }
443
};
444
445
}
446
447
/// element -> (stream name, idref)
448
typedef std::unordered_map< const Metadatable*,
449
    ::std::pair< OUString, OUString>, PtrHash<Metadatable> >
450
    XmlIdReverseMap_t;
451
452
struct XmlIdRegistryDocument::XmlIdRegistry_Impl
453
{
454
604
    XmlIdRegistry_Impl() {}
455
456
    bool TryInsertMetadatable(Metadatable& i_xObject,
457
        std::u16string_view i_rStream, const OUString & i_rIdref);
458
459
    bool LookupXmlId(const Metadatable& i_xObject,
460
        OUString & o_rStream, OUString & o_rIdref) const;
461
462
    Metadatable* LookupElement(std::u16string_view i_rStreamName,
463
        const OUString & i_rIdref) const;
464
465
    const XmlIdVector_t * LookupElementVector(
466
        std::u16string_view i_rStreamName,
467
        const OUString & i_rIdref) const;
468
469
          XmlIdVector_t * LookupElementVector(
470
        std::u16string_view i_rStreamName,
471
        const OUString & i_rIdref)
472
4.29k
    {
473
4.29k
        return const_cast<XmlIdVector_t*>(
474
4.29k
            std::as_const(*this).LookupElementVector(i_rStreamName, i_rIdref));
475
4.29k
    }
476
477
    XmlIdMap_t m_XmlIdMap;
478
    XmlIdReverseMap_t m_XmlIdReverseMap;
479
};
480
481
482
static void
483
rmIter(XmlIdMap_t & i_rXmlIdMap, XmlIdMap_t::iterator const& i_rIter,
484
    std::u16string_view i_rStream, Metadatable const& i_rObject)
485
4.10k
{
486
4.10k
    if (i_rIter != i_rXmlIdMap.end())
487
2.05k
    {
488
2.05k
        XmlIdVector_t & rVector( isContentFile(i_rStream)
489
2.05k
            ? i_rIter->second.first : i_rIter->second.second );
490
2.05k
        std::erase(rVector, &const_cast<Metadatable&>(i_rObject));
491
2.05k
        if (i_rIter->second.first.empty() && i_rIter->second.second.empty())
492
2.05k
        {
493
2.05k
            i_rXmlIdMap.erase(i_rIter);
494
2.05k
        }
495
2.05k
    }
496
4.10k
}
497
498
499
const XmlIdVector_t *
500
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElementVector(
501
    std::u16string_view i_rStreamName,
502
    const OUString & i_rIdref) const
503
4.29k
{
504
4.29k
    const XmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
505
4.29k
    if (iter != m_XmlIdMap.end())
506
2.24k
    {
507
2.24k
        OSL_ENSURE(!iter->second.first.empty() || !iter->second.second.empty(),
508
2.24k
            "null entry in m_XmlIdMap");
509
2.24k
        return (isContentFile(i_rStreamName))
510
2.24k
            ?  &iter->second.first
511
2.24k
            :  &iter->second.second;
512
2.24k
    }
513
2.05k
    else
514
2.05k
    {
515
2.05k
        return nullptr;
516
2.05k
    }
517
4.29k
}
518
519
Metadatable*
520
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElement(
521
    std::u16string_view i_rStreamName,
522
    const OUString & i_rIdref) const
523
0
{
524
0
    if (!isValidXmlId(i_rStreamName, i_rIdref))
525
0
    {
526
0
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
527
0
    }
528
529
0
    const XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
530
0
    if (pList)
531
0
    {
532
0
        const XmlIdVector_t::const_iterator iter(
533
0
            ::std::find_if(pList->begin(), pList->end(),
534
0
                [](Metadatable* item)->bool {
535
0
                    return !(item->IsInUndo() || item->IsInClipboard());
536
0
                    } ) ) ;
537
0
        if (iter != pList->end())
538
0
        {
539
0
            return *iter;
540
0
        }
541
0
    }
542
0
    return nullptr;
543
0
}
544
545
bool
546
XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupXmlId(
547
    const Metadatable& i_rObject,
548
    OUString & o_rStream, OUString & o_rIdref) const
549
6.34k
{
550
6.34k
    const XmlIdReverseMap_t::const_iterator iter(
551
6.34k
        m_XmlIdReverseMap.find(&i_rObject) );
552
6.34k
    if (iter != m_XmlIdReverseMap.end())
553
2.05k
    {
554
2.05k
        OSL_ENSURE(!iter->second.first.isEmpty(),
555
2.05k
            "null stream in m_XmlIdReverseMap");
556
2.05k
        OSL_ENSURE(!iter->second.second.isEmpty(),
557
2.05k
            "null id in m_XmlIdReverseMap");
558
2.05k
        o_rStream = iter->second.first;
559
2.05k
        o_rIdref  = iter->second.second;
560
2.05k
        return true;
561
2.05k
    }
562
4.29k
    else
563
4.29k
    {
564
4.29k
        return false;
565
4.29k
    }
566
6.34k
}
567
568
bool
569
XmlIdRegistryDocument::XmlIdRegistry_Impl::TryInsertMetadatable(
570
    Metadatable & i_rObject,
571
    std::u16string_view i_rStreamName, const OUString & i_rIdref)
572
4.29k
{
573
4.29k
    const bool bContent( isContentFile(i_rStreamName) );
574
4.29k
    OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
575
4.29k
        "invalid stream");
576
577
4.29k
    XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
578
4.29k
    if (pList)
579
2.24k
    {
580
2.24k
        if (pList->empty())
581
0
        {
582
0
            pList->push_back( &i_rObject );
583
0
            return true;
584
0
        }
585
2.24k
        else
586
2.24k
        {
587
            // this is only called from TryRegister now, so check
588
            // if all elements in the list are deleted (in undo) or
589
            // placeholders, then "steal" the id from them
590
2.24k
            if ( std::none_of(pList->begin(), pList->end(),
591
2.24k
                [](Metadatable* item)->bool {
592
2.24k
                    return !(item->IsInUndo() || item->IsInClipboard());
593
2.24k
                    } ) )
594
0
            {
595
0
                pList->insert(pList->begin(), &i_rObject );
596
0
                return true;
597
0
            }
598
2.24k
            else
599
2.24k
            {
600
2.24k
                return false;
601
2.24k
            }
602
2.24k
        }
603
2.24k
    }
604
2.05k
    else
605
2.05k
    {
606
2.05k
        m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
607
2.05k
            ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
608
2.05k
            : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
609
2.05k
        return true;
610
2.05k
    }
611
4.29k
}
612
613
614
// Document XML ID Registry
615
616
617
XmlIdRegistryDocument::XmlIdRegistryDocument()
618
604
    :   m_pImpl( new XmlIdRegistry_Impl )
619
604
{
620
604
}
621
622
static void
623
removeLink(Metadatable* i_pObject)
624
0
{
625
0
    OSL_ENSURE(i_pObject, "null in list ???");
626
0
    if (!i_pObject) return;
627
0
    if (i_pObject->IsInClipboard())
628
0
    {
629
0
        MetadatableClipboard* pLink(
630
0
            dynamic_cast<MetadatableClipboard*>( i_pObject ) );
631
0
        OSL_ENSURE(pLink, "IsInClipboard, but no MetadatableClipboard ?");
632
0
        if (pLink)
633
0
        {
634
0
            pLink->OriginNoLongerInBusinessAnymore();
635
0
        }
636
0
    }
637
0
}
638
639
XmlIdRegistryDocument::~XmlIdRegistryDocument()
640
604
{
641
    // notify all list elements that are actually in the clipboard
642
604
    for (const auto& aXmlId : m_pImpl->m_XmlIdMap) {
643
0
        for (auto aLink : aXmlId.second.first)
644
0
            removeLink(aLink);
645
0
        for (auto aLink : aXmlId.second.second)
646
0
            removeLink(aLink);
647
0
    }
648
604
}
649
650
bool
651
XmlIdRegistryDocument::LookupXmlId(
652
    const Metadatable& i_rObject,
653
    OUString & o_rStream, OUString & o_rIdref) const
654
0
{
655
0
    return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref);
656
0
}
657
658
Metadatable*
659
XmlIdRegistryDocument::LookupElement(
660
    const OUString & i_rStreamName,
661
    const OUString & i_rIdref) const
662
0
{
663
0
    return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
664
0
}
665
666
bool
667
XmlIdRegistryDocument::TryRegisterMetadatable(Metadatable & i_rObject,
668
    OUString const& i_rStreamName, OUString const& i_rIdref)
669
6.47k
{
670
6.47k
    SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref << ")");
671
672
6.47k
    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
673
6.47k
        "TryRegisterMetadatable called for MetadatableUndo?");
674
6.47k
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
675
6.47k
        "TryRegisterMetadatable called for MetadatableClipboard?");
676
677
6.47k
    if (!isValidXmlId(i_rStreamName, i_rIdref))
678
2.18k
    {
679
2.18k
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
680
2.18k
    }
681
4.29k
    if (i_rObject.IsInContent()
682
4.29k
        ?   !isContentFile(i_rStreamName)
683
4.29k
        :   !isStylesFile(i_rStreamName))
684
0
    {
685
0
        throw lang::IllegalArgumentException(u"illegal XmlId: wrong stream"_ustr, nullptr, 0);
686
0
    }
687
688
4.29k
    OUString old_path;
689
4.29k
    OUString old_idref;
690
4.29k
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);
691
4.29k
    if (old_path  == i_rStreamName && old_idref == i_rIdref)
692
0
    {
693
0
        return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
694
0
    }
695
4.29k
    XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
696
4.29k
    if (!old_idref.isEmpty())
697
0
    {
698
0
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
699
0
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
700
0
    }
701
4.29k
    if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
702
2.05k
    {
703
2.05k
        rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
704
2.05k
        m_pImpl->m_XmlIdReverseMap[&i_rObject] =
705
2.05k
            ::std::make_pair(i_rStreamName, i_rIdref);
706
2.05k
        return true;
707
2.05k
    }
708
2.24k
    else
709
2.24k
    {
710
2.24k
        return false;
711
2.24k
    }
712
4.29k
}
713
714
void
715
XmlIdRegistryDocument::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
716
0
{
717
0
    SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);
718
719
0
    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
720
0
        "RegisterMetadatableAndCreateID called for MetadatableUndo?");
721
0
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
722
0
        "RegisterMetadatableAndCreateID called for MetadatableClipboard?");
723
724
0
    const bool isInContent( i_rObject.IsInContent() );
725
0
    const OUString stream(
726
0
        isInContent ? s_content : s_styles );
727
    // check if we have a latent xmlid, and if yes, remove it
728
0
    OUString old_path;
729
0
    OUString old_idref;
730
0
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);
731
732
0
    XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
733
0
    if (!old_idref.isEmpty())
734
0
    {
735
0
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
736
0
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
737
0
        if (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject)
738
0
        {
739
0
            return;
740
0
        }
741
0
        else
742
0
        {
743
            // remove latent xmlid
744
0
            rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
745
0
        }
746
0
    }
747
748
    // create id
749
0
    const OUString id( create_id(m_pImpl->m_XmlIdMap) );
750
0
    OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
751
0
        "created id is in use");
752
0
    m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
753
0
        ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
754
0
        : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
755
0
    m_pImpl->m_XmlIdReverseMap[&i_rObject] = ::std::make_pair(stream, id);
756
0
}
757
758
void XmlIdRegistryDocument::UnregisterMetadatable(const Metadatable& i_rObject)
759
2.05k
{
760
2.05k
    SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);
761
762
2.05k
    OUString path;
763
2.05k
    OUString idref;
764
2.05k
    if (!m_pImpl->LookupXmlId(i_rObject, path, idref))
765
0
    {
766
0
        OSL_FAIL("unregister: no xml id?");
767
0
        return;
768
0
    }
769
2.05k
    const XmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
770
2.05k
    if (iter != m_pImpl->m_XmlIdMap.end())
771
2.05k
    {
772
2.05k
        rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
773
2.05k
    }
774
2.05k
}
775
776
void XmlIdRegistryDocument::RemoveXmlIdForElement(const Metadatable& i_rObject)
777
2.05k
{
778
2.05k
    SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);
779
780
2.05k
    const XmlIdReverseMap_t::iterator iter(
781
2.05k
        m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
782
2.05k
    if (iter != m_pImpl->m_XmlIdReverseMap.end())
783
2.05k
    {
784
2.05k
        OSL_ENSURE(!iter->second.second.isEmpty(),
785
2.05k
            "null id in m_XmlIdReverseMap");
786
2.05k
        m_pImpl->m_XmlIdReverseMap.erase(iter);
787
2.05k
    }
788
2.05k
}
789
790
791
void XmlIdRegistryDocument::RegisterCopy(Metadatable const& i_rSource,
792
    Metadatable & i_rCopy, const bool i_bCopyPrecedesSource)
793
0
{
794
0
    SAL_INFO("sfx", "RegisterCopy: " << &i_rSource << " -> " << &i_rCopy << " (" << i_bCopyPrecedesSource << ")");
795
796
    // potential sources: clipboard, undo array, splitNode
797
    // assumption: stream change can only happen via clipboard, and is handled
798
    // by Metadatable::RegisterAsCopyOf
799
0
    OSL_ENSURE(i_rSource.IsInUndo() || i_rCopy.IsInUndo() ||
800
0
        (i_rSource.IsInContent() == i_rCopy.IsInContent()),
801
0
        "RegisterCopy: not in same stream?");
802
803
0
    OUString path;
804
0
    OUString idref;
805
0
    if (!m_pImpl->LookupXmlId( i_rSource, path, idref ))
806
0
    {
807
0
        OSL_FAIL("no xml id?");
808
0
        return;
809
0
    }
810
0
    XmlIdVector_t * pList ( m_pImpl->LookupElementVector(path, idref) );
811
0
    OSL_ENSURE( ::std::find( pList->begin(), pList->end(), &i_rCopy )
812
0
        == pList->end(), "copy already registered???");
813
0
    XmlIdVector_t::iterator srcpos(
814
0
        ::std::find( pList->begin(), pList->end(), &i_rSource ) );
815
0
    OSL_ENSURE(srcpos != pList->end(), "source not in list???");
816
0
    if (srcpos == pList->end())
817
0
    {
818
0
        return;
819
0
    }
820
0
    if (i_bCopyPrecedesSource)
821
0
    {
822
0
        pList->insert( srcpos, &i_rCopy );
823
0
    }
824
0
    else
825
0
    {
826
        // for undo push_back does not work! must insert right after source
827
0
        pList->insert( ++srcpos, &i_rCopy );
828
0
    }
829
0
    m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
830
0
        ::std::make_pair(path, idref)));
831
0
}
832
833
std::shared_ptr<MetadatableUndo>
834
XmlIdRegistryDocument::CreateUndo(Metadatable const& i_rObject)
835
0
{
836
0
    SAL_INFO("sfx", "CreateUndo: " << &i_rObject);
837
838
0
    return std::make_shared<MetadatableUndo>(
839
0
                i_rObject.IsInContent() );
840
0
}
841
842
/*
843
i_rMerged is both a source and the target node of the merge
844
i_rOther is the other source, and will be deleted after the merge
845
846
dimensions: none|latent|actual empty|nonempty
847
i_rMerged(1)    i_rOther(2)        result
848
     *|empty         *|empty    => 1|2 (arbitrary)
849
     *|empty         *|nonempty => 2
850
     *|nonempty      *|empty    => 1
851
  none|nonempty   none|nonempty => none
852
  none|nonempty latent|nonempty => 2
853
latent|nonempty   none|nonempty => 1
854
latent|nonempty latent|nonempty => 1|2
855
     *|nonempty actual|nonempty => 2
856
actual|nonempty      *|nonempty => 1
857
actual|nonempty actual|nonempty => 1|2
858
*/
859
void
860
XmlIdRegistryDocument::JoinMetadatables(
861
    Metadatable & i_rMerged, Metadatable const & i_rOther)
862
0
{
863
0
    SAL_INFO("sfx", "JoinMetadatables: " << &i_rMerged << " <- " << &i_rOther);
864
865
0
    bool mergedOwnsRef;
866
0
    OUString path;
867
0
    OUString idref;
868
0
    if (m_pImpl->LookupXmlId(i_rMerged, path, idref))
869
0
    {
870
0
        mergedOwnsRef = (m_pImpl->LookupElement(path, idref) == &i_rMerged);
871
0
    }
872
0
    else
873
0
    {
874
0
        OSL_FAIL("JoinMetadatables: no xmlid?");
875
0
        return;
876
0
    }
877
0
    if (!mergedOwnsRef)
878
0
    {
879
0
        i_rMerged.RemoveMetadataReference();
880
0
        i_rMerged.RegisterAsCopyOf(i_rOther, true);
881
0
        return;
882
0
    }
883
    // other cases: merged has actual ref and is nonempty,
884
    // other has latent/actual ref and is nonempty: other loses => nothing to do
885
0
}
886
887
888
// Clipboard XML ID Registry (_Impl)
889
890
namespace {
891
892
struct RMapEntry
893
{
894
0
    RMapEntry() {}
895
    RMapEntry(OUString i_aStream,
896
            OUString i_aXmlId,
897
            std::shared_ptr<MetadatableClipboard>  i_pLink
898
                = std::shared_ptr<MetadatableClipboard>())
899
0
        :   m_Stream(std::move(i_aStream)), m_XmlId(std::move(i_aXmlId)), m_xLink(std::move(i_pLink))
900
0
        {}
901
    OUString m_Stream;
902
    OUString m_XmlId;
903
    // this would have been an auto_ptr, if only that would have compiled...
904
    std::shared_ptr<MetadatableClipboard> m_xLink;
905
};
906
907
}
908
909
/// element -> (stream name, idref, source)
910
typedef std::unordered_map< const Metadatable*,
911
    struct RMapEntry,
912
    PtrHash<Metadatable> >
913
    ClipboardXmlIdReverseMap_t;
914
915
/// Idref -> (content.xml element, styles.xml element)
916
typedef std::unordered_map< OUString,
917
    ::std::pair< Metadatable*, Metadatable* > >
918
    ClipboardXmlIdMap_t;
919
920
struct XmlIdRegistryClipboard::XmlIdRegistry_Impl
921
{
922
0
    XmlIdRegistry_Impl() {}
923
924
    bool TryInsertMetadatable(Metadatable& i_xObject,
925
        std::u16string_view i_rStream, const OUString & i_rIdref);
926
927
    bool LookupXmlId(const Metadatable& i_xObject,
928
        OUString & o_rStream, OUString & o_rIdref,
929
        MetadatableClipboard const* &o_rpLink) const;
930
931
    Metadatable* LookupElement(std::u16string_view i_rStreamName,
932
        const OUString & i_rIdref) const;
933
934
    Metadatable* const* LookupEntry(std::u16string_view i_rStreamName,
935
        const OUString & i_rIdref) const;
936
937
    ClipboardXmlIdMap_t m_XmlIdMap;
938
    ClipboardXmlIdReverseMap_t m_XmlIdReverseMap;
939
};
940
941
942
static void
943
rmIter(ClipboardXmlIdMap_t & i_rXmlIdMap,
944
    ClipboardXmlIdMap_t::iterator const& i_rIter,
945
    std::u16string_view i_rStream, Metadatable const& i_rObject)
946
0
{
947
0
    if (i_rIter == i_rXmlIdMap.end())
948
0
        return;
949
950
0
    Metadatable *& rMeta = isContentFile(i_rStream)
951
0
        ? i_rIter->second.first : i_rIter->second.second;
952
0
    if (rMeta == &i_rObject)
953
0
    {
954
0
        rMeta = nullptr;
955
0
    }
956
0
    if (!i_rIter->second.first && !i_rIter->second.second)
957
0
    {
958
0
        i_rXmlIdMap.erase(i_rIter);
959
0
    }
960
0
}
961
962
963
Metadatable* const*
964
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupEntry(
965
    std::u16string_view i_rStreamName,
966
    const OUString & i_rIdref) const
967
0
{
968
0
    if (!isValidXmlId(i_rStreamName, i_rIdref))
969
0
    {
970
0
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
971
0
    }
972
973
0
    const ClipboardXmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
974
0
    if (iter != m_XmlIdMap.end())
975
0
    {
976
0
        OSL_ENSURE(iter->second.first || iter->second.second,
977
0
            "null entry in m_XmlIdMap");
978
0
        return (isContentFile(i_rStreamName))
979
0
            ?  &iter->second.first
980
0
            :  &iter->second.second;
981
0
    }
982
0
    else
983
0
    {
984
0
        return nullptr;
985
0
    }
986
0
}
987
988
Metadatable*
989
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupElement(
990
    std::u16string_view i_rStreamName,
991
    const OUString & i_rIdref) const
992
0
{
993
0
    Metadatable * const * ppEntry = LookupEntry(i_rStreamName, i_rIdref);
994
0
    return ppEntry ? *ppEntry : nullptr;
995
0
}
996
997
bool
998
XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupXmlId(
999
    const Metadatable& i_rObject,
1000
    OUString & o_rStream, OUString & o_rIdref,
1001
    MetadatableClipboard const* &o_rpLink) const
1002
0
{
1003
0
    const ClipboardXmlIdReverseMap_t::const_iterator iter(
1004
0
        m_XmlIdReverseMap.find(&i_rObject) );
1005
0
    if (iter != m_XmlIdReverseMap.end())
1006
0
    {
1007
0
        OSL_ENSURE(!iter->second.m_Stream.isEmpty(),
1008
0
            "null stream in m_XmlIdReverseMap");
1009
0
        OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
1010
0
            "null id in m_XmlIdReverseMap");
1011
0
        o_rStream = iter->second.m_Stream;
1012
0
        o_rIdref  = iter->second.m_XmlId;
1013
0
        o_rpLink  = iter->second.m_xLink.get();
1014
0
        return true;
1015
0
    }
1016
0
    else
1017
0
    {
1018
0
        return false;
1019
0
    }
1020
0
}
1021
1022
bool
1023
XmlIdRegistryClipboard::XmlIdRegistry_Impl::TryInsertMetadatable(
1024
    Metadatable & i_rObject,
1025
    std::u16string_view i_rStreamName, const OUString & i_rIdref)
1026
0
{
1027
0
    bool bContent( isContentFile(i_rStreamName) );
1028
0
    OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
1029
0
        "invalid stream");
1030
1031
0
    Metadatable ** ppEntry = const_cast<Metadatable**>(LookupEntry(i_rStreamName, i_rIdref));
1032
0
    if (ppEntry)
1033
0
    {
1034
0
        if (*ppEntry)
1035
0
        {
1036
0
            return false;
1037
0
        }
1038
0
        else
1039
0
        {
1040
0
            *ppEntry = &i_rObject;
1041
0
            return true;
1042
0
        }
1043
0
    }
1044
0
    else
1045
0
    {
1046
0
        m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
1047
0
            ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
1048
0
            : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
1049
0
        return true;
1050
0
    }
1051
0
}
1052
1053
1054
// Clipboard XML ID Registry
1055
1056
1057
XmlIdRegistryClipboard::XmlIdRegistryClipboard()
1058
0
    :   m_pImpl( new XmlIdRegistry_Impl )
1059
0
{
1060
0
}
1061
1062
bool
1063
XmlIdRegistryClipboard::LookupXmlId(
1064
    const Metadatable& i_rObject,
1065
    OUString & o_rStream, OUString & o_rIdref) const
1066
0
{
1067
0
    const MetadatableClipboard * pLink;
1068
0
    return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref, pLink);
1069
0
}
1070
1071
Metadatable*
1072
XmlIdRegistryClipboard::LookupElement(
1073
    const OUString & i_rStreamName,
1074
    const OUString & i_rIdref) const
1075
0
{
1076
0
    return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
1077
0
}
1078
1079
bool
1080
XmlIdRegistryClipboard::TryRegisterMetadatable(Metadatable & i_rObject,
1081
    OUString const& i_rStreamName, OUString const& i_rIdref)
1082
0
{
1083
0
    SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref <<")");
1084
1085
0
    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
1086
0
        "TryRegisterMetadatable called for MetadatableUndo?");
1087
0
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
1088
0
        "TryRegisterMetadatable called for MetadatableClipboard?");
1089
1090
0
    if (!isValidXmlId(i_rStreamName, i_rIdref))
1091
0
    {
1092
0
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
1093
0
    }
1094
0
    if (i_rObject.IsInContent()
1095
0
        ?   !isContentFile(i_rStreamName)
1096
0
        :   !isStylesFile(i_rStreamName))
1097
0
    {
1098
0
        throw lang::IllegalArgumentException(u"illegal XmlId: wrong stream"_ustr, nullptr, 0);
1099
0
    }
1100
1101
0
    OUString old_path;
1102
0
    OUString old_idref;
1103
0
    const MetadatableClipboard * pLink;
1104
0
    m_pImpl->LookupXmlId(i_rObject, old_path, old_idref, pLink);
1105
0
    if (old_path  == i_rStreamName && old_idref == i_rIdref)
1106
0
    {
1107
0
        return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
1108
0
    }
1109
0
    ClipboardXmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
1110
0
    if (!old_idref.isEmpty())
1111
0
    {
1112
0
        old_id = m_pImpl->m_XmlIdMap.find(old_idref);
1113
0
        OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
1114
0
    }
1115
0
    if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
1116
0
    {
1117
0
        rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
1118
0
        m_pImpl->m_XmlIdReverseMap[&i_rObject] =
1119
0
            RMapEntry(i_rStreamName, i_rIdref);
1120
0
        return true;
1121
0
    }
1122
0
    else
1123
0
    {
1124
0
        return false;
1125
0
    }
1126
0
}
1127
1128
void
1129
XmlIdRegistryClipboard::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
1130
0
{
1131
0
    SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);
1132
1133
0
    OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
1134
0
        "RegisterMetadatableAndCreateID called for MetadatableUndo?");
1135
0
    OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
1136
0
        "RegisterMetadatableAndCreateID called for MetadatableClipboard?");
1137
1138
0
    bool isInContent( i_rObject.IsInContent() );
1139
0
    OUString stream(
1140
0
        isInContent ? s_content : s_styles );
1141
1142
0
    OUString old_path;
1143
0
    OUString old_idref;
1144
0
    LookupXmlId(i_rObject, old_path, old_idref);
1145
0
    if (!old_idref.isEmpty() &&
1146
0
        (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject))
1147
0
    {
1148
0
        return;
1149
0
    }
1150
1151
    // create id
1152
0
    const OUString id( create_id(m_pImpl->m_XmlIdMap) );
1153
0
    OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
1154
0
        "created id is in use");
1155
0
    m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
1156
0
        ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
1157
0
        : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
1158
    // N.B.: if i_rObject had a latent XmlId, then we implicitly delete the
1159
    // MetadatableClipboard and thus the latent XmlId here
1160
0
    m_pImpl->m_XmlIdReverseMap[&i_rObject] = RMapEntry(stream, id);
1161
0
}
1162
1163
void XmlIdRegistryClipboard::UnregisterMetadatable(const Metadatable& i_rObject)
1164
0
{
1165
0
    SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);
1166
1167
0
    OUString path;
1168
0
    OUString idref;
1169
0
    const MetadatableClipboard * pLink;
1170
0
    if (!m_pImpl->LookupXmlId(i_rObject, path, idref, pLink))
1171
0
    {
1172
0
        OSL_FAIL("unregister: no xml id?");
1173
0
        return;
1174
0
    }
1175
0
    const ClipboardXmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
1176
0
    if (iter != m_pImpl->m_XmlIdMap.end())
1177
0
    {
1178
0
        rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
1179
0
    }
1180
0
}
1181
1182
1183
void XmlIdRegistryClipboard::RemoveXmlIdForElement(const Metadatable& i_rObject)
1184
0
{
1185
0
    SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);
1186
1187
0
    ClipboardXmlIdReverseMap_t::iterator iter(
1188
0
        m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
1189
0
    if (iter != m_pImpl->m_XmlIdReverseMap.end())
1190
0
    {
1191
0
        OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
1192
0
            "null id in m_XmlIdReverseMap");
1193
0
        m_pImpl->m_XmlIdReverseMap.erase(iter);
1194
0
    }
1195
0
}
1196
1197
1198
std::shared_ptr<MetadatableClipboard>
1199
XmlIdRegistryClipboard::CreateClipboard(const bool i_isInContent)
1200
0
{
1201
0
    SAL_INFO("sfx", "CreateClipboard:");
1202
1203
0
    return std::make_shared<MetadatableClipboard>(
1204
0
        i_isInContent );
1205
0
}
1206
1207
MetadatableClipboard &
1208
XmlIdRegistryClipboard::RegisterCopyClipboard(Metadatable & i_rCopy,
1209
    beans::StringPair const & i_rReference,
1210
    const bool i_isLatent)
1211
0
{
1212
0
    SAL_INFO("sfx", "RegisterCopyClipboard: " << &i_rCopy
1213
0
              << " -> (" << i_rReference.First << "#" << i_rReference.Second << ") (" << i_isLatent << ")");
1214
1215
    // N.B.: when copying to the clipboard, the selection is always inserted
1216
    //       into the body, even if the source is a header/footer!
1217
    //       so we do not check whether the stream is right in this function
1218
1219
0
    if (!isValidXmlId(i_rReference.First, i_rReference.Second))
1220
0
    {
1221
0
        throw lang::IllegalArgumentException(u"illegal XmlId"_ustr, nullptr, 0);
1222
0
    }
1223
1224
0
    if (!i_isLatent)
1225
0
    {
1226
        // this should succeed assuming clipboard has a single source document
1227
0
        const bool success( m_pImpl->TryInsertMetadatable(i_rCopy,
1228
0
                i_rReference.First, i_rReference.Second) );
1229
0
        OSL_ENSURE(success, "RegisterCopyClipboard: TryInsert failed?");
1230
0
    }
1231
0
    const std::shared_ptr<MetadatableClipboard> xLink(
1232
0
        CreateClipboard( isContentFile(i_rReference.First)) );
1233
0
    m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
1234
0
        RMapEntry(i_rReference.First, i_rReference.Second, xLink)));
1235
0
    return *xLink;
1236
0
}
1237
1238
MetadatableClipboard const*
1239
XmlIdRegistryClipboard::SourceLink(Metadatable const& i_rObject)
1240
0
{
1241
0
    OUString path;
1242
0
    OUString idref;
1243
0
    const MetadatableClipboard * pLink( nullptr );
1244
0
    m_pImpl->LookupXmlId(i_rObject, path, idref, pLink);
1245
0
    return pLink;
1246
0
}
1247
1248
1249
// Metadatable mixin
1250
1251
1252
Metadatable::~Metadatable()
1253
6.12M
{
1254
6.12M
    RemoveMetadataReference();
1255
6.12M
}
1256
1257
void Metadatable::RemoveMetadataReference()
1258
6.14M
{
1259
6.14M
    try
1260
6.14M
    {
1261
6.14M
        if (m_pReg)
1262
2.05k
        {
1263
2.05k
            m_pReg->UnregisterMetadatable( *this );
1264
2.05k
            m_pReg->RemoveXmlIdForElement( *this );
1265
2.05k
            m_pReg = nullptr;
1266
2.05k
        }
1267
6.14M
    }
1268
6.14M
    catch (const uno::Exception &)
1269
6.14M
    {
1270
0
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RemoveMetadataReference");
1271
0
    }
1272
6.14M
}
1273
1274
// css::rdf::XMetadatable:
1275
beans::StringPair
1276
Metadatable::GetMetadataReference() const
1277
24
{
1278
24
    if (m_pReg)
1279
0
    {
1280
0
        return m_pReg->GetXmlIdForElement(*this);
1281
0
    }
1282
24
    return beans::StringPair();
1283
24
}
1284
1285
void Metadatable::SetMetadataReference( const css::beans::StringPair & i_rReference)
1286
6.47k
{
1287
6.47k
    if (i_rReference.Second.isEmpty())
1288
0
    {
1289
0
        RemoveMetadataReference();
1290
0
    }
1291
6.47k
    else
1292
6.47k
    {
1293
6.47k
        OUString streamName( i_rReference.First );
1294
6.47k
        if (streamName.isEmpty())
1295
6.47k
        {
1296
            // handle empty stream name as auto-detect.
1297
            // necessary for importing flat file format.
1298
6.47k
            streamName = IsInContent() ? s_content : s_styles;
1299
6.47k
        }
1300
6.47k
        XmlIdRegistry & rReg( dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
1301
6.47k
        if (!rReg.TryRegisterMetadatable(*this, streamName, i_rReference.Second))
1302
2.24k
        {
1303
2.24k
            throw lang::IllegalArgumentException(
1304
2.24k
                u"Metadatable::SetMetadataReference: argument is invalid"_ustr, /*this*/nullptr, 0);
1305
2.24k
        }
1306
1307
4.23k
        m_pReg = &rReg;
1308
4.23k
    }
1309
6.47k
}
1310
1311
void Metadatable::EnsureMetadataReference()
1312
0
{
1313
0
    XmlIdRegistry& rReg(
1314
0
        m_pReg ? *m_pReg : dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
1315
0
    rReg.RegisterMetadatableAndCreateID( *this );
1316
0
    m_pReg = &rReg;
1317
0
}
1318
1319
static const ::sfx2::IXmlIdRegistry& GetRegistryConst(Metadatable const& i_rObject)
1320
0
{
1321
0
    return const_cast< Metadatable& >( i_rObject ).GetRegistry();
1322
0
}
1323
1324
void
1325
Metadatable::RegisterAsCopyOf(Metadatable const & i_rSource,
1326
    const bool i_bCopyPrecedesSource)
1327
1.57M
{
1328
1.57M
    OSL_ENSURE(typeid(*this) == typeid(i_rSource)
1329
1.57M
        || typeid(i_rSource) == typeid(MetadatableUndo)
1330
1.57M
        || typeid(*this)     == typeid(MetadatableUndo)
1331
1.57M
        || typeid(i_rSource) == typeid(MetadatableClipboard)
1332
1.57M
        || typeid(*this)     == typeid(MetadatableClipboard),
1333
1.57M
        "RegisterAsCopyOf element with different class?");
1334
1.57M
    OSL_ENSURE(!m_pReg, "RegisterAsCopyOf called on element with XmlId?");
1335
1336
1.57M
    if (m_pReg)
1337
0
    {
1338
0
        RemoveMetadataReference();
1339
0
    }
1340
1341
1.57M
    try
1342
1.57M
    {
1343
1.57M
        if (i_rSource.m_pReg)
1344
0
        {
1345
0
            XmlIdRegistry & rReg(
1346
0
                dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
1347
0
            if (i_rSource.m_pReg == &rReg)
1348
0
            {
1349
0
                OSL_ENSURE(!IsInClipboard(),
1350
0
                    "RegisterAsCopy: both in clipboard?");
1351
0
                if (!IsInClipboard())
1352
0
                {
1353
0
                    XmlIdRegistryDocument & rRegDoc(
1354
0
                        dynamic_cast<XmlIdRegistryDocument&>( rReg ) );
1355
0
                    rRegDoc.RegisterCopy(i_rSource, *this,
1356
0
                        i_bCopyPrecedesSource);
1357
0
                    m_pReg = &rRegDoc;
1358
0
                }
1359
0
                return;
1360
0
            }
1361
            // source is in different document
1362
0
            XmlIdRegistryDocument  * pRegDoc(
1363
0
                dynamic_cast<XmlIdRegistryDocument *>(&rReg) );
1364
0
            XmlIdRegistryClipboard * pRegClp(
1365
0
                dynamic_cast<XmlIdRegistryClipboard*>(&rReg) );
1366
1367
0
            if (pRegClp)
1368
0
            {
1369
0
                beans::StringPair SourceRef(
1370
0
                    i_rSource.m_pReg->GetXmlIdForElement(i_rSource) );
1371
0
                bool isLatent( SourceRef.Second.isEmpty() );
1372
0
                XmlIdRegistryDocument * pSourceRegDoc(
1373
0
                    dynamic_cast<XmlIdRegistryDocument*>(i_rSource.m_pReg) );
1374
0
                OSL_ENSURE(pSourceRegDoc, "RegisterAsCopyOf: 2 clipboards?");
1375
0
                if (!pSourceRegDoc) return;
1376
                // this is a copy _to_ the clipboard
1377
0
                if (isLatent)
1378
0
                {
1379
0
                    pSourceRegDoc->LookupXmlId(i_rSource,
1380
0
                        SourceRef.First, SourceRef.Second);
1381
0
                }
1382
0
                Metadatable & rLink(
1383
0
                    pRegClp->RegisterCopyClipboard(*this, SourceRef, isLatent));
1384
0
                m_pReg = pRegClp;
1385
                // register as copy in the non-clipboard registry
1386
0
                pSourceRegDoc->RegisterCopy(i_rSource, rLink,
1387
0
                    false); // i_bCopyPrecedesSource);
1388
0
                rLink.m_pReg = pSourceRegDoc;
1389
0
            }
1390
0
            else if (pRegDoc)
1391
0
            {
1392
0
                XmlIdRegistryClipboard * pSourceRegClp(
1393
0
                    dynamic_cast<XmlIdRegistryClipboard*>(i_rSource.m_pReg) );
1394
0
                OSL_ENSURE(pSourceRegClp,
1395
0
                    "RegisterAsCopyOf: 2 non-clipboards?");
1396
0
                if (!pSourceRegClp) return;
1397
0
                const MetadatableClipboard * pLink(
1398
0
                    pSourceRegClp->SourceLink(i_rSource) );
1399
                // may happen if src got its id via UNO call
1400
0
                if (!pLink) return;
1401
                // only register copy if clipboard content is from this SwDoc!
1402
0
                if (&GetRegistryConst(*pLink) == pRegDoc)
1403
0
                {
1404
                    // this is a copy _from_ the clipboard; check if the
1405
                    // element is still in the same stream
1406
                    // N.B.: we check the stream of pLink, not of i_rSource!
1407
0
                    bool srcInContent( pLink->IsInContent() );
1408
0
                    bool tgtInContent( IsInContent() );
1409
0
                    if (srcInContent == tgtInContent)
1410
0
                    {
1411
0
                        pRegDoc->RegisterCopy(*pLink, *this,
1412
0
                            true); // i_bCopyPrecedesSource);
1413
0
                        m_pReg = pRegDoc;
1414
0
                    }
1415
                    // otherwise: stream change! do not register!
1416
0
                }
1417
0
            }
1418
0
            else
1419
0
            {
1420
0
                OSL_FAIL("neither RegDoc nor RegClp cannot happen");
1421
0
            }
1422
0
        }
1423
1.57M
    }
1424
1.57M
    catch (const uno::Exception &)
1425
1.57M
    {
1426
0
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RegisterAsCopyOf");
1427
0
    }
1428
1.57M
}
1429
1430
std::shared_ptr<MetadatableUndo> Metadatable::CreateUndo() const
1431
59.6k
{
1432
59.6k
    OSL_ENSURE(!IsInUndo(), "CreateUndo called for object in undo?");
1433
59.6k
    OSL_ENSURE(!IsInClipboard(), "CreateUndo called for object in clipboard?");
1434
59.6k
    try
1435
59.6k
    {
1436
59.6k
        if (!IsInClipboard() && !IsInUndo() && m_pReg)
1437
0
        {
1438
0
            XmlIdRegistryDocument * pRegDoc(
1439
0
                dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
1440
0
            assert(pRegDoc);
1441
0
            std::shared_ptr<MetadatableUndo> xUndo(
1442
0
                sfx2::XmlIdRegistryDocument::CreateUndo(*this) );
1443
0
            pRegDoc->RegisterCopy(*this, *xUndo, false);
1444
0
            xUndo->m_pReg = pRegDoc;
1445
0
            return xUndo;
1446
0
        }
1447
59.6k
    }
1448
59.6k
    catch (const uno::Exception &)
1449
59.6k
    {
1450
0
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::CreateUndo");
1451
0
    }
1452
59.6k
    return std::shared_ptr<MetadatableUndo>();
1453
59.6k
}
1454
1455
std::shared_ptr<MetadatableUndo> Metadatable::CreateUndoForDelete()
1456
0
{
1457
0
    std::shared_ptr<MetadatableUndo> xUndo(CreateUndo());
1458
0
    RemoveMetadataReference();
1459
0
    return xUndo;
1460
0
}
1461
1462
void Metadatable::RestoreMetadata(
1463
    std::shared_ptr<MetadatableUndo> const& i_pUndo)
1464
0
{
1465
0
    OSL_ENSURE(!IsInUndo(), "RestoreMetadata called for object in undo?");
1466
0
    OSL_ENSURE(!IsInClipboard(),
1467
0
        "RestoreMetadata called for object in clipboard?");
1468
0
    if (IsInClipboard() || IsInUndo()) return;
1469
0
    RemoveMetadataReference();
1470
0
    if (i_pUndo)
1471
0
    {
1472
0
        RegisterAsCopyOf(*i_pUndo, true);
1473
0
    }
1474
0
}
1475
1476
void
1477
Metadatable::JoinMetadatable(Metadatable const & i_rOther,
1478
    const bool i_isMergedEmpty, const bool i_isOtherEmpty)
1479
67.4k
{
1480
67.4k
    OSL_ENSURE(!IsInUndo(), "JoinMetadatables called for object in undo?");
1481
67.4k
    OSL_ENSURE(!IsInClipboard(),
1482
67.4k
        "JoinMetadatables called for object in clipboard?");
1483
67.4k
    if (IsInClipboard() || IsInUndo()) return;
1484
1485
67.4k
    if (i_isOtherEmpty && !i_isMergedEmpty)
1486
45.8k
    {
1487
        // other is empty, thus loses => nothing to do
1488
45.8k
        return;
1489
45.8k
    }
1490
21.6k
    if (i_isMergedEmpty && !i_isOtherEmpty)
1491
1.04k
    {
1492
1.04k
        RemoveMetadataReference();
1493
1.04k
        RegisterAsCopyOf(i_rOther, true);
1494
1.04k
        return;
1495
1.04k
    }
1496
1497
20.6k
    if (!i_rOther.m_pReg)
1498
20.6k
    {
1499
        // other doesn't have xmlid, thus loses => nothing to do
1500
20.6k
        return;
1501
20.6k
    }
1502
0
    if (!m_pReg)
1503
0
    {
1504
0
        RegisterAsCopyOf(i_rOther, true);
1505
        // assumption: i_rOther will be deleted, so don't unregister it here
1506
0
        return;
1507
0
    }
1508
0
    try
1509
0
    {
1510
0
        XmlIdRegistryDocument * pRegDoc(
1511
0
            dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
1512
0
        OSL_ENSURE(pRegDoc, "JoinMetadatable: no pRegDoc?");
1513
0
        if (pRegDoc)
1514
0
        {
1515
0
            pRegDoc->JoinMetadatables(*this, i_rOther);
1516
0
        }
1517
0
    }
1518
0
    catch (const uno::Exception &)
1519
0
    {
1520
0
        TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::JoinMetadatable");
1521
0
    }
1522
0
}
1523
1524
1525
// XMetadatable mixin
1526
1527
// css::rdf::XNode:
1528
OUString SAL_CALL MetadatableMixin::getStringValue()
1529
0
{
1530
0
    return getNamespace() + getLocalName();
1531
0
}
1532
1533
// css::rdf::XURI:
1534
OUString SAL_CALL MetadatableMixin::getLocalName()
1535
0
{
1536
0
    SolarMutexGuard aGuard;
1537
0
    beans::StringPair mdref( getMetadataReference() );
1538
0
    if (mdref.Second.isEmpty())
1539
0
    {
1540
0
        ensureMetadataReference(); // N.B.: side effect!
1541
0
        mdref = getMetadataReference();
1542
0
    }
1543
0
    return mdref.First + "#" + mdref.Second;
1544
0
}
1545
1546
OUString SAL_CALL MetadatableMixin::getNamespace()
1547
0
{
1548
0
    SolarMutexGuard aGuard;
1549
0
    const uno::Reference< frame::XModel > xModel( GetModel() );
1550
0
    const uno::Reference< rdf::XURI > xDMA( xModel, uno::UNO_QUERY_THROW );
1551
0
    return xDMA->getStringValue();
1552
0
}
1553
1554
// css::rdf::XMetadatable:
1555
beans::StringPair SAL_CALL
1556
MetadatableMixin::getMetadataReference()
1557
24
{
1558
24
    SolarMutexGuard aGuard;
1559
1560
24
    Metadatable *const pObject( GetCoreObject() );
1561
24
    if (!pObject)
1562
0
    {
1563
0
        throw uno::RuntimeException(
1564
0
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
1565
0
            *this);
1566
0
    }
1567
24
    return pObject->GetMetadataReference();
1568
24
}
1569
1570
void SAL_CALL
1571
MetadatableMixin::setMetadataReference(
1572
    const beans::StringPair & i_rReference)
1573
6.47k
{
1574
6.47k
    SolarMutexGuard aGuard;
1575
1576
6.47k
    Metadatable *const pObject( GetCoreObject() );
1577
6.47k
    if (!pObject)
1578
0
    {
1579
0
        throw uno::RuntimeException(
1580
0
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
1581
0
            *this);
1582
0
    }
1583
6.47k
    return pObject->SetMetadataReference(i_rReference);
1584
6.47k
}
1585
1586
void SAL_CALL MetadatableMixin::ensureMetadataReference()
1587
0
{
1588
0
    SolarMutexGuard aGuard;
1589
1590
0
    Metadatable *const pObject( GetCoreObject() );
1591
0
    if (!pObject)
1592
0
    {
1593
0
        throw uno::RuntimeException(
1594
0
            u"MetadatableMixin: cannot get core object; not inserted?"_ustr,
1595
0
            *this);
1596
0
    }
1597
0
    return pObject->EnsureMetadataReference();
1598
0
}
1599
1600
} // namespace sfx2
1601
1602
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */