tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / xmloff / source / text / XMLRedlineExport.cxx
blob093040c3b15b9d9a0da6c49755e4aabab34088f0
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>
45 #include <unotools/securityoptions.hxx>
48 using namespace ::com::sun::star;
49 using namespace ::xmloff::token;
51 using ::com::sun::star::beans::PropertyValue;
52 using ::com::sun::star::beans::XPropertySet;
53 using ::com::sun::star::beans::UnknownPropertyException;
54 using ::com::sun::star::document::XRedlinesSupplier;
55 using ::com::sun::star::container::XEnumerationAccess;
56 using ::com::sun::star::container::XEnumeration;
57 using ::com::sun::star::text::XText;
58 using ::com::sun::star::text::XTextContent;
59 using ::com::sun::star::text::XTextSection;
60 using ::com::sun::star::uno::Any;
61 using ::com::sun::star::uno::Reference;
62 using ::com::sun::star::uno::Sequence;
65 XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
66 : sDeletion(GetXMLToken(XML_DELETION))
67 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
68 , sInsertion(GetXMLToken(XML_INSERTION))
69 , rExport(rExp)
70 , pCurrentChangesList(nullptr)
75 XMLRedlineExport::~XMLRedlineExport()
80 void XMLRedlineExport::ExportChange(
81 const Reference<XPropertySet> & rPropSet,
82 bool bAutoStyle)
84 if (bAutoStyle)
86 // For the headers/footers, we have to collect the autostyles
87 // here. For the general case, however, it's better to collect
88 // the autostyles by iterating over the global redline
89 // list. So that's what we do: Here, we collect autostyles
90 // only if we have no current list of changes. For the
91 // main-document case, the autostyles are collected in
92 // ExportChangesListAutoStyles().
93 if (pCurrentChangesList != nullptr)
94 ExportChangeAutoStyle(rPropSet);
96 else
98 ExportChangeInline(rPropSet);
103 void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
105 if (bAutoStyles)
107 ExportChangesListAutoStyles();
109 else
111 ExportChangesListElements();
116 void XMLRedlineExport::ExportChangesList(
117 const Reference<XText> & rText,
118 bool bAutoStyles)
120 // in the header/footer case, auto styles are collected from the
121 // inline change elements.
122 if (bAutoStyles)
123 return;
125 // look for changes list for this XText
126 ChangesMapType::iterator aFind = aChangeMap.find(rText);
127 if (aFind == aChangeMap.end())
128 return;
130 ChangesVectorType& rChangesList = aFind->second;
132 // export only if changes are found
133 if (rChangesList.empty())
134 return;
136 // changes container element
137 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
138 XML_TRACKED_CHANGES,
139 true, true);
141 // iterate over changes list
142 for (auto const& change : rChangesList)
144 ExportChangedRegion(change);
146 // else: changes list empty -> ignore
147 // else: no changes list found -> empty
150 void XMLRedlineExport::SetCurrentXText(
151 const Reference<XText> & rText)
153 if (rText.is())
155 // look for appropriate list in map; use the found one, or create new
156 ChangesMapType::iterator aIter = aChangeMap.find(rText);
157 if (aIter == aChangeMap.end())
159 auto rv = aChangeMap.emplace(std::piecewise_construct, std::forward_as_tuple(rText), std::forward_as_tuple());
160 pCurrentChangesList = &rv.first->second;
162 else
163 pCurrentChangesList = &aIter->second;
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 u"RecordChanges"_ustr ));
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(u"IsInHeaderFooter"_ustr);
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(u"IsStart"_ustr);
247 Any aIsCollapsed = rPropSet->getPropertyValue(u"IsCollapsed"_ustr);
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(u"RedlineText"_ustr);
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(u"IsInHeaderFooter"_ustr);
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(u"IsCollapsed"_ustr);
308 bool bCollapsed = *o3tl::doAccess<bool>(aAny);
309 if (bCollapsed)
311 eElement = XML_CHANGE;
313 else
315 aAny = rPropSet->getPropertyValue(u"IsStart"_ustr);
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(u"MergeLastPara"_ustr);
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(u"RedlineType"_ustr);
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(u"RedlineText"_ustr);
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(u"RedlineSuccessorData"_ustr);
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 std::u16string_view sApiName)
400 if (sApiName == u"Delete")
402 return sDeletion;
404 else if (sApiName == u"Insert")
406 return sInsertion;
408 else if (sApiName == u"Format")
410 return sFormatChange;
412 else
414 OSL_FAIL("unknown redline type");
415 static constexpr OUString sUnknownChange(u"UnknownChange"_ustr);
416 return sUnknownChange;
421 /** Create a Redline-ID */
422 OUString XMLRedlineExport::GetRedlineID(
423 const Reference<XPropertySet> & rPropSet)
425 Any aAny = rPropSet->getPropertyValue(u"RedlineIdentifier"_ustr);
426 OUString sTmp;
427 aAny >>= sTmp;
429 return "ct" + sTmp;
433 void XMLRedlineExport::ExportChangeInfo(
434 const Reference<XPropertySet> & rPropSet)
436 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
437 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
438 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
440 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
441 XML_CHANGE_INFO, true, true);
443 Any aAny = rPropSet->getPropertyValue(u"RedlineAuthor"_ustr);
444 OUString sTmp;
445 aAny >>= sTmp;
446 if (!sTmp.isEmpty())
448 SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
449 XML_CREATOR, true,
450 false );
451 rExport.Characters(bRemovePersonalInfo
452 ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
453 : sTmp );
456 aAny = rPropSet->getPropertyValue(u"RedlineMovedID"_ustr);
457 sal_uInt32 nTmp(0);
458 aAny >>= nTmp;
459 if (nTmp > 1)
461 SvXMLElementExport aCreatorElem(rExport, XML_NAMESPACE_LO_EXT, XML_MOVE_ID, true, false);
462 rExport.Characters( OUString::number( nTmp ) );
465 aAny = rPropSet->getPropertyValue(u"RedlineDateTime"_ustr);
466 util::DateTime aDateTime;
467 aAny >>= aDateTime;
469 OUStringBuffer sBuf;
470 ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
471 ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
472 : aDateTime, nullptr, true);
473 SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
474 XML_DATE, true,
475 false );
476 rExport.Characters(sBuf.makeStringAndClear());
479 // comment as <text:p> sequence
480 aAny = rPropSet->getPropertyValue(u"RedlineComment"_ustr);
481 aAny >>= sTmp;
482 WriteComment( sTmp );
485 // write RedlineSuccessorData
486 void XMLRedlineExport::ExportChangeInfo(
487 const Sequence<PropertyValue> & rPropertyValues)
489 OUString sComment;
490 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
491 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
492 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
494 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
495 XML_CHANGE_INFO, true, true);
497 for(const PropertyValue& rVal : rPropertyValues)
499 if( rVal.Name == "RedlineAuthor" )
501 OUString sTmp;
502 rVal.Value >>= sTmp;
503 if (!sTmp.isEmpty())
505 SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
506 XML_CREATOR, true,
507 false );
508 rExport.Characters(bRemovePersonalInfo
509 ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
510 : sTmp );
513 else if( rVal.Name == "RedlineComment" )
515 rVal.Value >>= sComment;
517 else if( rVal.Name == "RedlineDateTime" )
519 util::DateTime aDateTime;
520 rVal.Value >>= aDateTime;
521 OUStringBuffer sBuf;
522 ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
523 ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
524 : aDateTime, nullptr, true);
525 SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
526 XML_DATE, true,
527 false );
528 rExport.Characters(sBuf.makeStringAndClear());
530 else if( rVal.Name == "RedlineType" )
532 // check if this is an insertion; cf. comment at calling location
533 OUString sTmp;
534 rVal.Value >>= sTmp;
535 DBG_ASSERT(sTmp == "Insert",
536 "hierarchical change must be insertion");
538 // else: unknown value -> ignore
541 // finally write comment paragraphs
542 WriteComment( sComment );
545 void XMLRedlineExport::ExportStartOrEndRedline(
546 const Reference<XPropertySet> & rPropSet,
547 bool bStart)
549 if( ! rPropSet.is() )
550 return;
552 // get appropriate (start or end) property
553 Any aAny;
556 aAny = rPropSet->getPropertyValue(bStart ? u"StartRedline"_ustr : u"EndRedline"_ustr);
558 catch(const UnknownPropertyException&)
560 // If we don't have the property, there's nothing to do.
561 return;
564 Sequence<PropertyValue> aValues;
565 aAny >>= aValues;
567 // seek for redline properties
568 bool bIsCollapsed = false;
569 bool bIsStart = true;
570 OUString sId;
571 bool bIdOK = false; // have we seen an ID?
572 for (const auto& rValue : aValues)
574 if (rValue.Name == "RedlineIdentifier")
576 rValue.Value >>= sId;
577 bIdOK = true;
579 else if (rValue.Name == "IsCollapsed")
581 bIsCollapsed = *o3tl::doAccess<bool>(rValue.Value);
583 else if (rValue.Name == "IsStart")
585 bIsStart = *o3tl::doAccess<bool>(rValue.Value);
589 if( !bIdOK )
590 return;
592 SAL_WARN_IF( sId.isEmpty(), "xmloff", "Redlines must have IDs" );
594 // TODO: use GetRedlineID or eliminate that function
595 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
596 "ct" + sId);
598 // export the element
599 // (whitespace because we're not inside paragraphs)
600 SvXMLElementExport aChangeElem(
601 rExport, XML_NAMESPACE_TEXT,
602 bIsCollapsed ? XML_CHANGE :
603 ( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
604 true, true);
607 void XMLRedlineExport::ExportStartOrEndRedline(
608 const Reference<XTextContent> & rContent,
609 bool bStart)
611 Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
612 if (xPropSet.is())
614 ExportStartOrEndRedline(xPropSet, bStart);
616 else
618 OSL_FAIL("XPropertySet expected");
622 void XMLRedlineExport::ExportStartOrEndRedline(
623 const Reference<XTextSection> & rSection,
624 bool bStart)
626 Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
627 if (xPropSet.is())
629 ExportStartOrEndRedline(xPropSet, bStart);
631 else
633 OSL_FAIL("XPropertySet expected");
637 void XMLRedlineExport::WriteComment(std::u16string_view rComment)
639 if (rComment.empty())
640 return;
642 // iterate over all string-pieces separated by return (0x0a) and
643 // put each inside a paragraph element.
644 SvXMLTokenEnumerator aEnumerator(rComment, char(0x0a));
645 std::u16string_view aSubString;
646 while (aEnumerator.getNextToken(aSubString))
648 SvXMLElementExport aParagraph(
649 rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
650 rExport.Characters(OUString(aSubString));
654 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */