1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "XMLRedlineExport.hxx"
21 #include <tools/debug.hxx>
22 #include <rtl/ustring.hxx>
23 #include <rtl/ustrbuf.hxx>
24 #include <com/sun/star/beans/XPropertySet.hpp>
25 #include <com/sun/star/beans/UnknownPropertyException.hpp>
26 #include <com/sun/star/container/XEnumerationAccess.hpp>
28 #include <com/sun/star/container/XEnumeration.hpp>
29 #include <com/sun/star/document/XRedlinesSupplier.hpp>
30 #include <com/sun/star/text/XText.hpp>
31 #include <com/sun/star/text/XTextContent.hpp>
32 #include <com/sun/star/text/XTextSection.hpp>
33 #include <com/sun/star/util/DateTime.hpp>
35 #include <sax/tools/converter.hxx>
37 #include <xmloff/xmltoken.hxx>
38 #include <xmloff/xmlnmspe.hxx>
39 #include <xmloff/xmlexp.hxx>
40 #include <xmloff/xmluconv.hxx>
43 using namespace ::com::sun::star
;
44 using namespace ::xmloff::token
;
46 using ::com::sun::star::beans::PropertyValue
;
47 using ::com::sun::star::beans::XPropertySet
;
48 using ::com::sun::star::beans::UnknownPropertyException
;
49 using ::com::sun::star::document::XRedlinesSupplier
;
50 using ::com::sun::star::container::XEnumerationAccess
;
51 using ::com::sun::star::container::XEnumeration
;
52 using ::com::sun::star::text::XText
;
53 using ::com::sun::star::text::XTextContent
;
54 using ::com::sun::star::text::XTextSection
;
55 using ::com::sun::star::uno::Any
;
56 using ::com::sun::star::uno::Reference
;
57 using ::com::sun::star::uno::Sequence
;
61 XMLRedlineExport::XMLRedlineExport(SvXMLExport
& rExp
)
63 , sDeletion(GetXMLToken(XML_DELETION
))
65 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE
))
67 , sInsertion(GetXMLToken(XML_INSERTION
))
68 , sIsCollapsed("IsCollapsed")
70 , sRedlineAuthor("RedlineAuthor")
71 , sRedlineComment("RedlineComment")
72 , sRedlineDateTime("RedlineDateTime")
73 , sRedlineSuccessorData("RedlineSuccessorData")
74 , sRedlineText("RedlineText")
75 , sRedlineType("RedlineType")
76 , sUnknownChange("UnknownChange")
77 , sStartRedline("StartRedline")
78 , sEndRedline("EndRedline")
79 , sRedlineIdentifier("RedlineIdentifier")
80 , sIsInHeaderFooter("IsInHeaderFooter")
81 , sRecordChanges("RecordChanges")
82 , sMergeLastPara("MergeLastPara")
85 , pCurrentChangesList(NULL
)
90 XMLRedlineExport::~XMLRedlineExport()
92 // delete changes lists
93 for( ChangesMapType::iterator aIter
= aChangeMap
.begin();
94 aIter
!= aChangeMap
.end();
103 void XMLRedlineExport::ExportChange(
104 const Reference
<XPropertySet
> & rPropSet
,
109 // For the headers/footers, we have to collect the autostyles
110 // here. For the general case, however, it's better to collet
111 // the autostyles by iterating over the global redline
112 // list. So that's what we do: Here, we collect autostyles
113 // only if we have no current list of changes. For the
114 // main-document case, the autostyles are collected in
115 // ExportChangesListAutoStyles().
116 if (pCurrentChangesList
!= NULL
)
117 ExportChangeAutoStyle(rPropSet
);
121 ExportChangeInline(rPropSet
);
126 void XMLRedlineExport::ExportChangesList(bool bAutoStyles
)
130 ExportChangesListAutoStyles();
134 ExportChangesListElements();
139 void XMLRedlineExport::ExportChangesList(
140 const Reference
<XText
> & rText
,
143 // in the header/footer case, auto styles are collected from the
144 // inline change elements.
148 // look for changes list for this XText
149 ChangesMapType::iterator aFind
= aChangeMap
.find(rText
);
150 if (aFind
!= aChangeMap
.end())
152 ChangesListType
* pChangesList
= aFind
->second
;
154 // export only if changes are found
155 if (pChangesList
->size() > 0)
157 // changes container element
158 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
162 // iterate over changes list
163 for( ChangesListType::iterator aIter
= pChangesList
->begin();
164 aIter
!= pChangesList
->end();
167 ExportChangedRegion( *aIter
);
170 // else: changes list empty -> ignore
172 // else: no changes list found -> empty
175 void XMLRedlineExport::SetCurrentXText(
176 const Reference
<XText
> & rText
)
180 // look for appropriate list in map; use the found one, or create new
181 ChangesMapType::iterator aIter
= aChangeMap
.find(rText
);
182 if (aIter
== aChangeMap
.end())
184 ChangesListType
* pList
= new ChangesListType
;
185 aChangeMap
[rText
] = pList
;
186 pCurrentChangesList
= pList
;
189 pCurrentChangesList
= aIter
->second
;
193 // don't record changes
198 void XMLRedlineExport::SetCurrentXText()
200 pCurrentChangesList
= NULL
;
204 void XMLRedlineExport::ExportChangesListElements()
206 // get redlines (aka tracked changes) from the model
207 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
210 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
212 // redline protection key
213 Reference
<XPropertySet
> aDocPropertySet( rExport
.GetModel(),
215 // redlining enabled?
216 bool bEnabled
= *static_cast<sal_Bool
const *>(aDocPropertySet
->getPropertyValue(
217 sRecordChanges
).getValue());
219 // only export if we have redlines or attributes
220 if ( aEnumAccess
->hasElements() || bEnabled
)
223 // export only if we have changes, but tracking is not enabled
224 if ( !bEnabled
!= !aEnumAccess
->hasElements() )
226 rExport
.AddAttribute(
227 XML_NAMESPACE_TEXT
, XML_TRACK_CHANGES
,
228 bEnabled
? XML_TRUE
: XML_FALSE
);
231 // changes container element
232 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
236 // get enumeration and iterate over elements
237 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
238 while (aEnum
->hasMoreElements())
240 Any aAny
= aEnum
->nextElement();
241 Reference
<XPropertySet
> xPropSet
;
244 DBG_ASSERT(xPropSet
.is(),
245 "can't get XPropertySet; skipping Redline");
248 // export only if not in header or footer
249 // (those must be exported with their XText)
250 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
251 if (! *static_cast<sal_Bool
const *>(aAny
.getValue()))
253 // and finally, export change
254 ExportChangedRegion(xPropSet
);
257 // else: no XPropertySet -> no export
260 // else: no redlines -> no export
262 // else: no XRedlineSupplier -> no export
265 void XMLRedlineExport::ExportChangeAutoStyle(
266 const Reference
<XPropertySet
> & rPropSet
)
268 // record change (if changes should be recorded)
269 if (NULL
!= pCurrentChangesList
)
271 // put redline in list if it's collapsed or the redline start
272 Any aIsStart
= rPropSet
->getPropertyValue(sIsStart
);
273 Any aIsCollapsed
= rPropSet
->getPropertyValue(sIsCollapsed
);
275 if ( *static_cast<sal_Bool
const *>(aIsStart
.getValue()) ||
276 *static_cast<sal_Bool
const *>(aIsCollapsed
.getValue()) )
277 pCurrentChangesList
->push_back(rPropSet
);
280 // get XText for export of redline auto styles
281 Any aAny
= rPropSet
->getPropertyValue(sRedlineText
);
282 Reference
<XText
> xText
;
286 // export the auto styles
287 rExport
.GetTextParagraphExport()->collectTextAutoStyles(xText
);
291 void XMLRedlineExport::ExportChangesListAutoStyles()
293 // get redlines (aka tracked changes) from the model
294 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
297 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
299 // only export if we actually have redlines
300 if (aEnumAccess
->hasElements())
302 // get enumeration and iterate over elements
303 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
304 while (aEnum
->hasMoreElements())
306 Any aAny
= aEnum
->nextElement();
307 Reference
<XPropertySet
> xPropSet
;
310 DBG_ASSERT(xPropSet
.is(),
311 "can't get XPropertySet; skipping Redline");
315 // export only if not in header or footer
316 // (those must be exported with their XText)
317 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
318 if (! *static_cast<sal_Bool
const *>(aAny
.getValue()))
320 ExportChangeAutoStyle(xPropSet
);
328 void XMLRedlineExport::ExportChangeInline(
329 const Reference
<XPropertySet
> & rPropSet
)
331 // determine element name (depending on collapsed, start/end)
332 enum XMLTokenEnum eElement
= XML_TOKEN_INVALID
;
333 Any aAny
= rPropSet
->getPropertyValue(sIsCollapsed
);
334 bool bCollapsed
= *static_cast<sal_Bool
const *>(aAny
.getValue());
337 eElement
= XML_CHANGE
;
341 aAny
= rPropSet
->getPropertyValue(sIsStart
);
342 const bool bStart
= *static_cast<sal_Bool
const *>(aAny
.getValue());
343 eElement
= bStart
? XML_CHANGE_START
: XML_CHANGE_END
;
346 if (XML_TOKEN_INVALID
!= eElement
)
348 // we always need the ID
349 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
350 GetRedlineID(rPropSet
));
352 // export the element (no whitespace because we're in the text body)
353 SvXMLElementExport
aChangeElem(rExport
, XML_NAMESPACE_TEXT
,
354 eElement
, false, false);
359 void XMLRedlineExport::ExportChangedRegion(
360 const Reference
<XPropertySet
> & rPropSet
)
363 rExport
.AddAttributeIdLegacy(XML_NAMESPACE_TEXT
, GetRedlineID(rPropSet
));
365 // merge-last-paragraph
366 Any aAny
= rPropSet
->getPropertyValue(sMergeLastPara
);
367 if( ! *static_cast<sal_Bool
const *>(aAny
.getValue()) )
368 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_MERGE_LAST_PARAGRAPH
,
371 // export change region element
372 SvXMLElementExport
aChangedRegion(rExport
, XML_NAMESPACE_TEXT
,
373 XML_CHANGED_REGION
, true, true);
376 // scope for (first) change element
378 aAny
= rPropSet
->getPropertyValue(sRedlineType
);
381 SvXMLElementExport
aChange(rExport
, XML_NAMESPACE_TEXT
,
382 ConvertTypeName(sType
), true, true);
384 ExportChangeInfo(rPropSet
);
386 // get XText from the redline and export (if the XText exists)
387 aAny
= rPropSet
->getPropertyValue(sRedlineText
);
388 Reference
<XText
> xText
;
392 rExport
.GetTextParagraphExport()->exportText(xText
);
393 // default parameters: bProgress, bExportParagraph ???
395 // else: no text interface -> content is inline and will
399 // changed change? Hierarchical changes can onl be two levels
400 // deep. Here we check for the second level.
401 aAny
= rPropSet
->getPropertyValue(sRedlineSuccessorData
);
402 Sequence
<PropertyValue
> aSuccessorData
;
403 aAny
>>= aSuccessorData
;
405 // if we actually got a hierarchical change, make element and
406 // process change info
407 if (aSuccessorData
.getLength() > 0)
409 // The only change that can be "undone" is an insertion -
410 // after all, you can't re-insert an deletion, but you can
411 // delete an insertion. This assumption is asserted in
412 // ExportChangeInfo(Sequence<PropertyValue>&).
413 SvXMLElementExport
aSecondChangeElem(
414 rExport
, XML_NAMESPACE_TEXT
, XML_INSERTION
,
417 ExportChangeInfo(aSuccessorData
);
419 // else: no hierarchical change
423 const OUString
XMLRedlineExport::ConvertTypeName(
424 const OUString
& sApiName
)
426 if (sApiName
== sDelete
)
430 else if (sApiName
== sInsert
)
434 else if (sApiName
== sFormat
)
436 return sFormatChange
;
440 OSL_FAIL("unknown redline type");
441 return sUnknownChange
;
446 /** Create a Redline-ID */
447 const OUString
XMLRedlineExport::GetRedlineID(
448 const Reference
<XPropertySet
> & rPropSet
)
450 Any aAny
= rPropSet
->getPropertyValue(sRedlineIdentifier
);
454 OUStringBuffer
sBuf(sChangePrefix
);
456 return sBuf
.makeStringAndClear();
460 void XMLRedlineExport::ExportChangeInfo(
461 const Reference
<XPropertySet
> & rPropSet
)
464 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
465 XML_CHANGE_INFO
, true, true);
467 Any aAny
= rPropSet
->getPropertyValue(sRedlineAuthor
);
472 SvXMLElementExport
aCreatorElem( rExport
, XML_NAMESPACE_DC
,
475 rExport
.Characters(sTmp
);
478 aAny
= rPropSet
->getPropertyValue(sRedlineDateTime
);
479 util::DateTime aDateTime
;
483 ::sax::Converter::convertDateTime(sBuf
, aDateTime
, 0);
484 SvXMLElementExport
aDateElem( rExport
, XML_NAMESPACE_DC
,
487 rExport
.Characters(sBuf
.makeStringAndClear());
490 // comment as <text:p> sequence
491 aAny
= rPropSet
->getPropertyValue(sRedlineComment
);
493 WriteComment( sTmp
);
496 void XMLRedlineExport::ExportChangeInfo(
497 const Sequence
<PropertyValue
> & rPropertyValues
)
501 sal_Int32 nCount
= rPropertyValues
.getLength();
502 for(sal_Int32 i
= 0; i
< nCount
; i
++)
504 const PropertyValue
& rVal
= rPropertyValues
[i
];
506 if( rVal
.Name
.equals(sRedlineAuthor
) )
512 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_AUTHOR
, sTmp
);
515 else if( rVal
.Name
.equals(sRedlineComment
) )
517 rVal
.Value
>>= sComment
;
519 else if( rVal
.Name
.equals(sRedlineDateTime
) )
521 util::DateTime aDateTime
;
522 rVal
.Value
>>= aDateTime
;
524 ::sax::Converter::convertDateTime(sBuf
, aDateTime
, 0);
525 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_DATE_TIME
,
526 sBuf
.makeStringAndClear());
528 else if( rVal
.Name
.equals(sRedlineType
) )
530 // check if this is an insertion; cf. comment at calling location
533 DBG_ASSERT(sTmp
.equals(sInsert
),
534 "hierarchical change must be insertion");
536 // else: unknown value -> ignore
539 // finally write element
540 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
541 XML_CHANGE_INFO
, true, true);
543 WriteComment( sComment
);
546 void XMLRedlineExport::ExportStartOrEndRedline(
547 const Reference
<XPropertySet
> & rPropSet
,
550 if( ! rPropSet
.is() )
553 // get appropriate (start or end) property
557 aAny
= rPropSet
->getPropertyValue(bStart
? sStartRedline
: sEndRedline
);
559 catch(const UnknownPropertyException
&)
561 // If we don't have the property, there's nothing to do.
565 Sequence
<PropertyValue
> aValues
;
567 const PropertyValue
* pValues
= aValues
.getConstArray();
569 // seek for redline properties
570 bool bIsCollapsed
= false;
571 bool bIsStart
= true;
573 bool bIdOK
= false; // have we seen an ID?
574 sal_Int32 nLength
= aValues
.getLength();
575 for(sal_Int32 i
= 0; i
< nLength
; i
++)
577 if (sRedlineIdentifier
.equals(pValues
[i
].Name
))
579 pValues
[i
].Value
>>= sId
;
582 else if (sIsCollapsed
.equals(pValues
[i
].Name
))
584 bIsCollapsed
= *static_cast<sal_Bool
const *>(pValues
[i
].Value
.getValue());
586 else if (sIsStart
.equals(pValues
[i
].Name
))
588 bIsStart
= *static_cast<sal_Bool
const *>(pValues
[i
].Value
.getValue());
594 DBG_ASSERT( !sId
.isEmpty(), "Redlines must have IDs" );
596 // TODO: use GetRedlineID or elimiate that function
597 OUStringBuffer
sBuffer(sChangePrefix
);
600 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
601 sBuffer
.makeStringAndClear());
603 // export the element
604 // (whitespace because we're not inside paragraphs)
605 SvXMLElementExport
aChangeElem(
606 rExport
, XML_NAMESPACE_TEXT
,
607 bIsCollapsed
? XML_CHANGE
:
608 ( bIsStart
? XML_CHANGE_START
: XML_CHANGE_END
),
613 void XMLRedlineExport::ExportStartOrEndRedline(
614 const Reference
<XTextContent
> & rContent
,
617 Reference
<XPropertySet
> xPropSet(rContent
, uno::UNO_QUERY
);
620 ExportStartOrEndRedline(xPropSet
, bStart
);
624 OSL_FAIL("XPropertySet expected");
628 void XMLRedlineExport::ExportStartOrEndRedline(
629 const Reference
<XTextSection
> & rSection
,
632 Reference
<XPropertySet
> xPropSet(rSection
, uno::UNO_QUERY
);
635 ExportStartOrEndRedline(xPropSet
, bStart
);
639 OSL_FAIL("XPropertySet expected");
643 void XMLRedlineExport::WriteComment(const OUString
& rComment
)
645 if (!rComment
.isEmpty())
647 // iterate over all string-pieces separated by return (0x0a) and
648 // put each inside a paragraph element.
649 SvXMLTokenEnumerator
aEnumerator(rComment
, sal_Char(0x0a));
651 while (aEnumerator
.getNextToken(aSubString
))
653 SvXMLElementExport
aParagraph(
654 rExport
, XML_NAMESPACE_TEXT
, XML_P
, true, false);
655 rExport
.Characters(aSubString
);
660 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */