nss: upgrade to release 3.73
[LibreOffice.git] / xmloff / source / text / XMLRedlineExport.cxx
blob43ced7d6e1d8fb63acfb9cb9f131258d128252d7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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))
68 , rExport(rExp)
69 , pCurrentChangesList(nullptr)
74 XMLRedlineExport::~XMLRedlineExport()
79 void XMLRedlineExport::ExportChange(
80 const Reference<XPropertySet> & rPropSet,
81 bool bAutoStyle)
83 if (bAutoStyle)
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);
95 else
97 ExportChangeInline(rPropSet);
102 void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
104 if (bAutoStyles)
106 ExportChangesListAutoStyles();
108 else
110 ExportChangesListElements();
115 void XMLRedlineExport::ExportChangesList(
116 const Reference<XText> & rText,
117 bool bAutoStyles)
119 // in the header/footer case, auto styles are collected from the
120 // inline change elements.
121 if (bAutoStyles)
122 return;
124 // look for changes list for this XText
125 ChangesMapType::iterator aFind = aChangeMap.find(rText);
126 if (aFind == aChangeMap.end())
127 return;
129 ChangesVectorType* pChangesList = aFind->second.get();
131 // export only if changes are found
132 if (pChangesList->empty())
133 return;
135 // changes container element
136 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
137 XML_TRACKED_CHANGES,
138 true, true);
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)
152 if (rText.is())
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;
162 else
163 pCurrentChangesList = aIter->second.get();
165 else
167 // don't record changes
168 SetCurrentXText();
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);
182 if (!xSupplier.is())
183 return;
185 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
187 // redline protection key
188 Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
189 uno::UNO_QUERY );
190 // redlining enabled?
191 bool bEnabled = *o3tl::doAccess<bool>(aDocPropertySet->getPropertyValue(
192 "RecordChanges" ));
194 // only export if we have redlines or attributes
195 if ( !(aEnumAccess->hasElements() || bEnabled) )
196 return;
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,
209 XML_TRACKED_CHANGES,
210 true, true);
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;
218 aAny >>= xPropSet;
220 DBG_ASSERT(xPropSet.is(),
221 "can't get XPropertySet; skipping Redline");
222 if (xPropSet.is())
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;
257 aAny >>= xText;
258 if (xText.is())
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);
269 if (!xSupplier.is())
270 return;
272 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
274 // only export if we actually have redlines
275 if (!aEnumAccess->hasElements())
276 return;
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;
284 aAny >>= xPropSet;
286 DBG_ASSERT(xPropSet.is(),
287 "can't get XPropertySet; skipping Redline");
288 if (xPropSet.is())
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);
309 if (bCollapsed)
311 eElement = XML_CHANGE;
313 else
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)
336 // Redline-ID
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,
343 XML_FALSE);
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");
353 OUString sType;
354 aAny >>= sType;
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;
363 aAny >>= xText;
364 if (xText.is())
366 rExport.GetTextParagraphExport()->exportText(xText);
367 // default parameters: bProgress, bExportParagraph ???
369 // else: no text interface -> content is inline and will
370 // be exported there
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,
389 true, true);
391 ExportChangeInfo(aSuccessorData);
393 // else: no hierarchical change
397 OUString const & XMLRedlineExport::ConvertTypeName(
398 const OUString& sApiName)
400 if (sApiName == "Delete")
402 return sDeletion;
404 else if (sApiName == "Insert")
406 return sInsertion;
408 else if (sApiName == "Format")
410 return sFormatChange;
412 else
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");
426 OUString sTmp;
427 aAny >>= sTmp;
429 return "ct" + sTmp;
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");
441 OUString sTmp;
442 aAny >>= sTmp;
443 if (!sTmp.isEmpty())
445 SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
446 XML_CREATOR, true,
447 false );
448 rExport.Characters(sTmp);
451 aAny = rPropSet->getPropertyValue("RedlineDateTime");
452 util::DateTime aDateTime;
453 aAny >>= aDateTime;
455 OUStringBuffer sBuf;
456 ::sax::Converter::convertDateTime(sBuf, aDateTime, nullptr);
457 SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
458 XML_DATE, true,
459 false );
460 rExport.Characters(sBuf.makeStringAndClear());
463 // comment as <text:p> sequence
464 aAny = rPropSet->getPropertyValue("RedlineComment");
465 aAny >>= sTmp;
466 WriteComment( sTmp );
469 void XMLRedlineExport::ExportChangeInfo(
470 const Sequence<PropertyValue> & rPropertyValues)
472 OUString sComment;
474 for(const PropertyValue& rVal : rPropertyValues)
476 if( rVal.Name == "RedlineAuthor" )
478 OUString sTmp;
479 rVal.Value >>= sTmp;
480 if (!sTmp.isEmpty())
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;
493 OUStringBuffer sBuf;
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
501 OUString sTmp;
502 rVal.Value >>= sTmp;
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,
518 bool bStart)
520 if( ! rPropSet.is() )
521 return;
523 // get appropriate (start or end) property
524 Any aAny;
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.
532 return;
535 Sequence<PropertyValue> aValues;
536 aAny >>= aValues;
538 // seek for redline properties
539 bool bIsCollapsed = false;
540 bool bIsStart = true;
541 OUString sId;
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;
548 bIdOK = true;
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);
560 if( !bIdOK )
561 return;
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,
567 "ct" + sId);
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 ),
575 true, true);
578 void XMLRedlineExport::ExportStartOrEndRedline(
579 const Reference<XTextContent> & rContent,
580 bool bStart)
582 Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
583 if (xPropSet.is())
585 ExportStartOrEndRedline(xPropSet, bStart);
587 else
589 OSL_FAIL("XPropertySet expected");
593 void XMLRedlineExport::ExportStartOrEndRedline(
594 const Reference<XTextSection> & rSection,
595 bool bStart)
597 Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
598 if (xPropSet.is())
600 ExportStartOrEndRedline(xPropSet, bStart);
602 else
604 OSL_FAIL("XPropertySet expected");
608 void XMLRedlineExport::WriteComment(const OUString& rComment)
610 if (rComment.isEmpty())
611 return;
613 // iterate over all string-pieces separated by return (0x0a) and
614 // put each inside a paragraph element.
615 SvXMLTokenEnumerator aEnumerator(rComment, char(0x0a));
616 OUString aSubString;
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: */