1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: XMLRedlineExport.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_xmloff.hxx"
33 #include "XMLRedlineExport.hxx"
34 #include <tools/debug.hxx>
35 #include <rtl/ustring.hxx>
36 #include <rtl/ustrbuf.hxx>
37 #include <com/sun/star/beans/XPropertySet.hpp>
38 #include <com/sun/star/beans/UnknownPropertyException.hpp>
39 #include <com/sun/star/container/XEnumerationAccess.hpp>
41 #include <com/sun/star/container/XEnumeration.hpp>
42 #include <com/sun/star/document/XRedlinesSupplier.hpp>
43 #include <com/sun/star/text/XText.hpp>
44 #include <com/sun/star/text/XTextContent.hpp>
45 #include <com/sun/star/text/XTextSection.hpp>
46 #include <com/sun/star/util/DateTime.hpp>
47 #include <xmloff/xmltoken.hxx>
48 #include "xmlnmspe.hxx"
49 #include <xmloff/xmlexp.hxx>
50 #include <xmloff/xmluconv.hxx>
53 using namespace ::com::sun::star
;
54 using namespace ::xmloff::token
;
56 using ::com::sun::star::beans::PropertyValue
;
57 using ::com::sun::star::beans::XPropertySet
;
58 using ::com::sun::star::beans::UnknownPropertyException
;
59 using ::com::sun::star::document::XRedlinesSupplier
;
60 using ::com::sun::star::container::XEnumerationAccess
;
61 using ::com::sun::star::container::XEnumeration
;
62 using ::com::sun::star::text::XText
;
63 using ::com::sun::star::text::XTextContent
;
64 using ::com::sun::star::text::XTextSection
;
65 using ::com::sun::star::uno::Any
;
66 using ::com::sun::star::uno::Reference
;
67 using ::com::sun::star::uno::Sequence
;
68 using ::com::sun::star::util::DateTime
;
69 using ::rtl::OUString
;
70 using ::rtl::OUStringBuffer
;
74 XMLRedlineExport::XMLRedlineExport(SvXMLExport
& rExp
)
75 : sDelete(RTL_CONSTASCII_USTRINGPARAM("Delete"))
76 , sDeletion(GetXMLToken(XML_DELETION
))
77 , sFormat(RTL_CONSTASCII_USTRINGPARAM("Format"))
78 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE
))
79 , sInsert(RTL_CONSTASCII_USTRINGPARAM("Insert"))
80 , sInsertion(GetXMLToken(XML_INSERTION
))
81 , sIsCollapsed(RTL_CONSTASCII_USTRINGPARAM("IsCollapsed"))
82 , sIsStart(RTL_CONSTASCII_USTRINGPARAM("IsStart"))
83 , sRedlineAuthor(RTL_CONSTASCII_USTRINGPARAM("RedlineAuthor"))
84 , sRedlineComment(RTL_CONSTASCII_USTRINGPARAM("RedlineComment"))
85 , sRedlineDateTime(RTL_CONSTASCII_USTRINGPARAM("RedlineDateTime"))
86 , sRedlineSuccessorData(RTL_CONSTASCII_USTRINGPARAM("RedlineSuccessorData"))
87 , sRedlineText(RTL_CONSTASCII_USTRINGPARAM("RedlineText"))
88 , sRedlineType(RTL_CONSTASCII_USTRINGPARAM("RedlineType"))
89 , sStyle(RTL_CONSTASCII_USTRINGPARAM("Style"))
90 , sTextTable(RTL_CONSTASCII_USTRINGPARAM("TextTable"))
91 , sUnknownChange(RTL_CONSTASCII_USTRINGPARAM("UnknownChange"))
92 , sStartRedline(RTL_CONSTASCII_USTRINGPARAM("StartRedline"))
93 , sEndRedline(RTL_CONSTASCII_USTRINGPARAM("EndRedline"))
94 , sRedlineIdentifier(RTL_CONSTASCII_USTRINGPARAM("RedlineIdentifier"))
95 , sIsInHeaderFooter(RTL_CONSTASCII_USTRINGPARAM("IsInHeaderFooter"))
96 , sRedlineProtectionKey(RTL_CONSTASCII_USTRINGPARAM("RedlineProtectionKey"))
97 , sRecordChanges(RTL_CONSTASCII_USTRINGPARAM("RecordChanges"))
98 , sMergeLastPara(RTL_CONSTASCII_USTRINGPARAM("MergeLastPara"))
99 , sChangePrefix(RTL_CONSTASCII_USTRINGPARAM("ct"))
101 , pCurrentChangesList(NULL
)
106 XMLRedlineExport::~XMLRedlineExport()
108 // delete changes lists
109 for( ChangesMapType::iterator aIter
= aChangeMap
.begin();
110 aIter
!= aChangeMap
.end();
113 delete aIter
->second
;
119 void XMLRedlineExport::ExportChange(
120 const Reference
<XPropertySet
> & rPropSet
,
125 // For the headers/footers, we have to collect the autostyles
126 // here. For the general case, however, it's better to collet
127 // the autostyles by iterating over the global redline
128 // list. So that's what we do: Here, we collect autostyles
129 // only if we have no current list of changes. For the
130 // main-document case, the autostyles are collected in
131 // ExportChangesListAutoStyles().
132 if (pCurrentChangesList
!= NULL
)
133 ExportChangeAutoStyle(rPropSet
);
137 ExportChangeInline(rPropSet
);
142 void XMLRedlineExport::ExportChangesList(sal_Bool bAutoStyles
)
146 ExportChangesListAutoStyles();
150 ExportChangesListElements();
155 void XMLRedlineExport::ExportChangesList(
156 const Reference
<XText
> & rText
,
157 sal_Bool bAutoStyles
)
159 // in the header/footer case, auto styles are collected from the
160 // inline change elements.
164 // look for changes list for this XText
165 ChangesMapType::iterator aFind
= aChangeMap
.find(rText
);
166 if (aFind
!= aChangeMap
.end())
168 ChangesListType
* pChangesList
= aFind
->second
;
170 // export only if changes are found
171 if (pChangesList
->size() > 0)
173 // changes container element
174 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
178 // iterate over changes list
179 for( ChangesListType::iterator aIter
= pChangesList
->begin();
180 aIter
!= pChangesList
->end();
183 ExportChangedRegion( *aIter
);
186 // else: changes list empty -> ignore
188 // else: no changes list found -> empty
191 void XMLRedlineExport::SetCurrentXText(
192 const Reference
<XText
> & rText
)
196 // look for appropriate list in map; use the found one, or create new
197 ChangesMapType::iterator aIter
= aChangeMap
.find(rText
);
198 if (aIter
== aChangeMap
.end())
200 ChangesListType
* pList
= new ChangesListType
;
201 aChangeMap
[rText
] = pList
;
202 pCurrentChangesList
= pList
;
205 pCurrentChangesList
= aIter
->second
;
209 // don't record changes
214 void XMLRedlineExport::SetCurrentXText()
216 pCurrentChangesList
= NULL
;
220 void XMLRedlineExport::ExportChangesListElements()
222 // get redlines (aka tracked changes) from the model
223 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
226 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
228 // redline protection key
229 Reference
<XPropertySet
> aDocPropertySet( rExport
.GetModel(),
231 // redlining enabled?
232 sal_Bool bEnabled
= *(sal_Bool
*)aDocPropertySet
->getPropertyValue(
233 sRecordChanges
).getValue();
235 // only export if we have redlines or attributes
236 if ( aEnumAccess
->hasElements() || bEnabled
)
239 // export only if we have changes, but tracking is not enabled
240 if ( !bEnabled
!= !aEnumAccess
->hasElements() )
242 rExport
.AddAttribute(
243 XML_NAMESPACE_TEXT
, XML_TRACK_CHANGES
,
244 bEnabled
? XML_TRUE
: XML_FALSE
);
247 // changes container element
248 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
252 // get enumeration and iterate over elements
253 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
254 while (aEnum
->hasMoreElements())
256 Any aAny
= aEnum
->nextElement();
257 Reference
<XPropertySet
> xPropSet
;
260 DBG_ASSERT(xPropSet
.is(),
261 "can't get XPropertySet; skipping Redline");
264 // export only if not in header or footer
265 // (those must be exported with their XText)
266 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
267 if (! *(sal_Bool
*)aAny
.getValue())
269 // and finally, export change
270 ExportChangedRegion(xPropSet
);
273 // else: no XPropertySet -> no export
276 // else: no redlines -> no export
278 // else: no XRedlineSupplier -> no export
281 void XMLRedlineExport::ExportChangeAutoStyle(
282 const Reference
<XPropertySet
> & rPropSet
)
284 // record change (if changes should be recorded)
285 if (NULL
!= pCurrentChangesList
)
287 // put redline in list if it's collapsed or the redline start
288 Any aIsStart
= rPropSet
->getPropertyValue(sIsStart
);
289 Any aIsCollapsed
= rPropSet
->getPropertyValue(sIsCollapsed
);
291 if ( *(sal_Bool
*)aIsStart
.getValue() ||
292 *(sal_Bool
*)aIsCollapsed
.getValue() )
293 pCurrentChangesList
->push_back(rPropSet
);
296 // get XText for export of redline auto styles
297 Any aAny
= rPropSet
->getPropertyValue(sRedlineText
);
298 Reference
<XText
> xText
;
302 // export the auto styles
303 rExport
.GetTextParagraphExport()->collectTextAutoStyles(xText
);
307 void XMLRedlineExport::ExportChangesListAutoStyles()
309 // get redlines (aka tracked changes) from the model
310 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
313 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
315 // only export if we actually have redlines
316 if (aEnumAccess
->hasElements())
318 // get enumeration and iterate over elements
319 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
320 while (aEnum
->hasMoreElements())
322 Any aAny
= aEnum
->nextElement();
323 Reference
<XPropertySet
> xPropSet
;
326 DBG_ASSERT(xPropSet
.is(),
327 "can't get XPropertySet; skipping Redline");
331 // export only if not in header or footer
332 // (those must be exported with their XText)
333 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
334 if (! *(sal_Bool
*)aAny
.getValue())
336 ExportChangeAutoStyle(xPropSet
);
344 void XMLRedlineExport::ExportChangeInline(
345 const Reference
<XPropertySet
> & rPropSet
)
347 // determine element name (depending on collapsed, start/end)
348 enum XMLTokenEnum eElement
= XML_TOKEN_INVALID
;
349 Any aAny
= rPropSet
->getPropertyValue(sIsCollapsed
);
350 sal_Bool bCollapsed
= *(sal_Bool
*)aAny
.getValue();
351 sal_Bool bStart
= sal_True
; // ignored if bCollapsed = sal_True
354 eElement
= XML_CHANGE
;
358 aAny
= rPropSet
->getPropertyValue(sIsStart
);
359 bStart
= *(sal_Bool
*)aAny
.getValue();
360 eElement
= bStart
? XML_CHANGE_START
: XML_CHANGE_END
;
363 if (XML_TOKEN_INVALID
!= eElement
)
365 // we always need the ID
366 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
367 GetRedlineID(rPropSet
));
369 // export the element (no whitespace because we're in the text body)
370 SvXMLElementExport
aChangeElem(rExport
, XML_NAMESPACE_TEXT
,
371 eElement
, sal_False
, sal_False
);
376 void XMLRedlineExport::ExportChangedRegion(
377 const Reference
<XPropertySet
> & rPropSet
)
380 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_ID
, GetRedlineID(rPropSet
) );
382 // merge-last-paragraph
383 Any aAny
= rPropSet
->getPropertyValue(sMergeLastPara
);
384 if( ! *(sal_Bool
*)aAny
.getValue() )
385 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_MERGE_LAST_PARAGRAPH
,
388 // export change region element
389 SvXMLElementExport
aChangedRegion(rExport
, XML_NAMESPACE_TEXT
,
390 XML_CHANGED_REGION
, sal_True
, sal_True
);
393 // scope for (first) change element
395 aAny
= rPropSet
->getPropertyValue(sRedlineType
);
398 SvXMLElementExport
aChange(rExport
, XML_NAMESPACE_TEXT
,
399 ConvertTypeName(sType
), sal_True
, sal_True
);
401 ExportChangeInfo(rPropSet
);
403 // get XText from the redline and export (if the XText exists)
404 aAny
= rPropSet
->getPropertyValue(sRedlineText
);
405 Reference
<XText
> xText
;
409 rExport
.GetTextParagraphExport()->exportText(xText
);
410 // default parameters: bProgress, bExportParagraph ???
412 // else: no text interface -> content is inline and will
416 // changed change? Hierarchical changes can onl be two levels
417 // deep. Here we check for the second level.
418 aAny
= rPropSet
->getPropertyValue(sRedlineSuccessorData
);
419 Sequence
<PropertyValue
> aSuccessorData
;
420 aAny
>>= aSuccessorData
;
422 // if we actually got a hierarchical change, make element and
423 // process change info
424 if (aSuccessorData
.getLength() > 0)
426 // The only change that can be "undone" is an insertion -
427 // after all, you can't re-insert an deletion, but you can
428 // delete an insertion. This assumption is asserted in
429 // ExportChangeInfo(Sequence<PropertyValue>&).
430 SvXMLElementExport
aSecondChangeElem(
431 rExport
, XML_NAMESPACE_TEXT
, XML_INSERTION
,
434 ExportChangeInfo(aSuccessorData
);
436 // else: no hierarchical change
440 const OUString
XMLRedlineExport::ConvertTypeName(
441 const OUString
& sApiName
)
443 if (sApiName
== sDelete
)
447 else if (sApiName
== sInsert
)
451 else if (sApiName
== sFormat
)
453 return sFormatChange
;
457 DBG_ERROR("unknown redline type");
458 return sUnknownChange
;
463 /** Create a Redline-ID */
464 const OUString
XMLRedlineExport::GetRedlineID(
465 const Reference
<XPropertySet
> & rPropSet
)
467 Any aAny
= rPropSet
->getPropertyValue(sRedlineIdentifier
);
471 OUStringBuffer
sBuf(sChangePrefix
);
473 return sBuf
.makeStringAndClear();
477 void XMLRedlineExport::ExportChangeInfo(
478 const Reference
<XPropertySet
> & rPropSet
)
481 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
482 XML_CHANGE_INFO
, sal_True
, sal_True
);
484 Any aAny
= rPropSet
->getPropertyValue(sRedlineAuthor
);
487 if (sTmp
.getLength() > 0)
489 SvXMLElementExport
aCreatorElem( rExport
, XML_NAMESPACE_DC
,
490 XML_CREATOR
, sal_True
,
492 rExport
.Characters(sTmp
);
495 aAny
= rPropSet
->getPropertyValue(sRedlineDateTime
);
496 util::DateTime aDateTime
;
500 rExport
.GetMM100UnitConverter().convertDateTime(sBuf
, aDateTime
);
501 SvXMLElementExport
aDateElem( rExport
, XML_NAMESPACE_DC
,
504 rExport
.Characters(sBuf
.makeStringAndClear());
507 // comment as <text:p> sequence
508 aAny
= rPropSet
->getPropertyValue(sRedlineComment
);
510 WriteComment( sTmp
);
513 void XMLRedlineExport::ExportChangeInfo(
514 const Sequence
<PropertyValue
> & rPropertyValues
)
518 sal_Int32 nCount
= rPropertyValues
.getLength();
519 for(sal_Int32 i
= 0; i
< nCount
; i
++)
521 const PropertyValue
& rVal
= rPropertyValues
[i
];
523 if( rVal
.Name
.equals(sRedlineAuthor
) )
527 if (sTmp
.getLength() > 0)
529 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_AUTHOR
, sTmp
);
532 else if( rVal
.Name
.equals(sRedlineComment
) )
534 rVal
.Value
>>= sComment
;
536 else if( rVal
.Name
.equals(sRedlineDateTime
) )
538 util::DateTime aDateTime
;
539 rVal
.Value
>>= aDateTime
;
541 rExport
.GetMM100UnitConverter().convertDateTime(sBuf
, aDateTime
);
542 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_DATE_TIME
,
543 sBuf
.makeStringAndClear());
545 else if( rVal
.Name
.equals(sRedlineType
) )
547 // check if this is an insertion; cf. comment at calling location
550 DBG_ASSERT(sTmp
.equals(sInsert
),
551 "hierarchical change must be insertion");
553 // else: unknown value -> ignore
556 // finally write element
557 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
558 XML_CHANGE_INFO
, sal_True
, sal_True
);
560 WriteComment( sComment
);
563 void XMLRedlineExport::ExportStartOrEndRedline(
564 const Reference
<XPropertySet
> & rPropSet
,
567 if( ! rPropSet
.is() )
570 // get appropriate (start or end) property
574 aAny
= rPropSet
->getPropertyValue(bStart
? sStartRedline
: sEndRedline
);
576 catch( UnknownPropertyException e
)
578 // If we don't have the property, there's nothing to do.
582 Sequence
<PropertyValue
> aValues
;
584 const PropertyValue
* pValues
= aValues
.getConstArray();
586 // seek for redline properties
587 sal_Bool bIsCollapsed
= sal_False
;
588 sal_Bool bIsStart
= sal_True
;
590 sal_Bool bIdOK
= sal_False
; // have we seen an ID?
591 sal_Int32 nLength
= aValues
.getLength();
592 for(sal_Int32 i
= 0; i
< nLength
; i
++)
594 if (sRedlineIdentifier
.equals(pValues
[i
].Name
))
596 pValues
[i
].Value
>>= sId
;
599 else if (sIsCollapsed
.equals(pValues
[i
].Name
))
601 bIsCollapsed
= *(sal_Bool
*)pValues
[i
].Value
.getValue();
603 else if (sIsStart
.equals(pValues
[i
].Name
))
605 bIsStart
= *(sal_Bool
*)pValues
[i
].Value
.getValue();
611 DBG_ASSERT( sId
.getLength() > 0, "Redlines must have IDs" );
613 // TODO: use GetRedlineID or elimiate that function
614 OUStringBuffer
sBuffer(sChangePrefix
);
617 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
618 sBuffer
.makeStringAndClear());
620 // export the element
621 // (whitespace because we're not inside paragraphs)
622 SvXMLElementExport
aChangeElem(
623 rExport
, XML_NAMESPACE_TEXT
,
624 bIsCollapsed
? XML_CHANGE
:
625 ( bIsStart
? XML_CHANGE_START
: XML_CHANGE_END
),
630 void XMLRedlineExport::ExportStartOrEndRedline(
631 const Reference
<XTextContent
> & rContent
,
634 Reference
<XPropertySet
> xPropSet(rContent
, uno::UNO_QUERY
);
637 ExportStartOrEndRedline(xPropSet
, bStart
);
641 DBG_ERROR("XPropertySet expected");
645 void XMLRedlineExport::ExportStartOrEndRedline(
646 const Reference
<XTextSection
> & rSection
,
649 Reference
<XPropertySet
> xPropSet(rSection
, uno::UNO_QUERY
);
652 ExportStartOrEndRedline(xPropSet
, bStart
);
656 DBG_ERROR("XPropertySet expected");
660 void XMLRedlineExport::WriteComment(const OUString
& rComment
)
662 if (rComment
.getLength() > 0)
664 // iterate over all string-pieces separated by return (0x0a) and
665 // put each inside a paragraph element.
666 SvXMLTokenEnumerator
aEnumerator(rComment
, sal_Char(0x0a));
668 while (aEnumerator
.getNextToken(aSubString
))
670 SvXMLElementExport
aParagraph(
671 rExport
, XML_NAMESPACE_TEXT
, XML_P
, sal_True
, sal_False
);
672 rExport
.Characters(aSubString
);