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")
77 , sTextTable("TextTable")
78 , sUnknownChange("UnknownChange")
79 , sStartRedline("StartRedline")
80 , sEndRedline("EndRedline")
81 , sRedlineIdentifier("RedlineIdentifier")
82 , sIsInHeaderFooter("IsInHeaderFooter")
83 , sRedlineProtectionKey("RedlineProtectionKey")
84 , sRecordChanges("RecordChanges")
85 , sMergeLastPara("MergeLastPara")
88 , pCurrentChangesList(NULL
)
93 XMLRedlineExport::~XMLRedlineExport()
95 // delete changes lists
96 for( ChangesMapType::iterator aIter
= aChangeMap
.begin();
97 aIter
!= aChangeMap
.end();
100 delete aIter
->second
;
106 void XMLRedlineExport::ExportChange(
107 const Reference
<XPropertySet
> & rPropSet
,
112 // For the headers/footers, we have to collect the autostyles
113 // here. For the general case, however, it's better to collet
114 // the autostyles by iterating over the global redline
115 // list. So that's what we do: Here, we collect autostyles
116 // only if we have no current list of changes. For the
117 // main-document case, the autostyles are collected in
118 // ExportChangesListAutoStyles().
119 if (pCurrentChangesList
!= NULL
)
120 ExportChangeAutoStyle(rPropSet
);
124 ExportChangeInline(rPropSet
);
129 void XMLRedlineExport::ExportChangesList(sal_Bool bAutoStyles
)
133 ExportChangesListAutoStyles();
137 ExportChangesListElements();
142 void XMLRedlineExport::ExportChangesList(
143 const Reference
<XText
> & rText
,
144 sal_Bool bAutoStyles
)
146 // in the header/footer case, auto styles are collected from the
147 // inline change elements.
151 // look for changes list for this XText
152 ChangesMapType::iterator aFind
= aChangeMap
.find(rText
);
153 if (aFind
!= aChangeMap
.end())
155 ChangesListType
* pChangesList
= aFind
->second
;
157 // export only if changes are found
158 if (pChangesList
->size() > 0)
160 // changes container element
161 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
165 // iterate over changes list
166 for( ChangesListType::iterator aIter
= pChangesList
->begin();
167 aIter
!= pChangesList
->end();
170 ExportChangedRegion( *aIter
);
173 // else: changes list empty -> ignore
175 // else: no changes list found -> empty
178 void XMLRedlineExport::SetCurrentXText(
179 const Reference
<XText
> & rText
)
183 // look for appropriate list in map; use the found one, or create new
184 ChangesMapType::iterator aIter
= aChangeMap
.find(rText
);
185 if (aIter
== aChangeMap
.end())
187 ChangesListType
* pList
= new ChangesListType
;
188 aChangeMap
[rText
] = pList
;
189 pCurrentChangesList
= pList
;
192 pCurrentChangesList
= aIter
->second
;
196 // don't record changes
201 void XMLRedlineExport::SetCurrentXText()
203 pCurrentChangesList
= NULL
;
207 void XMLRedlineExport::ExportChangesListElements()
209 // get redlines (aka tracked changes) from the model
210 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
213 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
215 // redline protection key
216 Reference
<XPropertySet
> aDocPropertySet( rExport
.GetModel(),
218 // redlining enabled?
219 sal_Bool bEnabled
= *(sal_Bool
*)aDocPropertySet
->getPropertyValue(
220 sRecordChanges
).getValue();
222 // only export if we have redlines or attributes
223 if ( aEnumAccess
->hasElements() || bEnabled
)
226 // export only if we have changes, but tracking is not enabled
227 if ( !bEnabled
!= !aEnumAccess
->hasElements() )
229 rExport
.AddAttribute(
230 XML_NAMESPACE_TEXT
, XML_TRACK_CHANGES
,
231 bEnabled
? XML_TRUE
: XML_FALSE
);
234 // changes container element
235 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
239 // get enumeration and iterate over elements
240 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
241 while (aEnum
->hasMoreElements())
243 Any aAny
= aEnum
->nextElement();
244 Reference
<XPropertySet
> xPropSet
;
247 DBG_ASSERT(xPropSet
.is(),
248 "can't get XPropertySet; skipping Redline");
251 // export only if not in header or footer
252 // (those must be exported with their XText)
253 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
254 if (! *(sal_Bool
*)aAny
.getValue())
256 // and finally, export change
257 ExportChangedRegion(xPropSet
);
260 // else: no XPropertySet -> no export
263 // else: no redlines -> no export
265 // else: no XRedlineSupplier -> no export
268 void XMLRedlineExport::ExportChangeAutoStyle(
269 const Reference
<XPropertySet
> & rPropSet
)
271 // record change (if changes should be recorded)
272 if (NULL
!= pCurrentChangesList
)
274 // put redline in list if it's collapsed or the redline start
275 Any aIsStart
= rPropSet
->getPropertyValue(sIsStart
);
276 Any aIsCollapsed
= rPropSet
->getPropertyValue(sIsCollapsed
);
278 if ( *(sal_Bool
*)aIsStart
.getValue() ||
279 *(sal_Bool
*)aIsCollapsed
.getValue() )
280 pCurrentChangesList
->push_back(rPropSet
);
283 // get XText for export of redline auto styles
284 Any aAny
= rPropSet
->getPropertyValue(sRedlineText
);
285 Reference
<XText
> xText
;
289 // export the auto styles
290 rExport
.GetTextParagraphExport()->collectTextAutoStyles(xText
);
294 void XMLRedlineExport::ExportChangesListAutoStyles()
296 // get redlines (aka tracked changes) from the model
297 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
300 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
302 // only export if we actually have redlines
303 if (aEnumAccess
->hasElements())
305 // get enumeration and iterate over elements
306 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
307 while (aEnum
->hasMoreElements())
309 Any aAny
= aEnum
->nextElement();
310 Reference
<XPropertySet
> xPropSet
;
313 DBG_ASSERT(xPropSet
.is(),
314 "can't get XPropertySet; skipping Redline");
318 // export only if not in header or footer
319 // (those must be exported with their XText)
320 aAny
= xPropSet
->getPropertyValue(sIsInHeaderFooter
);
321 if (! *(sal_Bool
*)aAny
.getValue())
323 ExportChangeAutoStyle(xPropSet
);
331 void XMLRedlineExport::ExportChangeInline(
332 const Reference
<XPropertySet
> & rPropSet
)
334 // determine element name (depending on collapsed, start/end)
335 enum XMLTokenEnum eElement
= XML_TOKEN_INVALID
;
336 Any aAny
= rPropSet
->getPropertyValue(sIsCollapsed
);
337 sal_Bool bCollapsed
= *(sal_Bool
*)aAny
.getValue();
338 sal_Bool bStart
= sal_True
; // ignored if bCollapsed = sal_True
341 eElement
= XML_CHANGE
;
345 aAny
= rPropSet
->getPropertyValue(sIsStart
);
346 bStart
= *(sal_Bool
*)aAny
.getValue();
347 eElement
= bStart
? XML_CHANGE_START
: XML_CHANGE_END
;
350 if (XML_TOKEN_INVALID
!= eElement
)
352 // we always need the ID
353 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
354 GetRedlineID(rPropSet
));
356 // export the element (no whitespace because we're in the text body)
357 SvXMLElementExport
aChangeElem(rExport
, XML_NAMESPACE_TEXT
,
358 eElement
, sal_False
, sal_False
);
363 void XMLRedlineExport::ExportChangedRegion(
364 const Reference
<XPropertySet
> & rPropSet
)
367 rExport
.AddAttributeIdLegacy(XML_NAMESPACE_TEXT
, GetRedlineID(rPropSet
));
369 // merge-last-paragraph
370 Any aAny
= rPropSet
->getPropertyValue(sMergeLastPara
);
371 if( ! *(sal_Bool
*)aAny
.getValue() )
372 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_MERGE_LAST_PARAGRAPH
,
375 // export change region element
376 SvXMLElementExport
aChangedRegion(rExport
, XML_NAMESPACE_TEXT
,
377 XML_CHANGED_REGION
, sal_True
, sal_True
);
380 // scope for (first) change element
382 aAny
= rPropSet
->getPropertyValue(sRedlineType
);
385 SvXMLElementExport
aChange(rExport
, XML_NAMESPACE_TEXT
,
386 ConvertTypeName(sType
), sal_True
, sal_True
);
388 ExportChangeInfo(rPropSet
);
390 // get XText from the redline and export (if the XText exists)
391 aAny
= rPropSet
->getPropertyValue(sRedlineText
);
392 Reference
<XText
> xText
;
396 rExport
.GetTextParagraphExport()->exportText(xText
);
397 // default parameters: bProgress, bExportParagraph ???
399 // else: no text interface -> content is inline and will
403 // changed change? Hierarchical changes can onl be two levels
404 // deep. Here we check for the second level.
405 aAny
= rPropSet
->getPropertyValue(sRedlineSuccessorData
);
406 Sequence
<PropertyValue
> aSuccessorData
;
407 aAny
>>= aSuccessorData
;
409 // if we actually got a hierarchical change, make element and
410 // process change info
411 if (aSuccessorData
.getLength() > 0)
413 // The only change that can be "undone" is an insertion -
414 // after all, you can't re-insert an deletion, but you can
415 // delete an insertion. This assumption is asserted in
416 // ExportChangeInfo(Sequence<PropertyValue>&).
417 SvXMLElementExport
aSecondChangeElem(
418 rExport
, XML_NAMESPACE_TEXT
, XML_INSERTION
,
421 ExportChangeInfo(aSuccessorData
);
423 // else: no hierarchical change
427 const OUString
XMLRedlineExport::ConvertTypeName(
428 const OUString
& sApiName
)
430 if (sApiName
== sDelete
)
434 else if (sApiName
== sInsert
)
438 else if (sApiName
== sFormat
)
440 return sFormatChange
;
444 OSL_FAIL("unknown redline type");
445 return sUnknownChange
;
450 /** Create a Redline-ID */
451 const OUString
XMLRedlineExport::GetRedlineID(
452 const Reference
<XPropertySet
> & rPropSet
)
454 Any aAny
= rPropSet
->getPropertyValue(sRedlineIdentifier
);
458 OUStringBuffer
sBuf(sChangePrefix
);
460 return sBuf
.makeStringAndClear();
464 void XMLRedlineExport::ExportChangeInfo(
465 const Reference
<XPropertySet
> & rPropSet
)
468 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
469 XML_CHANGE_INFO
, sal_True
, sal_True
);
471 Any aAny
= rPropSet
->getPropertyValue(sRedlineAuthor
);
476 SvXMLElementExport
aCreatorElem( rExport
, XML_NAMESPACE_DC
,
477 XML_CREATOR
, sal_True
,
479 rExport
.Characters(sTmp
);
482 aAny
= rPropSet
->getPropertyValue(sRedlineDateTime
);
483 util::DateTime aDateTime
;
487 ::sax::Converter::convertDateTime(sBuf
, aDateTime
);
488 SvXMLElementExport
aDateElem( rExport
, XML_NAMESPACE_DC
,
491 rExport
.Characters(sBuf
.makeStringAndClear());
494 // comment as <text:p> sequence
495 aAny
= rPropSet
->getPropertyValue(sRedlineComment
);
497 WriteComment( sTmp
);
500 void XMLRedlineExport::ExportChangeInfo(
501 const Sequence
<PropertyValue
> & rPropertyValues
)
505 sal_Int32 nCount
= rPropertyValues
.getLength();
506 for(sal_Int32 i
= 0; i
< nCount
; i
++)
508 const PropertyValue
& rVal
= rPropertyValues
[i
];
510 if( rVal
.Name
.equals(sRedlineAuthor
) )
516 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_AUTHOR
, sTmp
);
519 else if( rVal
.Name
.equals(sRedlineComment
) )
521 rVal
.Value
>>= sComment
;
523 else if( rVal
.Name
.equals(sRedlineDateTime
) )
525 util::DateTime aDateTime
;
526 rVal
.Value
>>= aDateTime
;
528 ::sax::Converter::convertDateTime(sBuf
, aDateTime
);
529 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_DATE_TIME
,
530 sBuf
.makeStringAndClear());
532 else if( rVal
.Name
.equals(sRedlineType
) )
534 // check if this is an insertion; cf. comment at calling location
537 DBG_ASSERT(sTmp
.equals(sInsert
),
538 "hierarchical change must be insertion");
540 // else: unknown value -> ignore
543 // finally write element
544 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
545 XML_CHANGE_INFO
, sal_True
, sal_True
);
547 WriteComment( sComment
);
550 void XMLRedlineExport::ExportStartOrEndRedline(
551 const Reference
<XPropertySet
> & rPropSet
,
554 if( ! rPropSet
.is() )
557 // get appropriate (start or end) property
561 aAny
= rPropSet
->getPropertyValue(bStart
? sStartRedline
: sEndRedline
);
563 catch(const UnknownPropertyException
&)
565 // If we don't have the property, there's nothing to do.
569 Sequence
<PropertyValue
> aValues
;
571 const PropertyValue
* pValues
= aValues
.getConstArray();
573 // seek for redline properties
574 sal_Bool bIsCollapsed
= sal_False
;
575 sal_Bool bIsStart
= sal_True
;
577 sal_Bool bIdOK
= sal_False
; // have we seen an ID?
578 sal_Int32 nLength
= aValues
.getLength();
579 for(sal_Int32 i
= 0; i
< nLength
; i
++)
581 if (sRedlineIdentifier
.equals(pValues
[i
].Name
))
583 pValues
[i
].Value
>>= sId
;
586 else if (sIsCollapsed
.equals(pValues
[i
].Name
))
588 bIsCollapsed
= *(sal_Bool
*)pValues
[i
].Value
.getValue();
590 else if (sIsStart
.equals(pValues
[i
].Name
))
592 bIsStart
= *(sal_Bool
*)pValues
[i
].Value
.getValue();
598 DBG_ASSERT( !sId
.isEmpty(), "Redlines must have IDs" );
600 // TODO: use GetRedlineID or elimiate that function
601 OUStringBuffer
sBuffer(sChangePrefix
);
604 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
605 sBuffer
.makeStringAndClear());
607 // export the element
608 // (whitespace because we're not inside paragraphs)
609 SvXMLElementExport
aChangeElem(
610 rExport
, XML_NAMESPACE_TEXT
,
611 bIsCollapsed
? XML_CHANGE
:
612 ( bIsStart
? XML_CHANGE_START
: XML_CHANGE_END
),
617 void XMLRedlineExport::ExportStartOrEndRedline(
618 const Reference
<XTextContent
> & rContent
,
621 Reference
<XPropertySet
> xPropSet(rContent
, uno::UNO_QUERY
);
624 ExportStartOrEndRedline(xPropSet
, bStart
);
628 OSL_FAIL("XPropertySet expected");
632 void XMLRedlineExport::ExportStartOrEndRedline(
633 const Reference
<XTextSection
> & rSection
,
636 Reference
<XPropertySet
> xPropSet(rSection
, uno::UNO_QUERY
);
639 ExportStartOrEndRedline(xPropSet
, bStart
);
643 OSL_FAIL("XPropertySet expected");
647 void XMLRedlineExport::WriteComment(const OUString
& rComment
)
649 if (!rComment
.isEmpty())
651 // iterate over all string-pieces separated by return (0x0a) and
652 // put each inside a paragraph element.
653 SvXMLTokenEnumerator
aEnumerator(rComment
, sal_Char(0x0a));
655 while (aEnumerator
.getNextToken(aSubString
))
657 SvXMLElementExport
aParagraph(
658 rExport
, XML_NAMESPACE_TEXT
, XML_P
, sal_True
, sal_False
);
659 rExport
.Characters(aSubString
);
664 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */