tdf#81880 Search the Gallery
[LibreOffice.git] / configmgr / source / xcsparser.cxx
blobe70ddac6a6c9b2a572bb4e5e6017fc64ad7775c8
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 <sal/config.h>
22 #include <cassert>
23 #include <set>
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 <xmlreader/span.hxx>
32 #include <xmlreader/xmlreader.hxx>
34 #include "data.hxx"
35 #include "localizedpropertynode.hxx"
36 #include "groupnode.hxx"
37 #include "node.hxx"
38 #include "nodemap.hxx"
39 #include "parsemanager.hxx"
40 #include "propertynode.hxx"
41 #include "setnode.hxx"
42 #include "xcsparser.hxx"
43 #include "xmldata.hxx"
45 namespace configmgr {
47 namespace {
49 // Conservatively merge a template or component (and its recursive parts) into
50 // an existing instance:
51 void merge(
52 rtl::Reference< Node > const & original,
53 rtl::Reference< Node > const & update)
55 assert(
56 original.is() && update.is() && original->kind() == update->kind() &&
57 update->getFinalized() == Data::NO_LAYER);
58 if (update->getLayer() < original->getLayer() ||
59 update->getLayer() > original->getFinalized())
60 return;
62 switch (original->kind()) {
63 case Node::KIND_PROPERTY:
64 case Node::KIND_LOCALIZED_PROPERTY:
65 case Node::KIND_LOCALIZED_VALUE:
66 break; //TODO: merge certain parts?
67 case Node::KIND_GROUP:
68 for (auto const& updateMember : update->getMembers())
70 NodeMap & members = original->getMembers();
71 NodeMap::iterator i1(members.find(updateMember.first));
72 if (i1 == members.end()) {
73 if (updateMember.second->kind() == Node::KIND_PROPERTY &&
74 static_cast< GroupNode * >(
75 original.get())->isExtensible())
77 members.insert(updateMember);
79 } else if (updateMember.second->kind() == i1->second->kind()) {
80 merge(i1->second, updateMember.second);
83 break;
84 case Node::KIND_SET:
85 for (auto const& updateMember : update->getMembers())
87 NodeMap & members = original->getMembers();
88 NodeMap::iterator i1(members.find(updateMember.first));
89 if (i1 == members.end()) {
90 if (static_cast< SetNode * >(original.get())->
91 isValidTemplate(updateMember.second->getTemplateName()))
93 members.insert(updateMember);
95 } else if (updateMember.second->kind() == i1->second->kind() &&
96 (updateMember.second->getTemplateName() ==
97 i1->second->getTemplateName()))
99 merge(i1->second, updateMember.second);
102 break;
103 case Node::KIND_ROOT:
104 assert(false); // this cannot happen
105 break;
111 XcsParser::XcsParser(int layer, Data & data):
112 valueParser_(layer), data_(data), state_(STATE_START), ignoring_(), bIsParsingInfo_(false)
115 XcsParser::~XcsParser() {}
117 xmlreader::XmlReader::Text XcsParser::getTextMode() {
118 if (bIsParsingInfo_)
119 return xmlreader::XmlReader::Text::Raw;
120 return valueParser_.getTextMode();
123 bool XcsParser::startElement(
124 xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name,
125 std::set< OUString > const * /*existingDependencies*/)
127 //TODO: ignoring component-schema import, component-schema uses, and
128 // prop constraints; accepting all four at illegal places (and with
129 // illegal content):
130 if (ignoring_ > 0
131 || (nsId == xmlreader::XmlReader::NAMESPACE_NONE
132 && (name == "import" || name == "uses" || name == "constraints" || name == "desc")))
134 assert(ignoring_ < LONG_MAX);
135 ++ignoring_;
136 return true;
139 if (bIsParsingInfo_)
140 return true;
141 if (valueParser_.startElement(reader, nsId, name)) {
142 return true;
144 if (state_ == STATE_START) {
145 if (nsId == ParseManager::NAMESPACE_OOR &&
146 name == "component-schema")
148 handleComponentSchema(reader);
149 state_ = STATE_COMPONENT_SCHEMA;
150 ignoring_ = 0;
151 return true;
153 } else {
154 switch (state_) {
155 case STATE_COMPONENT_SCHEMA:
156 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
157 name == "templates")
159 state_ = STATE_TEMPLATES;
160 return true;
162 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
163 name == "info")
165 bIsParsingInfo_ = true;
166 return true;
168 [[fallthrough]];
169 case STATE_TEMPLATES_DONE:
170 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
171 name == "component")
173 state_ = STATE_COMPONENT;
174 assert(elements_.empty());
175 elements_.push(
176 Element(
177 new GroupNode(valueParser_.getLayer(), false, ""),
178 componentName_));
179 return true;
181 break;
182 case STATE_TEMPLATES:
183 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
184 name == "info")
186 bIsParsingInfo_ = true;
187 return true;
189 if (elements_.empty()) {
190 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
191 name == "group")
193 handleGroup(reader, true);
194 return true;
196 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
197 name == "set")
199 handleSet(reader, true);
200 return true;
202 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
203 name == "info")
205 bIsParsingInfo_ = true;
206 return true;
208 break;
210 [[fallthrough]];
211 case STATE_COMPONENT:
212 assert(!elements_.empty());
213 switch (elements_.top().node->kind()) {
214 case Node::KIND_PROPERTY:
215 case Node::KIND_LOCALIZED_PROPERTY:
216 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
217 name == "value")
219 handlePropValue(reader, elements_.top().node);
220 return true;
222 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
223 name == "info")
225 bIsParsingInfo_ = true;
226 return true;
228 break;
229 case Node::KIND_GROUP:
230 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
231 name == "prop")
233 handleProp(reader);
234 return true;
236 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
237 name == "node-ref")
239 handleNodeRef(reader);
240 return true;
242 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
243 name == "group")
245 handleGroup(reader, false);
246 return true;
248 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
249 name == "set")
251 handleSet(reader, false);
252 return true;
254 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
255 name == "info")
257 bIsParsingInfo_ = true;
258 return true;
260 break;
261 case Node::KIND_SET:
262 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
263 name == "item")
265 handleSetItem(
266 reader,
267 static_cast< SetNode * >(elements_.top().node.get()));
268 return true;
270 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
271 name == "info")
273 bIsParsingInfo_ = true;
274 return true;
276 break;
277 default: // Node::KIND_LOCALIZED_VALUE
278 assert(false); // this cannot happen
279 break;
281 break;
282 case STATE_COMPONENT_DONE:
283 break;
284 default: // STATE_START
285 assert(false); // this cannot happen
286 break;
289 throw css::uno::RuntimeException(
290 "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl());
293 void XcsParser::endElement(xmlreader::XmlReader const & reader) {
294 if (ignoring_ > 0) {
295 --ignoring_;
296 return;
298 if (bIsParsingInfo_)
300 bIsParsingInfo_ = false;
301 return;
303 if (valueParser_.endElement()) {
304 return;
306 if (!elements_.empty()) {
307 Element top(std::move(elements_.top()));
308 elements_.pop();
309 if (top.node.is()) {
310 if (top.node->kind() == Node::KIND_PROPERTY
311 || top.node->kind() == Node::KIND_LOCALIZED_PROPERTY)
313 // Remove whitespace from description_ resulting from line breaks/indentation in xml files
314 OUString desc(description_.makeStringAndClear());
315 desc = desc.trim();
316 while (desc.indexOf(" ") != -1)
317 desc = desc.replaceAll(" ", " ");
318 top.node->setDescription(desc);
320 if (elements_.empty()) {
321 switch (state_) {
322 case STATE_TEMPLATES:
324 auto itPair = data_.templates.insert({top.name, top.node});
325 if (!itPair.second) {
326 merge(itPair.first->second, top.node);
329 break;
330 case STATE_COMPONENT:
332 NodeMap & components = data_.getComponents();
333 auto itPair = components.insert({top.name, top.node});
334 if (!itPair.second) {
335 merge(itPair.first->second, top.node);
337 state_ = STATE_COMPONENT_DONE;
339 break;
340 default:
341 assert(false);
342 throw css::uno::RuntimeException(
343 "this cannot happen");
345 } else {
346 if (!elements_.top().node->getMembers().insert(
347 NodeMap::value_type(top.name, top.node)).second)
349 throw css::uno::RuntimeException(
350 "duplicate " + top.name + " in " + reader.getUrl());
354 } else {
355 switch (state_) {
356 case STATE_COMPONENT_SCHEMA:
357 // To support old, broken extensions with .xcs files that contain
358 // empty <component-schema> elements:
359 state_ = STATE_COMPONENT_DONE;
360 break;
361 case STATE_TEMPLATES:
362 state_ = STATE_TEMPLATES_DONE;
363 break;
364 case STATE_TEMPLATES_DONE:
365 throw css::uno::RuntimeException(
366 "no component element in " + reader.getUrl());
367 case STATE_COMPONENT_DONE:
368 break;
369 default:
370 assert(false); // this cannot happen
375 void XcsParser::characters(xmlreader::Span const & text) {
376 if (bIsParsingInfo_)
378 description_.append(text.convertFromUtf8());
379 return;
381 valueParser_.characters(text);
384 void XcsParser::handleComponentSchema(xmlreader::XmlReader & reader) {
385 //TODO: oor:version, xml:lang attributes
386 OStringBuffer buf(256);
387 buf.append('.');
388 bool hasPackage = false;
389 bool hasName = false;
390 for (;;) {
391 int attrNsId;
392 xmlreader::Span attrLn;
393 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
394 break;
396 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package")
398 if (hasPackage) {
399 throw css::uno::RuntimeException(
400 "multiple component-schema package attributes in " +
401 reader.getUrl());
403 hasPackage = true;
404 xmlreader::Span s(reader.getAttributeValue(false));
405 buf.insert(0, s.begin, s.length);
406 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
407 attrLn == "name")
409 if (hasName) {
410 throw css::uno::RuntimeException(
411 "multiple component-schema name attributes in " +
412 reader.getUrl());
414 hasName = true;
415 xmlreader::Span s(reader.getAttributeValue(false));
416 buf.append(s.begin, s.length);
419 if (!hasPackage) {
420 throw css::uno::RuntimeException(
421 "no component-schema package attribute in " + reader.getUrl());
423 if (!hasName) {
424 throw css::uno::RuntimeException(
425 "no component-schema name attribute in " + reader.getUrl());
427 componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()).
428 convertFromUtf8();
431 void XcsParser::handleNodeRef(xmlreader::XmlReader & reader) {
432 bool hasName = false;
433 OUString name;
434 OUString component(componentName_);
435 bool hasNodeType = false;
436 OUString nodeType;
437 for (;;) {
438 int attrNsId;
439 xmlreader::Span attrLn;
440 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
441 break;
443 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
444 hasName = true;
445 name = reader.getAttributeValue(false).convertFromUtf8();
446 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
447 attrLn == "component")
449 component = reader.getAttributeValue(false).convertFromUtf8();
450 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
451 attrLn == "node-type")
453 hasNodeType = true;
454 nodeType = reader.getAttributeValue(false).convertFromUtf8();
457 if (!hasName) {
458 throw css::uno::RuntimeException(
459 "no node-ref name attribute in " + reader.getUrl());
461 rtl::Reference< Node > tmpl(
462 data_.getTemplate(
463 valueParser_.getLayer(),
464 xmldata::parseTemplateReference(
465 component, hasNodeType, nodeType, nullptr)));
466 if (!tmpl.is()) {
467 //TODO: this can erroneously happen as long as import/uses attributes
468 // are not correctly processed
469 throw css::uno::RuntimeException(
470 "unknown node-ref " + name + " in " + reader.getUrl());
472 rtl::Reference< Node > node(tmpl->clone(false));
473 node->setLayer(valueParser_.getLayer());
474 elements_.push(Element(node, name));
477 void XcsParser::handleProp(xmlreader::XmlReader & reader) {
478 bool hasName = false;
479 OUString name;
480 valueParser_.type_ = TYPE_ERROR;
481 bool localized = false;
482 bool nillable = true;
483 for (;;) {
484 int attrNsId;
485 xmlreader::Span attrLn;
486 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
487 break;
489 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
490 hasName = true;
491 name = reader.getAttributeValue(false).convertFromUtf8();
492 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
493 attrLn == "type")
495 valueParser_.type_ = xmldata::parseType(
496 reader, reader.getAttributeValue(true));
497 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
498 attrLn == "localized")
500 localized = xmldata::parseBoolean(reader.getAttributeValue(true));
501 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
502 attrLn == "nillable")
504 nillable = xmldata::parseBoolean(reader.getAttributeValue(true));
507 if (!hasName) {
508 throw css::uno::RuntimeException(
509 "no prop name attribute in " + reader.getUrl());
511 if (valueParser_.type_ == TYPE_ERROR) {
512 throw css::uno::RuntimeException(
513 "no prop type attribute in " + reader.getUrl());
515 elements_.push(
516 Element(
517 (localized
518 ? rtl::Reference< Node >(
519 new LocalizedPropertyNode(
520 valueParser_.getLayer(), valueParser_.type_, nillable))
521 : rtl::Reference< Node >(
522 new PropertyNode(
523 valueParser_.getLayer(), valueParser_.type_, nillable,
524 css::uno::Any(), false))),
525 name));
528 void XcsParser::handlePropValue(
529 xmlreader::XmlReader & reader, rtl::Reference< Node > const & property)
531 xmlreader::Span attrSeparator;
532 for (;;) {
533 int attrNsId;
534 xmlreader::Span attrLn;
535 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
536 break;
538 if (attrNsId == ParseManager::NAMESPACE_OOR &&
539 attrLn == "separator")
541 attrSeparator = reader.getAttributeValue(false);
542 if (attrSeparator.length == 0) {
543 throw css::uno::RuntimeException(
544 "bad oor:separator attribute in " + reader.getUrl());
548 valueParser_.separator_ = OString(
549 attrSeparator.begin, attrSeparator.length);
550 valueParser_.start(property);
553 void XcsParser::handleGroup(xmlreader::XmlReader & reader, bool isTemplate) {
554 bool hasName = false;
555 OUString name;
556 bool extensible = false;
557 for (;;) {
558 int attrNsId;
559 xmlreader::Span attrLn;
560 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
561 break;
563 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
564 hasName = true;
565 name = reader.getAttributeValue(false).convertFromUtf8();
566 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
567 attrLn == "extensible")
569 extensible = xmldata::parseBoolean(reader.getAttributeValue(true));
572 if (!hasName) {
573 throw css::uno::RuntimeException(
574 "no group name attribute in " + reader.getUrl());
576 if (isTemplate) {
577 name = Data::fullTemplateName(componentName_, name);
579 elements_.push(
580 Element(
581 new GroupNode(
582 valueParser_.getLayer(), extensible,
583 isTemplate ? name : OUString()),
584 name));
587 void XcsParser::handleSet(xmlreader::XmlReader & reader, bool isTemplate) {
588 bool hasName = false;
589 OUString name;
590 OUString component(componentName_);
591 bool hasNodeType = false;
592 OUString nodeType;
593 for (;;) {
594 int attrNsId;
595 xmlreader::Span attrLn;
596 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
597 break;
599 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
600 hasName = true;
601 name = reader.getAttributeValue(false).convertFromUtf8();
602 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
603 attrLn == "component")
605 component = reader.getAttributeValue(false).convertFromUtf8();
606 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
607 attrLn == "node-type")
609 hasNodeType = true;
610 nodeType = reader.getAttributeValue(false).convertFromUtf8();
613 if (!hasName) {
614 throw css::uno::RuntimeException(
615 "no set name attribute in " + reader.getUrl());
617 if (isTemplate) {
618 name = Data::fullTemplateName(componentName_, name);
620 elements_.push(
621 Element(
622 new SetNode(
623 valueParser_.getLayer(),
624 xmldata::parseTemplateReference(
625 component, hasNodeType, nodeType, nullptr),
626 isTemplate ? name : OUString()),
627 name));
630 void XcsParser::handleSetItem(xmlreader::XmlReader & reader, SetNode * set) {
631 OUString component(componentName_);
632 bool hasNodeType = false;
633 OUString nodeType;
634 for (;;) {
635 int attrNsId;
636 xmlreader::Span attrLn;
637 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
638 break;
640 if (attrNsId == ParseManager::NAMESPACE_OOR &&
641 attrLn == "component")
643 component = reader.getAttributeValue(false).convertFromUtf8();
644 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
645 attrLn == "node-type")
647 hasNodeType = true;
648 nodeType = reader.getAttributeValue(false).convertFromUtf8();
651 set->getAdditionalTemplateNames().push_back(
652 xmldata::parseTemplateReference(component, hasNodeType, nodeType, nullptr));
653 elements_.push(Element(rtl::Reference< Node >(), ""));
658 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */