Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/configmgr/source/xcuparser.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 <sal/config.h>
21
22
#include <algorithm>
23
#include <cassert>
24
25
#include <com/sun/star/uno/Any.hxx>
26
#include <com/sun/star/uno/RuntimeException.hpp>
27
#include <rtl/ref.hxx>
28
#include <rtl/strbuf.hxx>
29
#include <rtl/string.hxx>
30
#include <rtl/ustring.hxx>
31
#include <sal/log.hxx>
32
#include <xmlreader/span.hxx>
33
#include <xmlreader/xmlreader.hxx>
34
35
#include "data.hxx"
36
#include "localizedpropertynode.hxx"
37
#include "localizedvaluenode.hxx"
38
#include "groupnode.hxx"
39
#include "modifications.hxx"
40
#include "node.hxx"
41
#include "nodemap.hxx"
42
#include "parsemanager.hxx"
43
#include "partial.hxx"
44
#include "propertynode.hxx"
45
#include "setnode.hxx"
46
#include "xcuparser.hxx"
47
#include "xmldata.hxx"
48
49
namespace configmgr {
50
51
XcuParser::XcuParser(
52
    int layer, Data & data, Partial const * partial,
53
    Modifications * broadcastModifications, Additions * additions):
54
0
    valueParser_(layer), data_(data),
55
0
    partial_(partial), broadcastModifications_(broadcastModifications),
56
0
    additions_(additions), recordModifications_(layer == Data::NO_LAYER),
57
    trackPath_(
58
0
        partial_ != nullptr || broadcastModifications_ != nullptr || additions_ != nullptr ||
59
0
        recordModifications_)
60
0
{}
61
62
0
XcuParser::~XcuParser() {}
63
64
0
xmlreader::XmlReader::Text XcuParser::getTextMode() {
65
0
    return valueParser_.getTextMode();
66
0
}
67
68
bool XcuParser::startElement(
69
    xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name,
70
    std::set< OUString > const * /*existingDependencies*/)
71
0
{
72
0
    if (valueParser_.startElement(reader, nsId, name)) {
73
0
        return true;
74
0
    }
75
0
    if (state_.empty()) {
76
0
        if (nsId == ParseManager::NAMESPACE_OOR &&
77
0
            name == "component-data")
78
0
        {
79
0
            handleComponentData(reader);
80
0
        } else if (nsId == ParseManager::NAMESPACE_OOR && name == "items")
81
0
        {
82
0
            state_.push(State::Modify(rtl::Reference< Node >()));
83
0
        } else {
84
0
            throw css::uno::RuntimeException(
85
0
                "bad root element <" + name.convertFromUtf8() + "> in " +
86
0
                reader.getUrl());
87
0
        }
88
0
    } else if (state_.top().ignore) {
89
0
        state_.push(State::Ignore(false));
90
0
    } else if (!state_.top().node.is()) {
91
0
        if (nsId != xmlreader::XmlReader::NAMESPACE_NONE || name != "item")
92
0
        {
93
0
            throw css::uno::RuntimeException(
94
0
                "bad items node member <" + name.convertFromUtf8() + "> in " +
95
0
                reader.getUrl());
96
0
        }
97
0
        handleItem(reader);
98
0
    } else {
99
0
        switch (state_.top().node->kind()) {
100
0
        case Node::KIND_PROPERTY:
101
0
            if (nsId != xmlreader::XmlReader::NAMESPACE_NONE ||
102
0
                name != "value")
103
0
            {
104
0
                throw css::uno::RuntimeException(
105
0
                    "bad property node member <" + name.convertFromUtf8() +
106
0
                    "> in " + reader.getUrl());
107
0
            }
108
0
            handlePropValue(
109
0
                reader,
110
0
                static_cast< PropertyNode * >(state_.top().node.get()));
111
0
            break;
112
0
        case Node::KIND_LOCALIZED_PROPERTY:
113
0
            if (nsId != xmlreader::XmlReader::NAMESPACE_NONE ||
114
0
                name != "value")
115
0
            {
116
0
                throw css::uno::RuntimeException(
117
0
                    "bad localized property node member <" +
118
0
                    name.convertFromUtf8() + "> in " + reader.getUrl());
119
0
            }
120
0
            handleLocpropValue(
121
0
                reader,
122
0
                static_cast< LocalizedPropertyNode * >(
123
0
                    state_.top().node.get()));
124
0
            break;
125
0
        case Node::KIND_LOCALIZED_VALUE:
126
0
            throw css::uno::RuntimeException(
127
0
                "bad member <" + name.convertFromUtf8() + "> in " +
128
0
                reader.getUrl());
129
0
        case Node::KIND_GROUP:
130
0
            if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
131
0
                name == "prop")
132
0
            {
133
0
                handleGroupProp(
134
0
                    reader,
135
0
                    static_cast< GroupNode * >(state_.top().node.get()));
136
0
            } else if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
137
0
                       name == "node")
138
0
            {
139
0
                handleGroupNode(reader, state_.top().node);
140
0
            } else {
141
0
                throw css::uno::RuntimeException(
142
0
                    "bad group node member <" + name.convertFromUtf8() +
143
0
                    "> in " + reader.getUrl());
144
0
            }
145
0
            break;
146
0
        case Node::KIND_SET:
147
0
            if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
148
0
                name == "node")
149
0
            {
150
0
                handleSetNode(
151
0
                    reader, static_cast< SetNode * >(state_.top().node.get()));
152
0
            } else if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
153
0
                       name == "prop")
154
0
            {
155
0
                SAL_WARN(
156
0
                    "configmgr",
157
0
                    "bad set node <prop> member in \"" << reader.getUrl()
158
0
                        << '"');
159
0
                state_.push(State::Ignore(false));
160
0
            } else {
161
0
                throw css::uno::RuntimeException(
162
0
                    "bad set node member <" + name.convertFromUtf8() +
163
0
                    "> in " + reader.getUrl());
164
0
            }
165
0
            break;
166
0
        case Node::KIND_ROOT:
167
0
            assert(false); // this cannot happen
168
0
            break;
169
0
        }
170
0
    }
171
0
    return true;
172
0
}
173
174
0
void XcuParser::endElement(xmlreader::XmlReader const &) {
175
0
    if (valueParser_.endElement()) {
176
0
        return;
177
0
    }
178
0
    assert(!state_.empty());
179
0
    bool pop = state_.top().pop;
180
0
    rtl::Reference< Node > insert;
181
0
    OUString name;
182
0
    if (state_.top().insert) {
183
0
        insert = state_.top().node;
184
0
        assert(insert.is());
185
0
        name = state_.top().name;
186
0
    }
187
0
    state_.pop();
188
0
    if (insert.is()) {
189
0
        assert(!state_.empty() && state_.top().node.is());
190
0
        state_.top().node->getMembers()[name] = std::move(insert);
191
0
    }
192
0
    if (pop && !path_.empty()) {
193
0
        path_.pop_back();
194
            // </item> will pop less than <item> pushed, but that is harmless,
195
            // as the next <item> will reset path_
196
0
    }
197
0
}
198
199
0
void XcuParser::characters(xmlreader::Span const & text) {
200
0
    valueParser_.characters(text);
201
0
}
202
203
0
XcuParser::Operation XcuParser::parseOperation(xmlreader::Span const & text) {
204
0
    assert(text.is());
205
0
    if (text == "modify") {
206
0
        return OPERATION_MODIFY;
207
0
    }
208
0
    if (text == "replace") {
209
0
        return OPERATION_REPLACE;
210
0
    }
211
0
    if (text == "fuse") {
212
0
        return OPERATION_FUSE;
213
0
    }
214
0
    if (text == "remove") {
215
0
        return OPERATION_REMOVE;
216
0
    }
217
0
    throw css::uno::RuntimeException(
218
0
        "invalid op " + text.convertFromUtf8());
219
0
}
220
221
0
bool XcuParser::isAlreadyFinalized(int finalizedLayer) const {
222
0
    return finalizedLayer != Data::NO_LAYER && finalizedLayer <= valueParser_.getLayer();
223
0
}
224
225
0
void XcuParser::handleComponentData(xmlreader::XmlReader & reader) {
226
0
    OStringBuffer buf(256);
227
0
    buf.append('.');
228
0
    bool hasPackage = false;
229
0
    bool hasName = false;
230
0
    Operation op = OPERATION_MODIFY;
231
0
    bool finalized = false;
232
0
    for (;;) {
233
0
        int attrNsId;
234
0
        xmlreader::Span attrLn;
235
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
236
0
            break;
237
0
        }
238
0
        if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package")
239
0
        {
240
0
            if (hasPackage) {
241
0
                throw css::uno::RuntimeException(
242
0
                    "multiple component-update package attributes in " +
243
0
                    reader.getUrl());
244
0
            }
245
0
            hasPackage = true;
246
0
            xmlreader::Span s(reader.getAttributeValue(false));
247
0
            buf.insert(0, s.begin, s.length);
248
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
249
0
                   attrLn == "name")
250
0
        {
251
0
            if (hasName) {
252
0
                throw css::uno::RuntimeException(
253
0
                    "multiple component-update name attributes in " +
254
0
                    reader.getUrl());
255
0
            }
256
0
            hasName = true;
257
0
            xmlreader::Span s(reader.getAttributeValue(false));
258
0
            buf.append(s.begin, s.length);
259
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
260
0
                   attrLn == "op")
261
0
        {
262
0
            op = parseOperation(reader.getAttributeValue(true));
263
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
264
0
                   attrLn == "finalized")
265
0
        {
266
0
            finalized = xmldata::parseBoolean(reader.getAttributeValue(true));
267
0
        }
268
0
    }
269
0
    if (!hasPackage) {
270
0
        throw css::uno::RuntimeException(
271
0
            "no component-data package attribute in " + reader.getUrl());
272
0
    }
273
0
    if (!hasName) {
274
0
        throw css::uno::RuntimeException(
275
0
            "no component-data name attribute in " + reader.getUrl());
276
0
    }
277
0
    componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()).
278
0
        convertFromUtf8();
279
0
    if (trackPath_) {
280
0
        assert(path_.empty());
281
0
        path_.push_back(componentName_);
282
0
        if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT)
283
0
        {
284
0
            state_.push(State::Ignore(true));
285
0
            return;
286
0
        }
287
0
    }
288
0
    rtl::Reference< Node > node(
289
0
        data_.getComponents().findNode(valueParser_.getLayer(),
290
0
                                       componentName_));
291
0
    if (!node.is()) {
292
0
        SAL_WARN(
293
0
            "configmgr",
294
0
            "unknown component \"" << componentName_ << "\" in \""
295
0
                << reader.getUrl() << '"');
296
0
        state_.push(State::Ignore(true));
297
0
        return;
298
0
    }
299
0
    switch (op) {
300
0
    case OPERATION_MODIFY:
301
0
    case OPERATION_FUSE:
302
0
        break;
303
0
    default:
304
0
        throw css::uno::RuntimeException(
305
0
            "invalid operation on root node in " + reader.getUrl());
306
0
    }
307
0
    if (isAlreadyFinalized(node->getFinalized())) {
308
0
        state_.push(State::Ignore(true));
309
0
        return;
310
0
    }
311
0
    if (finalized) {
312
0
        node->setFinalized(valueParser_.getLayer());
313
0
    }
314
0
    state_.push(State::Modify(node));
315
0
}
316
317
0
void XcuParser::handleItem(xmlreader::XmlReader & reader) {
318
0
    xmlreader::Span attrPath;
319
0
    for (;;) {
320
0
        int attrNsId;
321
0
        xmlreader::Span attrLn;
322
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
323
0
            break;
324
0
        }
325
0
        if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "path") {
326
0
            attrPath = reader.getAttributeValue(false);
327
0
        }
328
0
    }
329
0
    if (!attrPath.is()) {
330
0
        throw css::uno::RuntimeException(
331
0
            "missing path attribute in " + reader.getUrl());
332
0
    }
333
0
    OUString path(attrPath.convertFromUtf8());
334
0
    int finalizedLayer;
335
0
    rtl::Reference< Node > node(
336
0
        data_.resolvePathRepresentation(
337
0
            path, nullptr, &path_, &finalizedLayer));
338
0
    if (!node.is()) {
339
0
        SAL_WARN(
340
0
            "configmgr",
341
0
            "unknown item \"" << path << "\" in \"" << reader.getUrl() << '"');
342
0
        state_.push(State::Ignore(true));
343
0
        return;
344
0
    }
345
0
    assert(!path_.empty());
346
0
    componentName_ = path_.front();
347
0
    if (trackPath_) {
348
0
        if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT)
349
0
        {
350
0
            state_.push(State::Ignore(true));
351
0
            return;
352
0
        }
353
0
    } else {
354
0
        path_.clear();
355
0
    }
356
0
    switch (node->kind()) {
357
0
    case Node::KIND_PROPERTY:
358
0
    case Node::KIND_LOCALIZED_VALUE:
359
0
        SAL_WARN(
360
0
            "configmgr",
361
0
            "item of bad type \"" << path << "\" in \"" << reader.getUrl()
362
0
                << '"');
363
0
        state_.push(State::Ignore(true));
364
0
        return;
365
0
    case Node::KIND_LOCALIZED_PROPERTY:
366
0
        valueParser_.type_ = static_cast< LocalizedPropertyNode * >(
367
0
            node.get())->getStaticType();
368
0
        break;
369
0
    default:
370
0
        break;
371
0
    }
372
0
    if (isAlreadyFinalized(finalizedLayer)) {
373
0
        state_.push(State::Ignore(true));
374
0
        return;
375
0
    }
376
0
    state_.push(State::Modify(node));
377
0
}
378
379
void XcuParser::handlePropValue(
380
    xmlreader::XmlReader & reader, PropertyNode * prop)
381
0
 {
382
0
    bool nil = false;
383
0
    OString separator;
384
0
    OUString external;
385
0
    for (;;) {
386
0
        int attrNsId;
387
0
        xmlreader::Span attrLn;
388
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
389
0
            break;
390
0
        }
391
0
        if (attrNsId == ParseManager::NAMESPACE_XSI && attrLn == "nil") {
392
0
            nil = xmldata::parseBoolean(reader.getAttributeValue(true));
393
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
394
0
                   attrLn == "type")
395
0
        {
396
0
            Type type = xmldata::parseType(
397
0
                reader, reader.getAttributeValue(true));
398
0
            if (valueParser_.type_ != TYPE_ANY && type != valueParser_.type_) {
399
0
                throw css::uno::RuntimeException(
400
0
                    "invalid value type in " + reader.getUrl());
401
0
            }
402
0
            valueParser_.type_ = type;
403
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
404
0
                   attrLn == "separator")
405
0
        {
406
0
            xmlreader::Span s(reader.getAttributeValue(false));
407
0
            if (s.length == 0) {
408
0
                throw css::uno::RuntimeException(
409
0
                    "bad oor:separator attribute in " + reader.getUrl());
410
0
            }
411
0
            separator = OString(s.begin, s.length);
412
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
413
0
                   attrLn == "external")
414
0
        {
415
0
            external = reader.getAttributeValue(true).convertFromUtf8();
416
0
            if (external.isEmpty()) {
417
0
                throw css::uno::RuntimeException(
418
0
                    "bad oor:external attribute value in " + reader.getUrl());
419
0
            }
420
0
        }
421
0
    }
422
0
    if (nil) {
423
0
        if (!prop->isNillable()) {
424
0
            throw css::uno::RuntimeException(
425
0
                "xsi:nil attribute for non-nillable prop in " + reader.getUrl());
426
0
        }
427
0
        if (!external.isEmpty()) {
428
0
            throw css::uno::RuntimeException(
429
0
                "xsi:nil and oor:external attributes for prop in " +
430
0
                reader.getUrl());
431
0
        }
432
0
        prop->setValue(valueParser_.getLayer(), css::uno::Any(), valueParser_.getLayer() == Data::NO_LAYER);
433
0
        state_.push(State::Ignore(false));
434
0
    } else if (external.isEmpty()) {
435
0
        valueParser_.separator_ = separator;
436
0
        valueParser_.start(prop);
437
0
    } else {
438
0
        prop->setExternal(valueParser_.getLayer(), external);
439
0
        state_.push(State::Ignore(false));
440
0
    }
441
0
}
442
443
void XcuParser::handleLocpropValue(
444
    xmlreader::XmlReader & reader, LocalizedPropertyNode * locprop)
445
0
{
446
0
    OUString name;
447
0
    bool nil = false;
448
0
    OString separator;
449
0
    Operation op = OPERATION_FUSE;
450
0
    for (;;) {
451
0
        int attrNsId;
452
0
        xmlreader::Span attrLn;
453
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
454
0
            break;
455
0
        }
456
0
        if (attrNsId == xmlreader::XmlReader::NAMESPACE_XML &&
457
0
            attrLn == "lang")
458
0
        {
459
0
            name = reader.getAttributeValue(false).convertFromUtf8();
460
0
        } else if (attrNsId == ParseManager::NAMESPACE_XSI &&
461
0
                   attrLn == "nil")
462
0
        {
463
0
            nil = xmldata::parseBoolean(reader.getAttributeValue(true));
464
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
465
0
                   attrLn == "type")
466
0
        {
467
0
            Type type = xmldata::parseType(
468
0
                reader, reader.getAttributeValue(true));
469
0
            if (valueParser_.type_ != TYPE_ANY && type != valueParser_.type_) {
470
0
                throw css::uno::RuntimeException(
471
0
                    "invalid value type in " + reader.getUrl());
472
0
            }
473
0
            valueParser_.type_ = type;
474
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
475
0
                   attrLn == "separator")
476
0
        {
477
0
            xmlreader::Span s(reader.getAttributeValue(false));
478
0
            if (s.length == 0) {
479
0
                throw css::uno::RuntimeException(
480
0
                    "bad oor:separator attribute in " + reader.getUrl());
481
0
            }
482
0
            separator = OString(s.begin, s.length);
483
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
484
0
                   attrLn == "op")
485
0
        {
486
0
            op = parseOperation(reader.getAttributeValue(true));
487
0
        }
488
0
    }
489
0
    if (trackPath_) {
490
0
        path_.push_back(name);
491
0
        if (partial_ != nullptr &&
492
0
            partial_->contains(path_) != Partial::CONTAINS_NODE)
493
0
        {
494
0
            state_.push(State::Ignore(true));
495
0
            return;
496
0
        }
497
0
    }
498
0
    NodeMap & members = locprop->getMembers();
499
0
    NodeMap::iterator i(members.find(name));
500
0
    if (i != members.end() && i->second->getLayer() > valueParser_.getLayer()) {
501
0
        state_.push(State::Ignore(true));
502
0
        return;
503
0
    }
504
0
    if (nil && !locprop->isNillable()) {
505
0
        throw css::uno::RuntimeException(
506
0
            "xsi:nil attribute for non-nillable prop in " + reader.getUrl());
507
0
    }
508
0
    switch (op) {
509
0
    case OPERATION_FUSE:
510
0
        {
511
0
            bool pop = false;
512
0
            if (nil) {
513
0
                if (i == members.end()) {
514
0
                    members[name] = new LocalizedValueNode(
515
0
                        valueParser_.getLayer(), css::uno::Any());
516
0
                } else {
517
0
                    static_cast< LocalizedValueNode * >(
518
0
                        i->second.get())->setValue(
519
0
                            valueParser_.getLayer(), css::uno::Any(), valueParser_.getLayer() == Data::NO_LAYER);
520
0
                }
521
0
                state_.push(State::Ignore(true));
522
0
            } else {
523
0
                valueParser_.separator_ = separator;
524
0
                valueParser_.start(locprop, name);
525
0
                pop = true;
526
0
            }
527
0
            if (trackPath_) {
528
0
                recordModification(false);
529
0
                if (pop) {
530
0
                    path_.pop_back();
531
0
                }
532
0
            }
533
0
        }
534
0
        break;
535
0
    case OPERATION_REMOVE:
536
        //TODO: only allow if parent.op == OPERATION_FUSE
537
        //TODO: disallow removing when e.g. lang=""?
538
0
        if (i != members.end()) {
539
0
            members.erase(i);
540
0
        }
541
0
        state_.push(State::Ignore(true));
542
0
        recordModification(false);
543
0
        break;
544
0
    default:
545
0
        throw css::uno::RuntimeException(
546
0
            "bad op attribute for value element in " + reader.getUrl());
547
0
    }
548
0
}
549
550
void XcuParser::handleGroupProp(
551
    xmlreader::XmlReader & reader, GroupNode * group)
552
0
{
553
0
    bool hasName = false;
554
0
    OUString name;
555
0
    Type type = TYPE_ERROR;
556
0
    Operation op = OPERATION_MODIFY;
557
0
    bool finalized = false;
558
0
    for (;;) {
559
0
        int attrNsId;
560
0
        xmlreader::Span attrLn;
561
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
562
0
            break;
563
0
        }
564
0
        if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
565
0
            hasName = true;
566
0
            name = reader.getAttributeValue(false).convertFromUtf8();
567
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
568
0
                   attrLn == "type")
569
0
        {
570
0
            type = xmldata::parseType(reader, reader.getAttributeValue(true));
571
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
572
0
                   attrLn == "op")
573
0
        {
574
0
            op = parseOperation(reader.getAttributeValue(true));
575
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
576
0
                   attrLn == "finalized")
577
0
        {
578
0
            finalized = xmldata::parseBoolean(reader.getAttributeValue(true));
579
0
        }
580
0
    }
581
0
    if (!hasName) {
582
0
        throw css::uno::RuntimeException(
583
0
            "no prop name attribute in " + reader.getUrl());
584
0
    }
585
0
    if (trackPath_) {
586
0
        path_.push_back(name);
587
        //TODO: This ignores locprop values for which specific include paths
588
        // exist (i.e., for which contains(locprop path) = CONTAINS_SUBNODES):
589
0
        if (partial_ != nullptr &&
590
0
            partial_->contains(path_) != Partial::CONTAINS_NODE)
591
0
        {
592
0
            state_.push(State::Ignore(true));
593
0
            return;
594
0
        }
595
0
    }
596
0
    NodeMap & members = group->getMembers();
597
0
    NodeMap::iterator i(members.find(name));
598
0
    if (i == members.end()) {
599
0
        handleUnknownGroupProp(reader, group, name, type, op, finalized);
600
0
    } else {
601
0
        switch (i->second->kind()) {
602
0
        case Node::KIND_PROPERTY:
603
0
            handlePlainGroupProp(reader, group, i, name, type, op, finalized);
604
0
            break;
605
0
        case Node::KIND_LOCALIZED_PROPERTY:
606
0
            handleLocalizedGroupProp(
607
0
                reader,
608
0
                static_cast< LocalizedPropertyNode * >(i->second.get()), name,
609
0
                type, op, finalized);
610
0
            break;
611
0
        default:
612
0
            throw css::uno::RuntimeException(
613
0
                "inappropriate prop " + name + " in " + reader.getUrl());
614
0
        }
615
0
    }
616
0
}
617
618
void XcuParser::handleUnknownGroupProp(
619
    xmlreader::XmlReader const & reader, GroupNode const * group,
620
    OUString const & name, Type type, Operation operation, bool finalized)
621
0
{
622
0
    switch (operation) {
623
0
    case OPERATION_REPLACE:
624
0
    case OPERATION_FUSE:
625
0
        if (group->isExtensible()) {
626
0
            if (type == TYPE_ERROR) {
627
0
                throw css::uno::RuntimeException(
628
0
                    "missing type attribute for prop " + name + " in " +
629
0
                    reader.getUrl());
630
0
            }
631
0
            valueParser_.type_ = type;
632
0
            rtl::Reference< Node > prop(
633
0
                new PropertyNode(
634
0
                    valueParser_.getLayer(), TYPE_ANY, true, css::uno::Any(),
635
0
                    true));
636
0
            if (finalized) {
637
0
                prop->setFinalized(valueParser_.getLayer());
638
0
            }
639
0
            state_.push(State::Insert(prop, name));
640
0
            recordModification(false);
641
0
            break;
642
0
        }
643
0
        [[fallthrough]];
644
0
    default:
645
0
        SAL_WARN(
646
0
            "configmgr",
647
0
            "unknown property \"" << name << "\" in \"" << reader.getUrl()
648
0
                << '"');
649
0
        state_.push(State::Ignore(true));
650
0
        break;
651
0
    }
652
0
}
653
654
void XcuParser::handlePlainGroupProp(
655
    xmlreader::XmlReader const & reader, GroupNode * group,
656
    NodeMap::iterator const & propertyIndex, std::u16string_view name,
657
    Type type, Operation operation, bool finalized)
658
0
{
659
0
    PropertyNode * property = static_cast< PropertyNode * >(
660
0
        propertyIndex->second.get());
661
0
    if (property->getLayer() > valueParser_.getLayer()) {
662
0
        state_.push(State::Ignore(true));
663
0
        return;
664
0
    }
665
0
    if (isAlreadyFinalized(property->getFinalized())) {
666
0
        state_.push(State::Ignore(true));
667
0
        return;
668
0
    }
669
0
    if (finalized) {
670
0
        property->setFinalized(valueParser_.getLayer());
671
0
    }
672
0
    if (type != TYPE_ERROR && property->getStaticType() != TYPE_ANY &&
673
0
        type != property->getStaticType())
674
0
    {
675
0
        throw css::uno::RuntimeException(
676
0
            OUString::Concat("invalid type for prop ") + name + " in " + reader.getUrl());
677
0
    }
678
0
    valueParser_.type_ = type == TYPE_ERROR ? property->getStaticType() : type;
679
0
    switch (operation) {
680
0
    case OPERATION_MODIFY:
681
0
    case OPERATION_REPLACE:
682
0
    case OPERATION_FUSE:
683
0
        state_.push(State::Modify(property));
684
0
        recordModification(false);
685
0
        break;
686
0
    case OPERATION_REMOVE:
687
0
        if (!property->isExtension()) {
688
0
            throw css::uno::RuntimeException(
689
0
                OUString::Concat("invalid remove of non-extension prop ") + name + " in " +
690
0
                reader.getUrl());
691
0
        }
692
0
        group->getMembers().erase(propertyIndex);
693
0
        state_.push(State::Ignore(true));
694
0
        recordModification(false);
695
0
        break;
696
0
    }
697
0
}
698
699
void XcuParser::handleLocalizedGroupProp(
700
    xmlreader::XmlReader const & reader, LocalizedPropertyNode * property,
701
    OUString const & name, Type type, Operation operation, bool finalized)
702
0
{
703
0
    if (property->getLayer() > valueParser_.getLayer()) {
704
0
        state_.push(State::Ignore(true));
705
0
        return;
706
0
    }
707
0
    if (isAlreadyFinalized(property->getFinalized())) {
708
0
        state_.push(State::Ignore(true));
709
0
        return;
710
0
    }
711
0
    if (finalized) {
712
0
        property->setFinalized(valueParser_.getLayer());
713
0
    }
714
0
    if (type != TYPE_ERROR && property->getStaticType() != TYPE_ANY &&
715
0
        type != property->getStaticType())
716
0
    {
717
0
        throw css::uno::RuntimeException(
718
0
            "invalid type for prop " + name + " in " + reader.getUrl());
719
0
    }
720
0
    valueParser_.type_ = type == TYPE_ERROR ? property->getStaticType() : type;
721
0
    switch (operation) {
722
0
    case OPERATION_MODIFY:
723
0
    case OPERATION_FUSE:
724
0
        state_.push(State::Modify(property));
725
0
        break;
726
0
    case OPERATION_REPLACE:
727
0
        {
728
0
            rtl::Reference< Node > replacement(
729
0
                new LocalizedPropertyNode(
730
0
                    valueParser_.getLayer(), property->getStaticType(),
731
0
                    property->isNillable()));
732
0
            replacement->setFinalized(property->getFinalized());
733
0
            state_.push(State::Insert(replacement, name));
734
0
            recordModification(false);
735
0
        }
736
0
        break;
737
0
    case OPERATION_REMOVE:
738
0
        throw css::uno::RuntimeException(
739
0
            "invalid remove of non-extension prop " + name + " in " +
740
0
            reader.getUrl());
741
0
    }
742
0
}
743
744
void XcuParser::handleGroupNode(
745
    xmlreader::XmlReader & reader, rtl::Reference< Node > const & group)
746
0
{
747
0
    bool hasName = false;
748
0
    OUString name;
749
0
    Operation op = OPERATION_MODIFY;
750
0
    bool finalized = false;
751
0
    for (;;) {
752
0
        int attrNsId;
753
0
        xmlreader::Span attrLn;
754
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
755
0
            break;
756
0
        }
757
0
        if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
758
0
            hasName = true;
759
0
            name = reader.getAttributeValue(false).convertFromUtf8();
760
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
761
0
                   attrLn == "op")
762
0
        {
763
0
            op = parseOperation(reader.getAttributeValue(true));
764
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
765
0
                   attrLn == "finalized")
766
0
        {
767
0
            finalized = xmldata::parseBoolean(reader.getAttributeValue(true));
768
0
        }
769
0
    }
770
0
    if (!hasName) {
771
0
        throw css::uno::RuntimeException(
772
0
            "no node name attribute in " + reader.getUrl());
773
0
    }
774
0
    if (trackPath_) {
775
0
        path_.push_back(name);
776
0
        if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT)
777
0
        {
778
0
            state_.push(State::Ignore(true));
779
0
            return;
780
0
        }
781
0
    }
782
0
    rtl::Reference< Node > child(
783
0
        group->getMembers().findNode(valueParser_.getLayer(), name));
784
0
    if (!child.is()) {
785
0
        SAL_WARN(
786
0
            "configmgr",
787
0
            "unknown node \"" << name << "\" in \"" << reader.getUrl() << '"');
788
0
        state_.push(State::Ignore(true));
789
0
        return;
790
0
    }
791
0
    Node::Kind kind = child->kind();
792
0
    if (kind != Node::KIND_GROUP && kind != Node::KIND_SET) {
793
0
        throw css::uno::RuntimeException(
794
0
            "bad <node> \"" + name + "\" of non group/set kind in " +
795
0
            reader.getUrl());
796
0
    }
797
0
    if (op != OPERATION_MODIFY && op != OPERATION_FUSE) {
798
0
        throw css::uno::RuntimeException(
799
0
            "invalid operation on group node in " + reader.getUrl());
800
0
    }
801
0
    if (isAlreadyFinalized(child->getFinalized())) {
802
0
        state_.push(State::Ignore(true));
803
0
        return;
804
0
    }
805
0
    if (finalized) {
806
0
        child->setFinalized(valueParser_.getLayer());
807
0
    }
808
0
    state_.push(State::Modify(child));
809
0
}
810
811
0
void XcuParser::handleSetNode(xmlreader::XmlReader & reader, SetNode * set) {
812
0
    bool hasName = false;
813
0
    OUString name;
814
0
    OUString component(componentName_);
815
0
    bool hasNodeType = false;
816
0
    OUString nodeType;
817
0
    Operation op = OPERATION_MODIFY;
818
0
    bool finalized = false;
819
0
    bool mandatory = false;
820
0
    for (;;) {
821
0
        int attrNsId;
822
0
        xmlreader::Span attrLn;
823
0
        if (!reader.nextAttribute(&attrNsId, &attrLn)) {
824
0
            break;
825
0
        }
826
0
        if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
827
0
            hasName = true;
828
0
            name = reader.getAttributeValue(false).convertFromUtf8();
829
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
830
0
                   attrLn == "component")
831
0
        {
832
0
            component = reader.getAttributeValue(false).convertFromUtf8();
833
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
834
0
                   attrLn == "node-type")
835
0
        {
836
0
            hasNodeType = true;
837
0
            nodeType = reader.getAttributeValue(false).convertFromUtf8();
838
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
839
0
                   attrLn == "op")
840
0
        {
841
0
            op = parseOperation(reader.getAttributeValue(true));
842
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
843
0
                   attrLn == "finalized")
844
0
        {
845
0
            finalized = xmldata::parseBoolean(reader.getAttributeValue(true));
846
0
        } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
847
0
                   attrLn == "mandatory")
848
0
        {
849
0
            mandatory = xmldata::parseBoolean(reader.getAttributeValue(true));
850
0
        }
851
0
    }
852
0
    if (!hasName) {
853
0
        throw css::uno::RuntimeException(
854
0
            "no node name attribute in " + reader.getUrl());
855
0
    }
856
0
    if (trackPath_) {
857
0
        path_.push_back(name);
858
0
        if (partial_ != nullptr && partial_->contains(path_) == Partial::CONTAINS_NOT)
859
0
        {
860
0
            state_.push(State::Ignore(true));
861
0
            return;
862
0
        }
863
0
    }
864
0
    OUString templateName(
865
0
        xmldata::parseTemplateReference(
866
0
            component, hasNodeType, nodeType, &set->getDefaultTemplateName()));
867
0
    if (!set->isValidTemplate(templateName)) {
868
0
        throw css::uno::RuntimeException(
869
0
            "set member node " + name + " references invalid template " +
870
0
            templateName + " in " + reader.getUrl());
871
0
    }
872
0
    rtl::Reference< Node > tmpl(
873
0
        data_.getTemplate(valueParser_.getLayer(), templateName));
874
0
    if (!tmpl.is()) {
875
0
        throw css::uno::RuntimeException(
876
0
            "set member node " + name + " references undefined template " +
877
0
            templateName + " in " + reader.getUrl());
878
0
    }
879
0
    bool alreadyFinalized = false;
880
0
    int finalizedLayer = finalized ? valueParser_.getLayer() : Data::NO_LAYER;
881
0
    int mandatoryLayer = mandatory ? valueParser_.getLayer() : Data::NO_LAYER;
882
0
    NodeMap & members = set->getMembers();
883
0
    NodeMap::iterator i(members.find(name));
884
0
    if (i != members.end()) {
885
0
        auto const fin = i->second->getFinalized();
886
0
        alreadyFinalized = isAlreadyFinalized(fin);
887
0
        finalizedLayer = std::min(finalizedLayer, fin);
888
0
        i->second->setFinalized(finalizedLayer);
889
0
        mandatoryLayer = std::min(mandatoryLayer, i->second->getMandatory());
890
0
        i->second->setMandatory(mandatoryLayer);
891
0
        if (i->second->getLayer() > valueParser_.getLayer()) {
892
0
            state_.push(State::Ignore(true));
893
0
            return;
894
0
        }
895
0
    }
896
0
    if (alreadyFinalized) {
897
0
        state_.push(State::Ignore(true));
898
0
        return;
899
0
    }
900
0
    switch (op) {
901
0
    case OPERATION_MODIFY:
902
0
        if (i == members.end()) {
903
0
            SAL_WARN(
904
0
                "configmgr",
905
0
                "ignoring modify of unknown set member node \"" << name
906
0
                    << "\" in \"" << reader.getUrl() << '"');
907
0
            state_.push(State::Ignore(true));
908
0
        } else {
909
0
            state_.push(State::Modify(i->second));
910
0
        }
911
0
        break;
912
0
    case OPERATION_REPLACE:
913
0
        {
914
0
            rtl::Reference< Node > member(tmpl->clone(true));
915
0
            member->setLayer(valueParser_.getLayer());
916
0
            member->setFinalized(finalizedLayer);
917
0
            member->setMandatory(mandatoryLayer);
918
0
            state_.push(State::Insert(member, name));
919
0
            recordModification(i == members.end());
920
0
        }
921
0
        break;
922
0
    case OPERATION_FUSE:
923
0
        if (i == members.end()) {
924
0
            rtl::Reference< Node > member(tmpl->clone(true));
925
0
            member->setLayer(valueParser_.getLayer());
926
0
            member->setFinalized(finalizedLayer);
927
0
            member->setMandatory(mandatoryLayer);
928
0
            state_.push(State::Insert(member, name));
929
0
            recordModification(true);
930
0
        } else {
931
0
            state_.push(State::Modify(i->second));
932
0
        }
933
0
        break;
934
0
    case OPERATION_REMOVE:
935
0
        {
936
            // Ignore removal of unknown members and members made mandatory in
937
            // this or a lower layer; forget about user-layer removals that no
938
            // longer remove anything (so that paired additions/removals in the
939
            // user layer do not grow registrymodifications.xcu unbounded):
940
0
            bool known = i != members.end();
941
0
            if (known &&
942
0
                (mandatoryLayer == Data::NO_LAYER ||
943
0
                 mandatoryLayer > valueParser_.getLayer()))
944
0
            {
945
0
                members.erase(i);
946
0
            }
947
0
            state_.push(State::Ignore(true));
948
0
            if (known) {
949
0
                recordModification(false);
950
0
            }
951
0
            break;
952
0
        }
953
0
    }
954
0
}
955
956
0
void XcuParser::recordModification(bool addition) {
957
0
    if (broadcastModifications_ != nullptr) {
958
0
        broadcastModifications_->add(path_);
959
0
    }
960
0
    if (addition && additions_ != nullptr) {
961
0
        additions_->push_back(path_);
962
0
    }
963
0
    if (recordModifications_) {
964
0
        data_.modifications.add(path_);
965
0
    }
966
0
}
967
968
}
969
970
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */