Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/xmloff/source/text/XMLRedlineExport.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
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/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
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 .
18
 */
19
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>
31
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>
38
39
#include <sax/tools/converter.hxx>
40
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 <comphelper/sequenceashashmap.hxx>
47
48
49
using namespace ::com::sun::star;
50
using namespace ::xmloff::token;
51
52
using ::com::sun::star::beans::PropertyValue;
53
using ::com::sun::star::beans::XPropertySet;
54
using ::com::sun::star::beans::UnknownPropertyException;
55
using ::com::sun::star::document::XRedlinesSupplier;
56
using ::com::sun::star::container::XEnumerationAccess;
57
using ::com::sun::star::container::XEnumeration;
58
using ::com::sun::star::text::XText;
59
using ::com::sun::star::text::XTextContent;
60
using ::com::sun::star::text::XTextSection;
61
using ::com::sun::star::uno::Any;
62
using ::com::sun::star::uno::Reference;
63
using ::com::sun::star::uno::Sequence;
64
65
66
XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
67
0
:   sDeletion(GetXMLToken(XML_DELETION))
68
0
,   sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
69
0
,   sInsertion(GetXMLToken(XML_INSERTION))
70
0
,   rExport(rExp)
71
0
,   pCurrentChangesList(nullptr)
72
0
{
73
0
}
74
75
76
XMLRedlineExport::~XMLRedlineExport()
77
0
{
78
0
}
79
80
81
void XMLRedlineExport::ExportChange(
82
    const Reference<XPropertySet> & rPropSet,
83
    bool bAutoStyle)
84
0
{
85
0
    if (bAutoStyle)
86
0
    {
87
        // For the headers/footers, we have to collect the autostyles
88
        // here.  For the general case, however, it's better to collect
89
        // the autostyles by iterating over the global redline
90
        // list. So that's what we do: Here, we collect autostyles
91
        // only if we have no current list of changes. For the
92
        // main-document case, the autostyles are collected in
93
        // ExportChangesListAutoStyles().
94
0
        if (pCurrentChangesList != nullptr)
95
0
            ExportChangeAutoStyle(rPropSet);
96
0
    }
97
0
    else
98
0
    {
99
0
        ExportChangeInline(rPropSet);
100
0
    }
101
0
}
102
103
104
void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
105
0
{
106
0
    if (bAutoStyles)
107
0
    {
108
0
        ExportChangesListAutoStyles();
109
0
    }
110
0
    else
111
0
    {
112
0
        ExportChangesListElements();
113
0
    }
114
0
}
115
116
117
void XMLRedlineExport::ExportChangesList(
118
    const Reference<XText> & rText,
119
    bool bAutoStyles)
120
0
{
121
    // in the header/footer case, auto styles are collected from the
122
    // inline change elements.
123
0
    if (bAutoStyles)
124
0
        return;
125
126
    // look for changes list for this XText
127
0
    ChangesMapType::iterator aFind = aChangeMap.find(rText);
128
0
    if (aFind == aChangeMap.end())
129
0
        return;
130
131
0
    ChangesVectorType& rChangesList = aFind->second;
132
133
    // export only if changes are found
134
0
    if (rChangesList.empty())
135
0
        return;
136
137
    // changes container element
138
0
    SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
139
0
                                XML_TRACKED_CHANGES,
140
0
                                true, true);
141
142
    // iterate over changes list
143
0
    for (auto const& change : rChangesList)
144
0
    {
145
0
        ExportChangedRegion(change);
146
0
    }
147
    // else: changes list empty -> ignore
148
    // else: no changes list found -> empty
149
0
}
150
151
void XMLRedlineExport::SetCurrentXText(
152
    const Reference<XText> & rText)
153
0
{
154
0
    if (rText.is())
155
0
    {
156
        // look for appropriate list in map; use the found one, or create new
157
0
        ChangesMapType::iterator aIter = aChangeMap.find(rText);
158
0
        if (aIter == aChangeMap.end())
159
0
        {
160
0
            auto rv = aChangeMap.emplace(std::piecewise_construct, std::forward_as_tuple(rText), std::forward_as_tuple());
161
0
            pCurrentChangesList = &rv.first->second;
162
0
        }
163
0
        else
164
0
            pCurrentChangesList = &aIter->second;
165
0
    }
166
0
    else
167
0
    {
168
        // don't record changes
169
0
        SetCurrentXText();
170
0
    }
171
0
}
172
173
void XMLRedlineExport::SetCurrentXText()
174
0
{
175
0
    pCurrentChangesList = nullptr;
176
0
}
177
178
179
void XMLRedlineExport::ExportChangesListElements()
180
0
{
181
    // get redlines (aka tracked changes) from the model
182
0
    Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
183
0
    if (!xSupplier.is())
184
0
        return;
185
186
0
    Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
187
188
    // redline protection key
189
0
    Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
190
0
                                             uno::UNO_QUERY );
191
    // redlining enabled?
192
0
    bool bEnabled = *o3tl::doAccess<bool>(aDocPropertySet->getPropertyValue(
193
0
                                            u"RecordChanges"_ustr ));
194
195
    // only export if we have redlines or attributes
196
0
    if ( !(aEnumAccess->hasElements() || bEnabled) )
197
0
        return;
198
199
200
    // export only if we have changes, but tracking is not enabled
201
0
    if ( !bEnabled != !aEnumAccess->hasElements() )
202
0
    {
203
0
        rExport.AddAttribute(
204
0
            XML_NAMESPACE_TEXT, XML_TRACK_CHANGES,
205
0
            bEnabled ? XML_TRUE : XML_FALSE );
206
0
    }
207
208
    // changes container element
209
0
    SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
210
0
                                XML_TRACKED_CHANGES,
211
0
                                true, true);
212
213
    // get enumeration and iterate over elements
214
0
    Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
215
0
    while (aEnum->hasMoreElements())
216
0
    {
217
0
        Any aAny = aEnum->nextElement();
218
0
        Reference<XPropertySet> xPropSet;
219
0
        aAny >>= xPropSet;
220
221
0
        DBG_ASSERT(xPropSet.is(),
222
0
                   "can't get XPropertySet; skipping Redline");
223
0
        if (xPropSet.is())
224
0
        {
225
            // export only if not in header or footer
226
            // (those must be exported with their XText)
227
0
            aAny = xPropSet->getPropertyValue(u"IsInHeaderFooter"_ustr);
228
0
            if (! *o3tl::doAccess<bool>(aAny))
229
0
            {
230
                // and finally, export change
231
0
                ExportChangedRegion(xPropSet);
232
0
            }
233
0
        }
234
        // else: no XPropertySet -> no export
235
0
    }
236
    // else: no redlines -> no export
237
    // else: no XRedlineSupplier -> no export
238
0
}
239
240
void XMLRedlineExport::ExportChangeAutoStyle(
241
    const Reference<XPropertySet> & rPropSet)
242
0
{
243
    // record change (if changes should be recorded)
244
0
    if (nullptr != pCurrentChangesList)
245
0
    {
246
        // put redline in list if it's collapsed or the redline start
247
0
        Any aIsStart = rPropSet->getPropertyValue(u"IsStart"_ustr);
248
0
        Any aIsCollapsed = rPropSet->getPropertyValue(u"IsCollapsed"_ustr);
249
250
0
        if ( *o3tl::doAccess<bool>(aIsStart) ||
251
0
             *o3tl::doAccess<bool>(aIsCollapsed) )
252
0
            pCurrentChangesList->push_back(rPropSet);
253
0
    }
254
255
    // get XText for export of redline auto styles
256
0
    Any aAny = rPropSet->getPropertyValue(u"RedlineText"_ustr);
257
0
    Reference<XText> xText;
258
0
    aAny >>= xText;
259
0
    if (xText.is())
260
0
    {
261
        // export the auto styles
262
0
        rExport.GetTextParagraphExport()->collectTextAutoStyles(xText);
263
0
    }
264
265
0
    SvtSaveOptions::ODFSaneDefaultVersion eVersion = rExport.getSaneDefaultVersion();
266
0
    if (eVersion & SvtSaveOptions::ODFSVER_EXTENDED)
267
0
    {
268
        // See if the format redline has an autostyle for old direct formatting: if so, export that as
269
        // an autostyle.
270
0
        aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr);
271
0
        uno::Reference<beans::XPropertySet> xAutoFormat;
272
0
        aAny >>= xAutoFormat;
273
0
        if (xAutoFormat.is())
274
0
        {
275
            // Check for parent style when declaring the automatic style.
276
0
            rExport.GetTextParagraphExport()->Add(XmlStyleFamily::TEXT_TEXT, xAutoFormat,
277
0
                    /*aAddStates=*/{},
278
0
                    /*bCheckParent=*/true);
279
0
        }
280
0
    }
281
0
}
282
283
void XMLRedlineExport::ExportChangesListAutoStyles()
284
0
{
285
    // get redlines (aka tracked changes) from the model
286
0
    Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
287
0
    if (!xSupplier.is())
288
0
        return;
289
290
0
    Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
291
292
    // only export if we actually have redlines
293
0
    if (!aEnumAccess->hasElements())
294
0
        return;
295
296
    // get enumeration and iterate over elements
297
0
    Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
298
0
    while (aEnum->hasMoreElements())
299
0
    {
300
0
        Any aAny = aEnum->nextElement();
301
0
        Reference<XPropertySet> xPropSet;
302
0
        aAny >>= xPropSet;
303
304
0
        DBG_ASSERT(xPropSet.is(),
305
0
                   "can't get XPropertySet; skipping Redline");
306
0
        if (xPropSet.is())
307
0
        {
308
309
            // export only if not in header or footer
310
            // (those must be exported with their XText)
311
0
            aAny = xPropSet->getPropertyValue(u"IsInHeaderFooter"_ustr);
312
0
            if (! *o3tl::doAccess<bool>(aAny))
313
0
            {
314
0
                ExportChangeAutoStyle(xPropSet);
315
0
            }
316
0
        }
317
0
    }
318
0
}
319
320
void XMLRedlineExport::ExportChangeInline(
321
    const Reference<XPropertySet> & rPropSet)
322
0
{
323
    // determine element name (depending on collapsed, start/end)
324
0
    enum XMLTokenEnum eElement = XML_TOKEN_INVALID;
325
0
    Any aAny = rPropSet->getPropertyValue(u"IsCollapsed"_ustr);
326
0
    bool bCollapsed = *o3tl::doAccess<bool>(aAny);
327
0
    if (bCollapsed)
328
0
    {
329
0
        eElement = XML_CHANGE;
330
0
    }
331
0
    else
332
0
    {
333
0
        aAny = rPropSet->getPropertyValue(u"IsStart"_ustr);
334
0
        const bool bStart = *o3tl::doAccess<bool>(aAny);
335
0
        eElement = bStart ? XML_CHANGE_START : XML_CHANGE_END;
336
0
    }
337
338
0
    if (XML_TOKEN_INVALID != eElement)
339
0
    {
340
        // we always need the ID
341
0
        rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
342
0
                             GetRedlineID(rPropSet));
343
344
        // export the element (no whitespace because we're in the text body)
345
0
        SvXMLElementExport aChangeElem(rExport, XML_NAMESPACE_TEXT,
346
0
                                       eElement, false, false);
347
0
    }
348
0
}
349
350
351
void XMLRedlineExport::ExportChangedRegion(
352
    const Reference<XPropertySet> & rPropSet)
353
0
{
354
    // Redline-ID
355
0
    rExport.AddAttributeIdLegacy(XML_NAMESPACE_TEXT, GetRedlineID(rPropSet));
356
357
    // merge-last-paragraph
358
0
    Any aAny = rPropSet->getPropertyValue(u"MergeLastPara"_ustr);
359
0
    if( ! *o3tl::doAccess<bool>(aAny) )
360
0
        rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_MERGE_LAST_PARAGRAPH,
361
0
                             XML_FALSE);
362
363
    // export change region element
364
0
    SvXMLElementExport aChangedRegion(rExport, XML_NAMESPACE_TEXT,
365
0
                                      XML_CHANGED_REGION, true, true);
366
367
368
    // scope for (first) change element
369
0
    {
370
0
        aAny = rPropSet->getPropertyValue(u"RedlineType"_ustr);
371
0
        OUString sType;
372
0
        aAny >>= sType;
373
374
0
        SvtSaveOptions::ODFSaneDefaultVersion eVersion = rExport.getSaneDefaultVersion();
375
0
        if (eVersion & SvtSaveOptions::ODFSVER_EXTENDED)
376
0
        {
377
            // See if the format redline has an autostyle for old direct formatting: if so, refer to the
378
            // already exported autostyle.
379
0
            aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr);
380
0
            uno::Reference<beans::XPropertySet> xAutoStyle;
381
0
            aAny >>= xAutoStyle;
382
0
            if (xAutoStyle.is())
383
0
            {
384
0
                bool bIsUICharStyle;
385
0
                bool bHasAutoStyle;
386
0
                OUString aParentName;
387
0
                uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xAutoStyle->getPropertySetInfo();
388
0
                if (xPropSetInfo->hasPropertyByName("CharStyleName"))
389
0
                {
390
                    // Consider parent style when referring to the declared automatic style.
391
0
                    xAutoStyle->getPropertyValue("CharStyleName") >>= aParentName;
392
0
                }
393
0
                OUString sStyle = rExport.GetTextParagraphExport()->FindTextStyle(
394
0
                        xAutoStyle, bIsUICharStyle, bHasAutoStyle, /*pAddState=*/nullptr, &aParentName);
395
0
                if (!sStyle.isEmpty())
396
0
                {
397
0
                    rExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_STYLE_NAME, rExport.EncodeStyleName(sStyle));
398
0
                }
399
0
            }
400
0
        }
401
402
0
        SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT,
403
0
                                   ConvertTypeName(sType), true, true);
404
405
0
        ExportChangeInfo(rPropSet);
406
407
        // get XText from the redline and export (if the XText exists)
408
0
        aAny = rPropSet->getPropertyValue(u"RedlineText"_ustr);
409
0
        Reference<XText> xText;
410
0
        aAny >>= xText;
411
        // Avoid double export for format-on-delete: no write on format, only on delete.
412
0
        if (xText.is() && sType == u"Delete")
413
0
        {
414
0
            rExport.GetTextParagraphExport()->exportText(xText);
415
            // default parameters: bProgress, bExportParagraph ???
416
0
        }
417
        // else: no text interface -> content is inline and will
418
        //       be exported there
419
0
    }
420
421
    // changed change? Hierarchical changes can only be two levels
422
    // deep. Here we check for the second level.
423
0
    aAny = rPropSet->getPropertyValue(u"RedlineSuccessorData"_ustr);
424
0
    Sequence<PropertyValue> aSuccessorData;
425
0
    aAny >>= aSuccessorData;
426
427
    // if we actually got a hierarchical change, make element and
428
    // process change info
429
0
    if (aSuccessorData.hasElements())
430
0
    {
431
        // Look up the type of the change. In practice this is always insert or delete, other types
432
        // can't have additional redlines on top of them.
433
0
        OUString sType;
434
0
        comphelper::SequenceAsHashMap aMap(aSuccessorData);
435
0
        auto it = aMap.find("RedlineType");
436
0
        if (it != aMap.end())
437
0
        {
438
0
            it->second >>= sType;
439
0
        }
440
0
        SvXMLElementExport aSecondChangeElem(
441
0
            rExport, XML_NAMESPACE_TEXT, ConvertTypeName(sType),
442
0
            true, true);
443
444
0
        ExportChangeInfo(aSuccessorData);
445
0
        it = aMap.find("RedlineText");
446
0
        if (it != aMap.end())
447
0
        {
448
            // Delete has its own content outside the body text: export it here.
449
0
            uno::Reference<text::XText> xText;
450
0
            it->second >>= xText;
451
0
            if (xText.is())
452
0
            {
453
0
                rExport.GetTextParagraphExport()->exportText(xText);
454
0
            }
455
0
        }
456
0
    }
457
    // else: no hierarchical change
458
0
}
459
460
461
OUString const & XMLRedlineExport::ConvertTypeName(
462
    std::u16string_view sApiName)
463
0
{
464
0
    if (sApiName == u"Delete")
465
0
    {
466
0
        return sDeletion;
467
0
    }
468
0
    else if (sApiName == u"Insert")
469
0
    {
470
0
        return sInsertion;
471
0
    }
472
0
    else if (sApiName == u"Format")
473
0
    {
474
0
        return sFormatChange;
475
0
    }
476
0
    else
477
0
    {
478
0
        OSL_FAIL("unknown redline type");
479
0
        static constexpr OUString sUnknownChange(u"UnknownChange"_ustr);
480
0
        return sUnknownChange;
481
0
    }
482
0
}
483
484
485
/** Create a Redline-ID */
486
OUString XMLRedlineExport::GetRedlineID(
487
    const Reference<XPropertySet> & rPropSet)
488
0
{
489
0
    Any aAny = rPropSet->getPropertyValue(u"RedlineIdentifier"_ustr);
490
0
    OUString sTmp;
491
0
    aAny >>= sTmp;
492
493
0
    return "ct" + sTmp;
494
0
}
495
496
497
void XMLRedlineExport::ExportChangeInfo(
498
    const Reference<XPropertySet> & rPropSet)
499
0
{
500
0
    bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
501
0
            SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
502
0
                SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
503
504
0
    SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
505
0
                                   XML_CHANGE_INFO, true, true);
506
507
0
    Any aAny = rPropSet->getPropertyValue(u"RedlineAuthor"_ustr);
508
0
    OUString sTmp;
509
0
    aAny >>= sTmp;
510
0
    if (!sTmp.isEmpty())
511
0
    {
512
0
        SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
513
0
                                          XML_CREATOR, true,
514
0
                                          false );
515
0
        rExport.Characters(bRemovePersonalInfo
516
0
                ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
517
0
                : sTmp );
518
0
    }
519
520
0
    aAny = rPropSet->getPropertyValue(u"RedlineMovedID"_ustr);
521
0
    sal_uInt32 nTmp(0);
522
0
    aAny >>= nTmp;
523
0
    if (nTmp > 1)
524
0
    {
525
0
        SvXMLElementExport aCreatorElem(rExport, XML_NAMESPACE_LO_EXT, XML_MOVE_ID, true, false);
526
0
        rExport.Characters( OUString::number( nTmp ) );
527
0
    }
528
529
0
    aAny = rPropSet->getPropertyValue(u"RedlineDateTime"_ustr);
530
0
    util::DateTime aDateTime;
531
0
    aAny >>= aDateTime;
532
0
    {
533
0
        OUStringBuffer sBuf;
534
0
        ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
535
0
                ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
536
0
                : aDateTime, nullptr, true);
537
0
        SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
538
0
                                          XML_DATE, true,
539
0
                                          false );
540
0
        rExport.Characters(sBuf.makeStringAndClear());
541
0
    }
542
543
    // comment as <text:p> sequence
544
0
    aAny = rPropSet->getPropertyValue(u"RedlineComment"_ustr);
545
0
    aAny >>= sTmp;
546
0
    WriteComment( sTmp );
547
0
}
548
549
// write RedlineSuccessorData
550
void XMLRedlineExport::ExportChangeInfo(
551
    const Sequence<PropertyValue> & rPropertyValues)
552
0
{
553
0
    OUString sComment;
554
0
    bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
555
0
            SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
556
0
                SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
557
558
0
    SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
559
0
                                   XML_CHANGE_INFO, true, true);
560
561
0
    for(const PropertyValue& rVal : rPropertyValues)
562
0
    {
563
0
        if( rVal.Name == "RedlineAuthor" )
564
0
        {
565
0
            OUString sTmp;
566
0
            rVal.Value >>= sTmp;
567
0
            if (!sTmp.isEmpty())
568
0
            {
569
0
                SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
570
0
                                        XML_CREATOR, true,
571
0
                                        false );
572
0
                rExport.Characters(bRemovePersonalInfo
573
0
                                        ? "Author" + OUString::number(rExport.GetInfoID(sTmp))
574
0
                                        : sTmp );
575
0
            }
576
0
        }
577
0
        else if( rVal.Name == "RedlineComment" )
578
0
        {
579
0
            rVal.Value >>= sComment;
580
0
        }
581
0
        else if( rVal.Name == "RedlineDateTime" )
582
0
        {
583
0
            util::DateTime aDateTime;
584
0
            rVal.Value >>= aDateTime;
585
0
            OUStringBuffer sBuf;
586
0
            ::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
587
0
                                        ? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
588
0
                                        : aDateTime, nullptr, true);
589
0
            SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
590
0
                                          XML_DATE, true,
591
0
                                          false );
592
0
            rExport.Characters(sBuf.makeStringAndClear());
593
0
        }
594
0
        else if( rVal.Name == "RedlineType" )
595
0
        {
596
            // check if this has one of the expected types
597
0
            OUString sTmp;
598
0
            rVal.Value >>= sTmp;
599
0
            DBG_ASSERT(sTmp == "Insert" || sTmp == "Delete",
600
0
                       "hierarchical change must be insertion");
601
0
        }
602
        // else: unknown value -> ignore
603
0
    }
604
605
    // finally write comment paragraphs
606
0
    WriteComment( sComment );
607
0
}
608
609
void XMLRedlineExport::ExportStartOrEndRedline(
610
    const Reference<XPropertySet> & rPropSet,
611
    bool bStart)
612
0
{
613
0
    if( ! rPropSet.is() )
614
0
        return;
615
616
    // get appropriate (start or end) property
617
0
    Any aAny;
618
0
    try
619
0
    {
620
0
        aAny = rPropSet->getPropertyValue(bStart ? u"StartRedline"_ustr : u"EndRedline"_ustr);
621
0
    }
622
0
    catch(const UnknownPropertyException&)
623
0
    {
624
        // If we don't have the property, there's nothing to do.
625
0
        return;
626
0
    }
627
628
0
    Sequence<PropertyValue> aValues;
629
0
    aAny >>= aValues;
630
631
    // seek for redline properties
632
0
    bool bIsCollapsed = false;
633
0
    bool bIsStart = true;
634
0
    OUString sId;
635
0
    bool bIdOK = false; // have we seen an ID?
636
0
    for (const auto& rValue : aValues)
637
0
    {
638
0
        if (rValue.Name == "RedlineIdentifier")
639
0
        {
640
0
            rValue.Value >>= sId;
641
0
            bIdOK = true;
642
0
        }
643
0
        else if (rValue.Name == "IsCollapsed")
644
0
        {
645
0
            bIsCollapsed = *o3tl::doAccess<bool>(rValue.Value);
646
0
        }
647
0
        else if (rValue.Name == "IsStart")
648
0
        {
649
0
            bIsStart = *o3tl::doAccess<bool>(rValue.Value);
650
0
        }
651
0
    }
652
653
0
    if( !bIdOK )
654
0
        return;
655
656
0
    SAL_WARN_IF( sId.isEmpty(), "xmloff", "Redlines must have IDs" );
657
658
    // TODO: use GetRedlineID or eliminate that function
659
0
    rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
660
0
                         "ct" + sId);
661
662
    // export the element
663
    // (whitespace because we're not inside paragraphs)
664
0
    SvXMLElementExport aChangeElem(
665
0
        rExport, XML_NAMESPACE_TEXT,
666
0
        bIsCollapsed ? XML_CHANGE :
667
0
            ( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
668
0
        true, true);
669
0
}
670
671
void XMLRedlineExport::ExportStartOrEndRedline(
672
    const Reference<XTextContent> & rContent,
673
    bool bStart)
674
0
{
675
0
    Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
676
0
    if (xPropSet.is())
677
0
    {
678
0
        ExportStartOrEndRedline(xPropSet, bStart);
679
0
    }
680
0
    else
681
0
    {
682
0
        OSL_FAIL("XPropertySet expected");
683
0
    }
684
0
}
685
686
void XMLRedlineExport::ExportStartOrEndRedline(
687
    const Reference<XTextSection> & rSection,
688
    bool bStart)
689
0
{
690
0
    Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
691
0
    if (xPropSet.is())
692
0
    {
693
0
        ExportStartOrEndRedline(xPropSet, bStart);
694
0
    }
695
0
    else
696
0
    {
697
0
        OSL_FAIL("XPropertySet expected");
698
0
    }
699
0
}
700
701
void XMLRedlineExport::WriteComment(std::u16string_view rComment)
702
0
{
703
0
    if (rComment.empty())
704
0
        return;
705
706
    // iterate over all string-pieces separated by return (0x0a) and
707
    // put each inside a paragraph element.
708
0
    SvXMLTokenEnumerator aEnumerator(rComment, char(0x0a));
709
0
    std::u16string_view aSubString;
710
0
    while (aEnumerator.getNextToken(aSubString))
711
0
    {
712
0
        SvXMLElementExport aParagraph(
713
0
            rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
714
0
        rExport.Characters(OUString(aSubString));
715
0
    }
716
0
}
717
718
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */