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 <o3tl/any.hxx>
22 #include <tools/debug.hxx>
23 #include <rtl/ustring.hxx>
24 #include <rtl/ustrbuf.hxx>
25 #include <sal/log.hxx>
26 #include <osl/diagnose.h>
27 #include <com/sun/star/frame/XModel.hpp>
28 #include <com/sun/star/beans/XPropertySet.hpp>
29 #include <com/sun/star/beans/UnknownPropertyException.hpp>
30 #include <com/sun/star/container/XEnumerationAccess.hpp>
32 #include <com/sun/star/container/XEnumeration.hpp>
33 #include <com/sun/star/document/XRedlinesSupplier.hpp>
34 #include <com/sun/star/text/XText.hpp>
35 #include <com/sun/star/text/XTextContent.hpp>
36 #include <com/sun/star/text/XTextSection.hpp>
37 #include <com/sun/star/util/DateTime.hpp>
39 #include <sax/tools/converter.hxx>
41 #include <xmloff/xmltoken.hxx>
42 #include <xmloff/xmlnamespace.hxx>
43 #include <xmloff/xmlexp.hxx>
44 #include <xmloff/xmluconv.hxx>
47 using namespace ::com::sun::star
;
48 using namespace ::xmloff::token
;
50 using ::com::sun::star::beans::PropertyValue
;
51 using ::com::sun::star::beans::XPropertySet
;
52 using ::com::sun::star::beans::UnknownPropertyException
;
53 using ::com::sun::star::document::XRedlinesSupplier
;
54 using ::com::sun::star::container::XEnumerationAccess
;
55 using ::com::sun::star::container::XEnumeration
;
56 using ::com::sun::star::text::XText
;
57 using ::com::sun::star::text::XTextContent
;
58 using ::com::sun::star::text::XTextSection
;
59 using ::com::sun::star::uno::Any
;
60 using ::com::sun::star::uno::Reference
;
61 using ::com::sun::star::uno::Sequence
;
64 XMLRedlineExport::XMLRedlineExport(SvXMLExport
& rExp
)
65 : sDeletion(GetXMLToken(XML_DELETION
))
66 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE
))
67 , sInsertion(GetXMLToken(XML_INSERTION
))
69 , pCurrentChangesList(nullptr)
74 XMLRedlineExport::~XMLRedlineExport()
79 void XMLRedlineExport::ExportChange(
80 const Reference
<XPropertySet
> & rPropSet
,
85 // For the headers/footers, we have to collect the autostyles
86 // here. For the general case, however, it's better to collect
87 // the autostyles by iterating over the global redline
88 // list. So that's what we do: Here, we collect autostyles
89 // only if we have no current list of changes. For the
90 // main-document case, the autostyles are collected in
91 // ExportChangesListAutoStyles().
92 if (pCurrentChangesList
!= nullptr)
93 ExportChangeAutoStyle(rPropSet
);
97 ExportChangeInline(rPropSet
);
102 void XMLRedlineExport::ExportChangesList(bool bAutoStyles
)
106 ExportChangesListAutoStyles();
110 ExportChangesListElements();
115 void XMLRedlineExport::ExportChangesList(
116 const Reference
<XText
> & rText
,
119 // in the header/footer case, auto styles are collected from the
120 // inline change elements.
124 // look for changes list for this XText
125 ChangesMapType::iterator aFind
= aChangeMap
.find(rText
);
126 if (aFind
== aChangeMap
.end())
129 ChangesVectorType
* pChangesList
= aFind
->second
.get();
131 // export only if changes are found
132 if (pChangesList
->empty())
135 // changes container element
136 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
140 // iterate over changes list
141 for (auto const& change
: *pChangesList
)
143 ExportChangedRegion(change
);
145 // else: changes list empty -> ignore
146 // else: no changes list found -> empty
149 void XMLRedlineExport::SetCurrentXText(
150 const Reference
<XText
> & rText
)
154 // look for appropriate list in map; use the found one, or create new
155 ChangesMapType::iterator aIter
= aChangeMap
.find(rText
);
156 if (aIter
== aChangeMap
.end())
158 ChangesVectorType
* pList
= new ChangesVectorType
;
159 aChangeMap
[rText
].reset( pList
);
160 pCurrentChangesList
= pList
;
163 pCurrentChangesList
= aIter
->second
.get();
167 // don't record changes
172 void XMLRedlineExport::SetCurrentXText()
174 pCurrentChangesList
= nullptr;
178 void XMLRedlineExport::ExportChangesListElements()
180 // get redlines (aka tracked changes) from the model
181 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
185 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
187 // redline protection key
188 Reference
<XPropertySet
> aDocPropertySet( rExport
.GetModel(),
190 // redlining enabled?
191 bool bEnabled
= *o3tl::doAccess
<bool>(aDocPropertySet
->getPropertyValue(
194 // only export if we have redlines or attributes
195 if ( !(aEnumAccess
->hasElements() || bEnabled
) )
199 // export only if we have changes, but tracking is not enabled
200 if ( !bEnabled
!= !aEnumAccess
->hasElements() )
202 rExport
.AddAttribute(
203 XML_NAMESPACE_TEXT
, XML_TRACK_CHANGES
,
204 bEnabled
? XML_TRUE
: XML_FALSE
);
207 // changes container element
208 SvXMLElementExport
aChanges(rExport
, XML_NAMESPACE_TEXT
,
212 // get enumeration and iterate over elements
213 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
214 while (aEnum
->hasMoreElements())
216 Any aAny
= aEnum
->nextElement();
217 Reference
<XPropertySet
> xPropSet
;
220 DBG_ASSERT(xPropSet
.is(),
221 "can't get XPropertySet; skipping Redline");
224 // export only if not in header or footer
225 // (those must be exported with their XText)
226 aAny
= xPropSet
->getPropertyValue("IsInHeaderFooter");
227 if (! *o3tl::doAccess
<bool>(aAny
))
229 // and finally, export change
230 ExportChangedRegion(xPropSet
);
233 // else: no XPropertySet -> no export
235 // else: no redlines -> no export
236 // else: no XRedlineSupplier -> no export
239 void XMLRedlineExport::ExportChangeAutoStyle(
240 const Reference
<XPropertySet
> & rPropSet
)
242 // record change (if changes should be recorded)
243 if (nullptr != pCurrentChangesList
)
245 // put redline in list if it's collapsed or the redline start
246 Any aIsStart
= rPropSet
->getPropertyValue("IsStart");
247 Any aIsCollapsed
= rPropSet
->getPropertyValue("IsCollapsed");
249 if ( *o3tl::doAccess
<bool>(aIsStart
) ||
250 *o3tl::doAccess
<bool>(aIsCollapsed
) )
251 pCurrentChangesList
->push_back(rPropSet
);
254 // get XText for export of redline auto styles
255 Any aAny
= rPropSet
->getPropertyValue("RedlineText");
256 Reference
<XText
> xText
;
260 // export the auto styles
261 rExport
.GetTextParagraphExport()->collectTextAutoStyles(xText
);
265 void XMLRedlineExport::ExportChangesListAutoStyles()
267 // get redlines (aka tracked changes) from the model
268 Reference
<XRedlinesSupplier
> xSupplier(rExport
.GetModel(), uno::UNO_QUERY
);
272 Reference
<XEnumerationAccess
> aEnumAccess
= xSupplier
->getRedlines();
274 // only export if we actually have redlines
275 if (!aEnumAccess
->hasElements())
278 // get enumeration and iterate over elements
279 Reference
<XEnumeration
> aEnum
= aEnumAccess
->createEnumeration();
280 while (aEnum
->hasMoreElements())
282 Any aAny
= aEnum
->nextElement();
283 Reference
<XPropertySet
> xPropSet
;
286 DBG_ASSERT(xPropSet
.is(),
287 "can't get XPropertySet; skipping Redline");
291 // export only if not in header or footer
292 // (those must be exported with their XText)
293 aAny
= xPropSet
->getPropertyValue("IsInHeaderFooter");
294 if (! *o3tl::doAccess
<bool>(aAny
))
296 ExportChangeAutoStyle(xPropSet
);
302 void XMLRedlineExport::ExportChangeInline(
303 const Reference
<XPropertySet
> & rPropSet
)
305 // determine element name (depending on collapsed, start/end)
306 enum XMLTokenEnum eElement
= XML_TOKEN_INVALID
;
307 Any aAny
= rPropSet
->getPropertyValue("IsCollapsed");
308 bool bCollapsed
= *o3tl::doAccess
<bool>(aAny
);
311 eElement
= XML_CHANGE
;
315 aAny
= rPropSet
->getPropertyValue("IsStart");
316 const bool bStart
= *o3tl::doAccess
<bool>(aAny
);
317 eElement
= bStart
? XML_CHANGE_START
: XML_CHANGE_END
;
320 if (XML_TOKEN_INVALID
!= eElement
)
322 // we always need the ID
323 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
324 GetRedlineID(rPropSet
));
326 // export the element (no whitespace because we're in the text body)
327 SvXMLElementExport
aChangeElem(rExport
, XML_NAMESPACE_TEXT
,
328 eElement
, false, false);
333 void XMLRedlineExport::ExportChangedRegion(
334 const Reference
<XPropertySet
> & rPropSet
)
337 rExport
.AddAttributeIdLegacy(XML_NAMESPACE_TEXT
, GetRedlineID(rPropSet
));
339 // merge-last-paragraph
340 Any aAny
= rPropSet
->getPropertyValue("MergeLastPara");
341 if( ! *o3tl::doAccess
<bool>(aAny
) )
342 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_MERGE_LAST_PARAGRAPH
,
345 // export change region element
346 SvXMLElementExport
aChangedRegion(rExport
, XML_NAMESPACE_TEXT
,
347 XML_CHANGED_REGION
, true, true);
350 // scope for (first) change element
352 aAny
= rPropSet
->getPropertyValue("RedlineType");
355 SvXMLElementExport
aChange(rExport
, XML_NAMESPACE_TEXT
,
356 ConvertTypeName(sType
), true, true);
358 ExportChangeInfo(rPropSet
);
360 // get XText from the redline and export (if the XText exists)
361 aAny
= rPropSet
->getPropertyValue("RedlineText");
362 Reference
<XText
> xText
;
366 rExport
.GetTextParagraphExport()->exportText(xText
);
367 // default parameters: bProgress, bExportParagraph ???
369 // else: no text interface -> content is inline and will
373 // changed change? Hierarchical changes can only be two levels
374 // deep. Here we check for the second level.
375 aAny
= rPropSet
->getPropertyValue("RedlineSuccessorData");
376 Sequence
<PropertyValue
> aSuccessorData
;
377 aAny
>>= aSuccessorData
;
379 // if we actually got a hierarchical change, make element and
380 // process change info
381 if (aSuccessorData
.hasElements())
383 // The only change that can be "undone" is an insertion -
384 // after all, you can't re-insert a deletion, but you can
385 // delete an insertion. This assumption is asserted in
386 // ExportChangeInfo(Sequence<PropertyValue>&).
387 SvXMLElementExport
aSecondChangeElem(
388 rExport
, XML_NAMESPACE_TEXT
, XML_INSERTION
,
391 ExportChangeInfo(aSuccessorData
);
393 // else: no hierarchical change
397 OUString
const & XMLRedlineExport::ConvertTypeName(
398 const OUString
& sApiName
)
400 if (sApiName
== "Delete")
404 else if (sApiName
== "Insert")
408 else if (sApiName
== "Format")
410 return sFormatChange
;
414 OSL_FAIL("unknown redline type");
415 static const OUString
sUnknownChange("UnknownChange");
416 return sUnknownChange
;
421 /** Create a Redline-ID */
422 OUString
XMLRedlineExport::GetRedlineID(
423 const Reference
<XPropertySet
> & rPropSet
)
425 Any aAny
= rPropSet
->getPropertyValue("RedlineIdentifier");
433 void XMLRedlineExport::ExportChangeInfo(
434 const Reference
<XPropertySet
> & rPropSet
)
437 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
438 XML_CHANGE_INFO
, true, true);
440 Any aAny
= rPropSet
->getPropertyValue("RedlineAuthor");
445 SvXMLElementExport
aCreatorElem( rExport
, XML_NAMESPACE_DC
,
448 rExport
.Characters(sTmp
);
451 aAny
= rPropSet
->getPropertyValue("RedlineDateTime");
452 util::DateTime aDateTime
;
456 ::sax::Converter::convertDateTime(sBuf
, aDateTime
, nullptr);
457 SvXMLElementExport
aDateElem( rExport
, XML_NAMESPACE_DC
,
460 rExport
.Characters(sBuf
.makeStringAndClear());
463 // comment as <text:p> sequence
464 aAny
= rPropSet
->getPropertyValue("RedlineComment");
466 WriteComment( sTmp
);
469 void XMLRedlineExport::ExportChangeInfo(
470 const Sequence
<PropertyValue
> & rPropertyValues
)
474 for(const PropertyValue
& rVal
: rPropertyValues
)
476 if( rVal
.Name
== "RedlineAuthor" )
482 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_AUTHOR
, sTmp
);
485 else if( rVal
.Name
== "RedlineComment" )
487 rVal
.Value
>>= sComment
;
489 else if( rVal
.Name
== "RedlineDateTime" )
491 util::DateTime aDateTime
;
492 rVal
.Value
>>= aDateTime
;
494 ::sax::Converter::convertDateTime(sBuf
, aDateTime
, nullptr);
495 rExport
.AddAttribute(XML_NAMESPACE_OFFICE
, XML_CHG_DATE_TIME
,
496 sBuf
.makeStringAndClear());
498 else if( rVal
.Name
== "RedlineType" )
500 // check if this is an insertion; cf. comment at calling location
503 DBG_ASSERT(sTmp
== "Insert",
504 "hierarchical change must be insertion");
506 // else: unknown value -> ignore
509 // finally write element
510 SvXMLElementExport
aChangeInfo(rExport
, XML_NAMESPACE_OFFICE
,
511 XML_CHANGE_INFO
, true, true);
513 WriteComment( sComment
);
516 void XMLRedlineExport::ExportStartOrEndRedline(
517 const Reference
<XPropertySet
> & rPropSet
,
520 if( ! rPropSet
.is() )
523 // get appropriate (start or end) property
527 aAny
= rPropSet
->getPropertyValue(bStart
? OUString("StartRedline") : OUString("EndRedline"));
529 catch(const UnknownPropertyException
&)
531 // If we don't have the property, there's nothing to do.
535 Sequence
<PropertyValue
> aValues
;
538 // seek for redline properties
539 bool bIsCollapsed
= false;
540 bool bIsStart
= true;
542 bool bIdOK
= false; // have we seen an ID?
543 for(const auto& rValue
: std::as_const(aValues
))
545 if (rValue
.Name
== "RedlineIdentifier")
547 rValue
.Value
>>= sId
;
550 else if (rValue
.Name
== "IsCollapsed")
552 bIsCollapsed
= *o3tl::doAccess
<bool>(rValue
.Value
);
554 else if (rValue
.Name
== "IsStart")
556 bIsStart
= *o3tl::doAccess
<bool>(rValue
.Value
);
563 SAL_WARN_IF( sId
.isEmpty(), "xmloff", "Redlines must have IDs" );
565 // TODO: use GetRedlineID or eliminate that function
566 rExport
.AddAttribute(XML_NAMESPACE_TEXT
, XML_CHANGE_ID
,
569 // export the element
570 // (whitespace because we're not inside paragraphs)
571 SvXMLElementExport
aChangeElem(
572 rExport
, XML_NAMESPACE_TEXT
,
573 bIsCollapsed
? XML_CHANGE
:
574 ( bIsStart
? XML_CHANGE_START
: XML_CHANGE_END
),
578 void XMLRedlineExport::ExportStartOrEndRedline(
579 const Reference
<XTextContent
> & rContent
,
582 Reference
<XPropertySet
> xPropSet(rContent
, uno::UNO_QUERY
);
585 ExportStartOrEndRedline(xPropSet
, bStart
);
589 OSL_FAIL("XPropertySet expected");
593 void XMLRedlineExport::ExportStartOrEndRedline(
594 const Reference
<XTextSection
> & rSection
,
597 Reference
<XPropertySet
> xPropSet(rSection
, uno::UNO_QUERY
);
600 ExportStartOrEndRedline(xPropSet
, bStart
);
604 OSL_FAIL("XPropertySet expected");
608 void XMLRedlineExport::WriteComment(const OUString
& rComment
)
610 if (rComment
.isEmpty())
613 // iterate over all string-pieces separated by return (0x0a) and
614 // put each inside a paragraph element.
615 SvXMLTokenEnumerator
aEnumerator(rComment
, char(0x0a));
617 while (aEnumerator
.getNextToken(aSubString
))
619 SvXMLElementExport
aParagraph(
620 rExport
, XML_NAMESPACE_TEXT
, XML_P
, true, false);
621 rExport
.Characters(aSubString
);
625 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */