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