Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/xmloff/source/meta/xmlmetae.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 <i18nlangtag/languagetag.hxx>
21
#include <rtl/ustrbuf.hxx>
22
#include <sal/log.hxx>
23
24
#include <utility>
25
#include <xmloff/xmlmetae.hxx>
26
#include <xmloff/xmlexp.hxx>
27
#include <xmloff/namespacemap.hxx>
28
#include <xmloff/xmlnamespace.hxx>
29
30
#include <com/sun/star/beans/XPropertyAccess.hpp>
31
#include <com/sun/star/beans/StringPair.hpp>
32
#include <com/sun/star/document/XDocumentProperties.hpp>
33
#include <com/sun/star/util/DateTime.hpp>
34
#include <com/sun/star/util/Duration.hpp>
35
#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
36
37
#include <sax/tools/converter.hxx>
38
39
#include <comphelper/sequence.hxx>
40
#include <unotools/docinfohelper.hxx>
41
42
using namespace com::sun::star;
43
using namespace ::xmloff::token;
44
45
static void lcl_AddTwoDigits( OUStringBuffer& rStr, sal_Int32 nVal )
46
0
{
47
0
    if ( nVal < 10 )
48
0
        rStr.append( '0' );
49
0
    rStr.append( nVal );
50
0
}
51
52
OUString
53
SvXMLMetaExport::GetISODateTimeString( const util::DateTime& rDateTime )
54
0
{
55
    //  return ISO date string "YYYY-MM-DDThh:mm:ss"
56
57
0
    OUStringBuffer sTmp =
58
0
        OUString::number( static_cast<sal_Int32>(rDateTime.Year) )
59
0
        + "-";
60
0
    lcl_AddTwoDigits( sTmp, rDateTime.Month );
61
0
    sTmp.append( '-' );
62
0
    lcl_AddTwoDigits( sTmp, rDateTime.Day );
63
0
    sTmp.append( 'T' );
64
0
    lcl_AddTwoDigits( sTmp, rDateTime.Hours );
65
0
    sTmp.append( ':' );
66
0
    lcl_AddTwoDigits( sTmp, rDateTime.Minutes );
67
0
    sTmp.append( ':' );
68
0
    lcl_AddTwoDigits( sTmp, rDateTime.Seconds );
69
70
0
    return sTmp.makeStringAndClear();
71
0
}
72
73
void SvXMLMetaExport::SimpleStringElement( const OUString& rText,
74
        sal_uInt16 nNamespace, enum XMLTokenEnum eElementName )
75
0
{
76
0
    if ( !rText.isEmpty() ) {
77
0
        SvXMLElementExport aElem( mrExport, nNamespace, eElementName,
78
0
                                  true, false );
79
0
        mrExport.Characters( rText );
80
0
    }
81
0
}
82
83
void SvXMLMetaExport::SimpleDateTimeElement( const util::DateTime & rDate,
84
        sal_uInt16 nNamespace, enum XMLTokenEnum eElementName )
85
0
{
86
0
    if (rDate.Month != 0) { // invalid dates are 0-0-0
87
0
        OUString sValue = GetISODateTimeString( rDate );
88
0
        if ( !sValue.isEmpty() ) {
89
0
            SvXMLElementExport aElem( mrExport, nNamespace, eElementName,
90
0
                                      true, false );
91
0
            mrExport.Characters( sValue );
92
0
        }
93
0
    }
94
0
}
95
96
void SvXMLMetaExport::MExport_()
97
0
{
98
0
    bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
99
0
    bool bRemoveUserInfo = bRemovePersonalInfo && !SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnKeepDocUserInfo);
100
101
    //  generator
102
0
    {
103
0
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_GENERATOR,
104
0
                                  true, true );
105
0
        mrExport.Characters( ::utl::DocInfoHelper::GetGeneratorString() );
106
0
    }
107
108
    //  document title
109
0
    SimpleStringElement  ( mxDocProps->getTitle(),
110
0
                           XML_NAMESPACE_DC, XML_TITLE );
111
112
    //  description
113
0
    SimpleStringElement  ( mxDocProps->getDescription(),
114
0
                           XML_NAMESPACE_DC, XML_DESCRIPTION );
115
116
    //  subject
117
0
    SimpleStringElement  ( mxDocProps->getSubject(),
118
0
                           XML_NAMESPACE_DC, XML_SUBJECT );
119
120
    //  created...
121
0
    if (!bRemoveUserInfo)
122
0
    {
123
0
        SimpleStringElement(mxDocProps->getAuthor(), XML_NAMESPACE_META, XML_INITIAL_CREATOR);
124
0
        SimpleDateTimeElement(mxDocProps->getCreationDate(), XML_NAMESPACE_META, XML_CREATION_DATE);
125
126
        //  modified...
127
0
        SimpleStringElement(mxDocProps->getModifiedBy(), XML_NAMESPACE_DC, XML_CREATOR);
128
0
        SimpleDateTimeElement(mxDocProps->getModificationDate(), XML_NAMESPACE_DC, XML_DATE);
129
130
        //  printed...
131
0
        SimpleStringElement(mxDocProps->getPrintedBy(), XML_NAMESPACE_META, XML_PRINTED_BY);
132
0
        SimpleDateTimeElement(mxDocProps->getPrintDate(), XML_NAMESPACE_META, XML_PRINT_DATE);
133
0
    }
134
135
    //  keywords
136
0
    const uno::Sequence< OUString > keywords = mxDocProps->getKeywords();
137
0
    for (const auto& rKeyword : keywords) {
138
0
        SvXMLElementExport aKwElem( mrExport, XML_NAMESPACE_META, XML_KEYWORD,
139
0
                                    true, false );
140
0
        mrExport.Characters( rKeyword );
141
0
    }
142
143
    //  document language
144
0
    {
145
0
        OUString sValue = LanguageTag( mxDocProps->getLanguage()).getBcp47( false);
146
0
        if (!sValue.isEmpty()) {
147
0
            SvXMLElementExport aElem( mrExport, XML_NAMESPACE_DC, XML_LANGUAGE,
148
0
                                      true, false );
149
0
            mrExport.Characters( sValue );
150
0
        }
151
0
    }
152
153
    //  editing cycles
154
0
    if (!bRemovePersonalInfo)
155
0
    {
156
0
        SvXMLElementExport aElem( mrExport,
157
0
                                  XML_NAMESPACE_META, XML_EDITING_CYCLES,
158
0
                                  true, false );
159
0
        mrExport.Characters( OUString::number(
160
0
            mxDocProps->getEditingCycles() ) );
161
0
    }
162
163
    //  editing duration
164
    //  property is a int32 (seconds)
165
0
    if (!bRemovePersonalInfo && !SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnRemoveEditingTimeInfo))
166
0
    {
167
0
        sal_Int32 secs = mxDocProps->getEditingDuration();
168
0
        SvXMLElementExport aElem( mrExport,
169
0
            XML_NAMESPACE_META, XML_EDITING_DURATION,
170
0
            true, false );
171
0
        OUStringBuffer buf;
172
0
        ::sax::Converter::convertDuration(buf, util::Duration(
173
0
            false, 0, 0, 0, secs/3600, (secs%3600)/60, secs%60, 0));
174
0
        mrExport.Characters(buf.makeStringAndClear());
175
0
    }
176
177
    //  default target
178
0
    const OUString sDefTarget = mxDocProps->getDefaultTarget();
179
0
    if ( !sDefTarget.isEmpty() )
180
0
    {
181
0
        mrExport.AddAttribute( XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME,
182
0
                               sDefTarget );
183
184
        //! define strings for xlink:show values
185
0
        const XMLTokenEnum eShow = sDefTarget == "_blank" ? XML_NEW : XML_REPLACE;
186
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_SHOW, eShow );
187
188
0
        SvXMLElementExport aElem( mrExport,
189
0
                                  XML_NAMESPACE_META,XML_HYPERLINK_BEHAVIOUR,
190
0
                                  true, false );
191
0
    }
192
193
    //  auto-reload
194
0
    const OUString sReloadURL = mxDocProps->getAutoloadURL();
195
0
    const sal_Int32 sReloadDelay = mxDocProps->getAutoloadSecs();
196
0
    if (sReloadDelay != 0 || !sReloadURL.isEmpty())
197
0
    {
198
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF,
199
0
                              mrExport.GetRelativeReference( sReloadURL ) );
200
201
0
        OUStringBuffer buf;
202
0
        ::sax::Converter::convertDuration(buf, util::Duration(false, 0, 0, 0,
203
0
                sReloadDelay/3600, (sReloadDelay%3600)/60, sReloadDelay%60, 0));
204
0
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_DELAY,
205
0
            buf.makeStringAndClear());
206
207
0
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_AUTO_RELOAD,
208
0
                                  true, false );
209
0
    }
210
211
    //  template
212
0
    const OUString sTplPath = mxDocProps->getTemplateURL();
213
0
    if ( !bRemovePersonalInfo && !sTplPath.isEmpty() )
214
0
    {
215
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE );
216
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONREQUEST );
217
218
        //  template URL
219
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF,
220
0
                              mrExport.GetRelativeReference(sTplPath) );
221
222
        //  template name
223
0
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TITLE,
224
0
                              mxDocProps->getTemplateName() );
225
226
        //  template date
227
0
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_DATE,
228
0
                GetISODateTimeString( mxDocProps->getTemplateDate() ) );
229
230
0
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_TEMPLATE,
231
0
                                  true, false );
232
0
    }
233
234
    //  user defined fields
235
0
    uno::Reference< beans::XPropertyAccess > xUserDefined(
236
0
        mxDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
237
0
    const uno::Sequence< beans::PropertyValue > props =
238
0
        xUserDefined->getPropertyValues();
239
0
    for (const auto& rProp : props) {
240
0
        OUStringBuffer sValueBuffer;
241
0
        OUStringBuffer sType;
242
0
        if (!::sax::Converter::convertAny(sValueBuffer, sType, rProp.Value))
243
0
        {
244
0
            continue;
245
0
        }
246
0
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_NAME, rProp.Name );
247
0
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_VALUE_TYPE,
248
0
                              sType.makeStringAndClear() );
249
0
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META,
250
0
                                  XML_USER_DEFINED, true, false );
251
0
        mrExport.Characters( sValueBuffer.makeStringAndClear() );
252
0
    }
253
254
0
    const uno::Sequence< beans::NamedValue > aDocStatistic =
255
0
            mxDocProps->getDocumentStatistics();
256
    // write document statistic if there is any provided
257
0
    if ( !aDocStatistic.hasElements() )
258
0
        return;
259
260
0
    for ( const auto& rDocStat : aDocStatistic )
261
0
    {
262
0
        sal_Int32 nValue = 0;
263
0
        if ( rDocStat.Value >>= nValue )
264
0
        {
265
0
            OUString aValue = OUString::number( nValue );
266
0
            if ( rDocStat.Name == "TableCount" )
267
0
                mrExport.AddAttribute(
268
0
                    XML_NAMESPACE_META, XML_TABLE_COUNT, aValue );
269
0
            else if ( rDocStat.Name == "ObjectCount" )
270
0
                mrExport.AddAttribute(
271
0
                    XML_NAMESPACE_META, XML_OBJECT_COUNT, aValue );
272
0
            else if ( rDocStat.Name == "ImageCount" )
273
0
                mrExport.AddAttribute(
274
0
                    XML_NAMESPACE_META, XML_IMAGE_COUNT, aValue );
275
0
            else if ( rDocStat.Name == "PageCount" )
276
0
                mrExport.AddAttribute(
277
0
                    XML_NAMESPACE_META, XML_PAGE_COUNT, aValue );
278
0
            else if ( rDocStat.Name == "ParagraphCount" )
279
0
                mrExport.AddAttribute(
280
0
                    XML_NAMESPACE_META, XML_PARAGRAPH_COUNT, aValue );
281
0
            else if ( rDocStat.Name == "WordCount" )
282
0
                mrExport.AddAttribute(
283
0
                    XML_NAMESPACE_META, XML_WORD_COUNT, aValue );
284
0
            else if ( rDocStat.Name == "CharacterCount" )
285
0
                mrExport.AddAttribute(
286
0
                    XML_NAMESPACE_META, XML_CHARACTER_COUNT, aValue );
287
0
            else if ( rDocStat.Name == "CellCount" )
288
0
                mrExport.AddAttribute(
289
0
                    XML_NAMESPACE_META, XML_CELL_COUNT, aValue );
290
0
            else
291
0
            {
292
0
                SAL_WARN("xmloff", "Unknown statistic value!");
293
0
            }
294
0
        }
295
0
    }
296
0
    SvXMLElementExport aElem( mrExport,
297
0
        XML_NAMESPACE_META, XML_DOCUMENT_STATISTIC, true, true );
298
0
}
299
300
const char s_xmlns[] = "xmlns";
301
const char s_xmlns2[] = "xmlns:";
302
const char s_meta[] = "meta:";
303
const char s_href[] = "xlink:href";
304
305
SvXMLMetaExport::SvXMLMetaExport(
306
        SvXMLExport& i_rExp,
307
        uno::Reference<document::XDocumentProperties> i_rDocProps ) :
308
23
    mrExport( i_rExp ),
309
23
    mxDocProps(std::move( i_rDocProps )),
310
23
    m_level( 0 )
311
23
{
312
23
    assert(mxDocProps.is());
313
23
}
314
315
SvXMLMetaExport::~SvXMLMetaExport()
316
23
{
317
23
}
318
319
void SvXMLMetaExport::Export()
320
23
{
321
23
    uno::Reference< xml::sax::XSAXSerializable> xSAXable(mxDocProps,
322
23
        uno::UNO_QUERY);
323
23
    bool bRemovePersonalInfo
324
23
        = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo);
325
23
    if (xSAXable.is() && !bRemovePersonalInfo) {
326
23
        ::std::vector< beans::StringPair > namespaces;
327
23
        const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap());
328
23
        for (sal_uInt16 key = rNsMap.GetFirstKey();
329
920
             key != USHRT_MAX; key = rNsMap.GetNextKey(key)) {
330
897
            beans::StringPair ns;
331
897
            const OUString attrname = rNsMap.GetAttrNameByKey(key);
332
897
            if (!attrname.startsWith(s_xmlns2, &ns.First)
333
897
                || attrname == s_xmlns) // default initialized empty string
334
0
            {
335
0
                assert(!"namespace attribute not starting with xmlns unexpected");
336
0
            }
337
897
            ns.Second = rNsMap.GetNameByKey(key);
338
897
            namespaces.push_back(ns);
339
897
        }
340
23
        xSAXable->serialize(this, comphelper::containerToSequence(namespaces));
341
23
    } else {
342
        // office:meta
343
0
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_OFFICE, XML_META,
344
0
                                  true, true );
345
        // fall back to using public interface of XDocumentProperties
346
0
        MExport_();
347
0
    }
348
23
}
349
350
// css::xml::sax::XDocumentHandler:
351
void SAL_CALL
352
SvXMLMetaExport::startDocument()
353
23
{
354
    // ignore: has already been done by SvXMLExport::exportDoc
355
23
    assert(m_level == 0 && "SvXMLMetaExport: level error");
356
23
}
357
358
void SAL_CALL
359
SvXMLMetaExport::endDocument()
360
23
{
361
    // ignore: will be done by SvXMLExport::exportDoc
362
23
    assert(m_level == 0 && "SvXMLMetaExport: level error");
363
23
}
364
365
// unfortunately, this method contains far too much ugly namespace mangling.
366
void SAL_CALL
367
SvXMLMetaExport::startElement(const OUString & i_rName,
368
    const uno::Reference< xml::sax::XAttributeList > & i_xAttribs)
369
92
{
370
371
92
    if (m_level == 0) {
372
        // namespace decls: default ones have been written at the root element
373
        // non-default ones must be preserved here
374
23
        const sal_Int16 nCount = i_xAttribs->getLength();
375
943
        for (sal_Int16 i = 0; i < nCount; ++i) {
376
920
            const OUString name(i_xAttribs->getNameByIndex(i));
377
920
            if (name.startsWith(s_xmlns)) {
378
897
                bool found(false);
379
897
                const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap());
380
897
                for (sal_uInt16 key = rNsMap.GetFirstKey();
381
17.9k
                     key != USHRT_MAX; key = rNsMap.GetNextKey(key)) {
382
17.9k
                    if (name == rNsMap.GetAttrNameByKey(key)) {
383
897
                        found = true;
384
897
                        break;
385
897
                    }
386
17.9k
                }
387
897
                if (!found) {
388
0
                    m_preservedNSs.emplace_back(name,
389
0
                        i_xAttribs->getValueByIndex(i));
390
0
                }
391
897
            }
392
920
        }
393
        // ignore the root: it has been written already
394
23
        ++m_level;
395
23
        return;
396
23
    }
397
398
69
    if (m_level == 1) {
399
        // attach preserved namespace decls from root node here
400
23
        for (const auto& rPreservedNS : m_preservedNSs) {
401
0
            const OUString ns(rPreservedNS.First);
402
0
            bool found(false);
403
            // but only if it is not already there
404
0
            const sal_Int16 nCount = i_xAttribs->getLength();
405
0
            for (sal_Int16 i = 0; i < nCount; ++i) {
406
0
                const OUString name(i_xAttribs->getNameByIndex(i));
407
0
                if (ns == name) {
408
0
                    found = true;
409
0
                    break;
410
0
                }
411
0
            }
412
0
            if (!found) {
413
0
                mrExport.AddAttribute(ns, rPreservedNS.Second);
414
0
            }
415
0
        }
416
23
    }
417
418
    // attach the attributes
419
69
    if (i_rName.startsWith(s_meta)) {
420
        // special handling for all elements that may have
421
        // xlink:href attributes; these must be made relative
422
46
        const sal_Int16 nLength = i_xAttribs->getLength();
423
69
        for (sal_Int16 i = 0; i < nLength; ++i) {
424
23
            const OUString name (i_xAttribs->getNameByIndex (i));
425
23
            OUString value(i_xAttribs->getValueByIndex(i));
426
23
            if (name.startsWith(s_href)) {
427
0
                value = mrExport.GetRelativeReference(value);
428
0
            }
429
23
            mrExport.AddAttribute(name, value);
430
23
        }
431
46
    } else {
432
23
        const sal_Int16 nLength = i_xAttribs->getLength();
433
23
        for (sal_Int16 i = 0; i < nLength; ++i) {
434
0
            const OUString name  (i_xAttribs->getNameByIndex(i));
435
0
            const OUString value (i_xAttribs->getValueByIndex(i));
436
0
            mrExport.AddAttribute(name, value);
437
0
        }
438
23
    }
439
440
    // finally, start the element
441
    // #i107240# no whitespace here, because the DOM may already contain
442
    // whitespace, which is not cleared when loading and thus accumulates.
443
69
    mrExport.StartElement(i_rName, m_level <= 1);
444
69
    ++m_level;
445
69
}
446
447
void SAL_CALL
448
SvXMLMetaExport::endElement(const OUString & i_rName)
449
92
{
450
92
    --m_level;
451
92
    if (m_level == 0) {
452
        // ignore the root; see startElement
453
23
        return;
454
23
    }
455
92
    assert(m_level >= 0 && "SvXMLMetaExport: level error");
456
69
    mrExport.EndElement(i_rName, false);
457
69
}
458
459
void SAL_CALL
460
SvXMLMetaExport::characters(const OUString & i_rChars)
461
23
{
462
23
    mrExport.Characters(i_rChars);
463
23
}
464
465
void SAL_CALL
466
SvXMLMetaExport::ignorableWhitespace(const OUString & /*i_rWhitespaces*/)
467
0
{
468
0
    mrExport.IgnorableWhitespace(/*i_rWhitespaces*/);
469
0
}
470
471
void SAL_CALL
472
SvXMLMetaExport::processingInstruction(const OUString &,
473
    const OUString &)
474
0
{
475
    // ignore; the exporter cannot handle these
476
0
}
477
478
void SAL_CALL
479
SvXMLMetaExport::setDocumentLocator(const uno::Reference<xml::sax::XLocator>&)
480
0
{
481
    // nothing to do here, move along...
482
0
}
483
484
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */