LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / xmloff / source / text / XMLRedlineExport.cxx
blob6e5d4d79550a5c0159020a4c17ac3f20d6e07972
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>
46 #include <tools/date.hxx>
47 #include <tools/datetime.hxx>
50 using namespace ::com::sun::star;
51 using namespace ::xmloff::token;
53 using ::com::sun::star::beans::PropertyValue;
54 using ::com::sun::star::beans::XPropertySet;
55 using ::com::sun::star::beans::UnknownPropertyException;
56 using ::com::sun::star::document::XRedlinesSupplier;
57 using ::com::sun::star::container::XEnumerationAccess;
58 using ::com::sun::star::container::XEnumeration;
59 using ::com::sun::star::text::XText;
60 using ::com::sun::star::text::XTextContent;
61 using ::com::sun::star::text::XTextSection;
62 using ::com::sun::star::uno::Any;
63 using ::com::sun::star::uno::Reference;
64 using ::com::sun::star::uno::Sequence;
67 XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
68 : sDeletion(GetXMLToken(XML_DELETION))
69 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
70 , sInsertion(GetXMLToken(XML_INSERTION))
71 , rExport(rExp)
72 , pCurrentChangesList(nullptr)
77 XMLRedlineExport::~XMLRedlineExport()
82 void XMLRedlineExport::ExportChange(
83 const Reference<XPropertySet> & rPropSet,
84 bool bAutoStyle)
86 if (bAutoStyle)
88 // For the headers/footers, we have to collect the autostyles
89 // here. For the general case, however, it's better to collect
90 // the autostyles by iterating over the global redline
91 // list. So that's what we do: Here, we collect autostyles
92 // only if we have no current list of changes. For the
93 // main-document case, the autostyles are collected in
94 // ExportChangesListAutoStyles().
95 if (pCurrentChangesList != nullptr)
96 ExportChangeAutoStyle(rPropSet);
98 else
100 ExportChangeInline(rPropSet);
105 void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
107 if (bAutoStyles)
109 ExportChangesListAutoStyles();
111 else
113 ExportChangesListElements();
118 void XMLRedlineExport::ExportChangesList(
119 const Reference<XText> & rText,
120 bool bAutoStyles)
122 // in the header/footer case, auto styles are collected from the
123 // inline change elements.
124 if (bAutoStyles)
125 return;
127 // look for changes list for this XText
128 ChangesMapType::iterator aFind = aChangeMap.find(rText);
129 if (aFind == aChangeMap.end())
130 return;
132 ChangesVectorType* pChangesList = aFind->second.get();
134 // export only if changes are found
135 if (pChangesList->empty())
136 return;
138 // changes container element
139 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
140 XML_TRACKED_CHANGES,
141 true, true);
143 // iterate over changes list
144 for (auto const& change : *pChangesList)
146 ExportChangedRegion(change);
148 // else: changes list empty -> ignore
149 // else: no changes list found -> empty
152 void XMLRedlineExport::SetCurrentXText(
153 const Reference<XText> & rText)
155 if (rText.is())
157 // look for appropriate list in map; use the found one, or create new
158 ChangesMapType::iterator aIter = aChangeMap.find(rText);
159 if (aIter == aChangeMap.end())
161 ChangesVectorType* pList = new ChangesVectorType;
162 aChangeMap[rText].reset( pList );
163 pCurrentChangesList = pList;
165 else
166 pCurrentChangesList = aIter->second.get();
168 else
170 // don't record changes
171 SetCurrentXText();
175 void XMLRedlineExport::SetCurrentXText()
177 pCurrentChangesList = nullptr;
181 void XMLRedlineExport::ExportChangesListElements()
183 // get redlines (aka tracked changes) from the model
184 Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
185 if (!xSupplier.is())
186 return;
188 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
190 // redline protection key
191 Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
192 uno::UNO_QUERY );
193 // redlining enabled?
194 bool bEnabled = *o3tl::doAccess<bool>(aDocPropertySet->getPropertyValue(
195 "RecordChanges" ));
197 // only export if we have redlines or attributes
198 if ( !(aEnumAccess->hasElements() || bEnabled) )
199 return;
202 // export only if we have changes, but tracking is not enabled
203 if ( !bEnabled != !aEnumAccess->hasElements() )
205 rExport.AddAttribute(
206 XML_NAMESPACE_TEXT, XML_TRACK_CHANGES,
207 bEnabled ? XML_TRUE : XML_FALSE );
210 // changes container element
211 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
212 XML_TRACKED_CHANGES,
213 true, true);
215 // get enumeration and iterate over elements
216 Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
217 while (aEnum->hasMoreElements())
219 Any aAny = aEnum->nextElement();
220 Reference<XPropertySet> xPropSet;
221 aAny >>= xPropSet;
223 DBG_ASSERT(xPropSet.is(),
224 "can't get XPropertySet; skipping Redline");
225 if (xPropSet.is())
227 // export only if not in header or footer
228 // (those must be exported with their XText)
229 aAny = xPropSet->getPropertyValue("IsInHeaderFooter");
230 if (! *o3tl::doAccess<bool>(aAny))
232 // and finally, export change
233 ExportChangedRegion(xPropSet);
236 // else: no XPropertySet -> no export
238 // else: no redlines -> no export
239 // else: no XRedlineSupplier -> no export
242 void XMLRedlineExport::ExportChangeAutoStyle(
243 const Reference<XPropertySet> & rPropSet)
245 // record change (if changes should be recorded)
246 if (nullptr != pCurrentChangesList)
248 // put redline in list if it's collapsed or the redline start
249 Any aIsStart = rPropSet->getPropertyValue("IsStart");
250 Any aIsCollapsed = rPropSet->getPropertyValue("IsCollapsed");
252 if ( *o3tl::doAccess<bool>(aIsStart) ||
253 *o3tl::doAccess<bool>(aIsCollapsed) )
254 pCurrentChangesList->push_back(rPropSet);
257 // get XText for export of redline auto styles
258 Any aAny = rPropSet->getPropertyValue("RedlineText");
259 Reference<XText> xText;
260 aAny >>= xText;
261 if (xText.is())
263 // export the auto styles
264 rExport.GetTextParagraphExport()->collectTextAutoStyles(xText);
268 void XMLRedlineExport::ExportChangesListAutoStyles()
270 // get redlines (aka tracked changes) from the model
271 Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
272 if (!xSupplier.is())
273 return;
275 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
277 // only export if we actually have redlines
278 if (!aEnumAccess->hasElements())
279 return;
281 // get enumeration and iterate over elements
282 Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
283 while (aEnum->hasMoreElements())
285 Any aAny = aEnum->nextElement();
286 Reference<XPropertySet> xPropSet;
287 aAny >>= xPropSet;
289 DBG_ASSERT(xPropSet.is(),
290 "can't get XPropertySet; skipping Redline");
291 if (xPropSet.is())
294 // export only if not in header or footer
295 // (those must be exported with their XText)
296 aAny = xPropSet->getPropertyValue("IsInHeaderFooter");
297 if (! *o3tl::doAccess<bool>(aAny))
299 ExportChangeAutoStyle(xPropSet);
305 void XMLRedlineExport::ExportChangeInline(
306 const Reference<XPropertySet> & rPropSet)
308 // determine element name (depending on collapsed, start/end)
309 enum XMLTokenEnum eElement = XML_TOKEN_INVALID;
310 Any aAny = rPropSet->getPropertyValue("IsCollapsed");
311 bool bCollapsed = *o3tl::doAccess<bool>(aAny);
312 if (bCollapsed)
314 eElement = XML_CHANGE;
316 else
318 aAny = rPropSet->getPropertyValue("IsStart");
319 const bool bStart = *o3tl::doAccess<bool>(aAny);
320 eElement = bStart ? XML_CHANGE_START : XML_CHANGE_END;
323 if (XML_TOKEN_INVALID != eElement)
325 // we always need the ID
326 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
327 GetRedlineID(rPropSet));
329 // export the element (no whitespace because we're in the text body)
330 SvXMLElementExport aChangeElem(rExport, XML_NAMESPACE_TEXT,
331 eElement, false, false);
336 void XMLRedlineExport::ExportChangedRegion(
337 const Reference<XPropertySet> & rPropSet)
339 // Redline-ID
340 rExport.AddAttributeIdLegacy(XML_NAMESPACE_TEXT, GetRedlineID(rPropSet));
342 // merge-last-paragraph
343 Any aAny = rPropSet->getPropertyValue("MergeLastPara");
344 if( ! *o3tl::doAccess<bool>(aAny) )
345 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_MERGE_LAST_PARAGRAPH,
346 XML_FALSE);
348 // export change region element
349 SvXMLElementExport aChangedRegion(rExport, XML_NAMESPACE_TEXT,
350 XML_CHANGED_REGION, true, true);
353 // scope for (first) change element
355 aAny = rPropSet->getPropertyValue("RedlineType");
356 OUString sType;
357 aAny >>= sType;
358 SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT,
359 ConvertTypeName(sType), true, true);
361 ExportChangeInfo(rPropSet);
363 // get XText from the redline and export (if the XText exists)
364 aAny = rPropSet->getPropertyValue("RedlineText");
365 Reference<XText> xText;
366 aAny >>= xText;
367 if (xText.is())
369 rExport.GetTextParagraphExport()->exportText(xText);
370 // default parameters: bProgress, bExportParagraph ???
372 // else: no text interface -> content is inline and will
373 // be exported there
376 // changed change? Hierarchical changes can only be two levels
377 // deep. Here we check for the second level.
378 aAny = rPropSet->getPropertyValue("RedlineSuccessorData");
379 Sequence<PropertyValue> aSuccessorData;
380 aAny >>= aSuccessorData;
382 // if we actually got a hierarchical change, make element and
383 // process change info
384 if (aSuccessorData.hasElements())
386 // The only change that can be "undone" is an insertion -
387 // after all, you can't re-insert a deletion, but you can
388 // delete an insertion. This assumption is asserted in
389 // ExportChangeInfo(Sequence<PropertyValue>&).
390 SvXMLElementExport aSecondChangeElem(
391 rExport, XML_NAMESPACE_TEXT, XML_INSERTION,
392 true, true);
394 ExportChangeInfo(aSuccessorData);
396 // else: no hierarchical change
400 OUString const & XMLRedlineExport::ConvertTypeName(
401 std::u16string_view sApiName)
403 if (sApiName == u"Delete")
405 return sDeletion;
407 else if (sApiName == u"Insert")
409 return sInsertion;
411 else if (sApiName == u"Format")
413 return sFormatChange;
415 else
417 OSL_FAIL("unknown redline type");
418 static const OUString sUnknownChange("UnknownChange");
419 return sUnknownChange;
424 /** Create a Redline-ID */
425 OUString XMLRedlineExport::GetRedlineID(
426 const Reference<XPropertySet> & rPropSet)
428 Any aAny = rPropSet->getPropertyValue("RedlineIdentifier");
429 OUString sTmp;
430 aAny >>= sTmp;
432 return "ct" + sTmp;
436 void XMLRedlineExport::ExportChangeInfo(
437 const Reference<XPropertySet> & rPropSet)
439 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
440 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
442 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
443 XML_CHANGE_INFO, true, true);
445 Any aAny = rPropSet->getPropertyValue("RedlineAuthor");
446 OUString sTmp;
447 aAny >>= sTmp;
448 if (!sTmp.isEmpty())
450 SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
451 XML_CREATOR, true,
452 false );
453 rExport.Characters(bRemovePersonalInfo
454 ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
455 : sTmp );
458 aAny = rPropSet->getPropertyValue("RedlineDateTime");
459 util::DateTime aDateTime;
460 aAny >>= aDateTime;
462 OUStringBuffer sBuf;
463 ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
464 ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
465 : aDateTime, nullptr);
466 SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
467 XML_DATE, true,
468 false );
469 rExport.Characters(sBuf.makeStringAndClear());
472 // comment as <text:p> sequence
473 aAny = rPropSet->getPropertyValue("RedlineComment");
474 aAny >>= sTmp;
475 WriteComment( sTmp );
478 void XMLRedlineExport::ExportChangeInfo(
479 const Sequence<PropertyValue> & rPropertyValues)
481 OUString sComment;
482 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
483 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
485 for(const PropertyValue& rVal : rPropertyValues)
487 if( rVal.Name == "RedlineAuthor" )
489 OUString sTmp;
490 rVal.Value >>= sTmp;
491 if (!sTmp.isEmpty())
493 rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_AUTHOR, bRemovePersonalInfo
494 ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
495 : sTmp);
498 else if( rVal.Name == "RedlineComment" )
500 rVal.Value >>= sComment;
502 else if( rVal.Name == "RedlineDateTime" )
504 util::DateTime aDateTime;
505 rVal.Value >>= aDateTime;
506 OUStringBuffer sBuf;
507 ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
508 ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
509 : aDateTime, nullptr);
510 rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_DATE_TIME, sBuf.makeStringAndClear());
512 else if( rVal.Name == "RedlineType" )
514 // check if this is an insertion; cf. comment at calling location
515 OUString sTmp;
516 rVal.Value >>= sTmp;
517 DBG_ASSERT(sTmp == "Insert",
518 "hierarchical change must be insertion");
520 // else: unknown value -> ignore
523 // finally write element
524 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
525 XML_CHANGE_INFO, true, true);
527 WriteComment( sComment );
530 void XMLRedlineExport::ExportStartOrEndRedline(
531 const Reference<XPropertySet> & rPropSet,
532 bool bStart)
534 if( ! rPropSet.is() )
535 return;
537 // get appropriate (start or end) property
538 Any aAny;
541 aAny = rPropSet->getPropertyValue(bStart ? OUString("StartRedline") : OUString("EndRedline"));
543 catch(const UnknownPropertyException&)
545 // If we don't have the property, there's nothing to do.
546 return;
549 Sequence<PropertyValue> aValues;
550 aAny >>= aValues;
552 // seek for redline properties
553 bool bIsCollapsed = false;
554 bool bIsStart = true;
555 OUString sId;
556 bool bIdOK = false; // have we seen an ID?
557 for(const auto& rValue : std::as_const(aValues))
559 if (rValue.Name == "RedlineIdentifier")
561 rValue.Value >>= sId;
562 bIdOK = true;
564 else if (rValue.Name == "IsCollapsed")
566 bIsCollapsed = *o3tl::doAccess<bool>(rValue.Value);
568 else if (rValue.Name == "IsStart")
570 bIsStart = *o3tl::doAccess<bool>(rValue.Value);
574 if( !bIdOK )
575 return;
577 SAL_WARN_IF( sId.isEmpty(), "xmloff", "Redlines must have IDs" );
579 // TODO: use GetRedlineID or eliminate that function
580 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
581 "ct" + sId);
583 // export the element
584 // (whitespace because we're not inside paragraphs)
585 SvXMLElementExport aChangeElem(
586 rExport, XML_NAMESPACE_TEXT,
587 bIsCollapsed ? XML_CHANGE :
588 ( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
589 true, true);
592 void XMLRedlineExport::ExportStartOrEndRedline(
593 const Reference<XTextContent> & rContent,
594 bool bStart)
596 Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
597 if (xPropSet.is())
599 ExportStartOrEndRedline(xPropSet, bStart);
601 else
603 OSL_FAIL("XPropertySet expected");
607 void XMLRedlineExport::ExportStartOrEndRedline(
608 const Reference<XTextSection> & rSection,
609 bool bStart)
611 Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
612 if (xPropSet.is())
614 ExportStartOrEndRedline(xPropSet, bStart);
616 else
618 OSL_FAIL("XPropertySet expected");
622 void XMLRedlineExport::WriteComment(std::u16string_view rComment)
624 if (rComment.empty())
625 return;
627 // iterate over all string-pieces separated by return (0x0a) and
628 // put each inside a paragraph element.
629 SvXMLTokenEnumerator aEnumerator(rComment, char(0x0a));
630 std::u16string_view aSubString;
631 while (aEnumerator.getNextToken(aSubString))
633 SvXMLElementExport aParagraph(
634 rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
635 rExport.Characters(OUString(aSubString));
639 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */