1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
35 #include "localizedpropertynode.hxx"
36 #include "groupnode.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"
49 // Conservatively merge a template or component (and its recursive parts) into
50 // an existing instance:
52 rtl::Reference
< Node
> const & original
,
53 rtl::Reference
< Node
> const & update
)
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())
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
);
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
);
103 case Node::KIND_ROOT
:
104 assert(false); // this cannot happen
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() {
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
131 || (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
132 && (name
== "import" || name
== "uses" || name
== "constraints" || name
== "desc"
133 // the following are unused by LO but valid
134 || name
== "deprecated" || name
== "author" || name
== "label")))
136 assert(ignoring_
< LONG_MAX
);
143 if (valueParser_
.startElement(reader
, nsId
, name
)) {
146 if (state_
== STATE_START
) {
147 if (nsId
== ParseManager::NAMESPACE_OOR
&&
148 name
== "component-schema")
150 handleComponentSchema(reader
);
151 state_
= STATE_COMPONENT_SCHEMA
;
157 case STATE_COMPONENT_SCHEMA
:
158 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
161 state_
= STATE_TEMPLATES
;
164 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
167 bIsParsingInfo_
= true;
171 case STATE_TEMPLATES_DONE
:
172 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
175 state_
= STATE_COMPONENT
;
176 assert(elements_
.empty());
179 new GroupNode(valueParser_
.getLayer(), false, ""),
184 case STATE_TEMPLATES
:
185 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
188 bIsParsingInfo_
= true;
191 if (elements_
.empty()) {
192 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
195 handleGroup(reader
, true);
198 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
201 handleSet(reader
, true);
204 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
207 bIsParsingInfo_
= true;
213 case STATE_COMPONENT
:
214 assert(!elements_
.empty());
215 switch (elements_
.top().node
->kind()) {
216 case Node::KIND_PROPERTY
:
217 case Node::KIND_LOCALIZED_PROPERTY
:
218 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
221 handlePropValue(reader
, elements_
.top().node
);
224 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
227 bIsParsingInfo_
= true;
231 case Node::KIND_GROUP
:
232 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
238 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
241 handleNodeRef(reader
);
244 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
247 handleGroup(reader
, false);
250 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
253 handleSet(reader
, false);
256 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
259 bIsParsingInfo_
= true;
264 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
269 static_cast< SetNode
* >(elements_
.top().node
.get()));
272 if (nsId
== xmlreader::XmlReader::NAMESPACE_NONE
&&
275 bIsParsingInfo_
= true;
279 default: // Node::KIND_LOCALIZED_VALUE
280 assert(false); // this cannot happen
284 case STATE_COMPONENT_DONE
:
286 default: // STATE_START
287 assert(false); // this cannot happen
291 throw css::uno::RuntimeException(
292 "bad member <" + name
.convertFromUtf8() + "> in " + reader
.getUrl());
295 void XcsParser::endElement(xmlreader::XmlReader
const & reader
) {
302 bIsParsingInfo_
= false;
305 if (valueParser_
.endElement()) {
308 if (!elements_
.empty()) {
309 Element
top(std::move(elements_
.top()));
312 if (top
.node
->kind() == Node::KIND_PROPERTY
313 || top
.node
->kind() == Node::KIND_LOCALIZED_PROPERTY
)
315 // Remove whitespace from description_ resulting from line breaks/indentation in xml files
316 OUString
desc(description_
.makeStringAndClear());
318 while (desc
.indexOf(" ") != -1)
319 desc
= desc
.replaceAll(" ", " ");
320 top
.node
->setDescription(desc
);
322 if (elements_
.empty()) {
324 case STATE_TEMPLATES
:
326 auto itPair
= data_
.templates
.insert({top
.name
, top
.node
});
327 if (!itPair
.second
) {
328 merge(itPair
.first
->second
, top
.node
);
332 case STATE_COMPONENT
:
334 NodeMap
& components
= data_
.getComponents();
335 auto itPair
= components
.insert({top
.name
, top
.node
});
336 if (!itPair
.second
) {
337 merge(itPair
.first
->second
, top
.node
);
339 state_
= STATE_COMPONENT_DONE
;
344 throw css::uno::RuntimeException(
345 "this cannot happen");
348 if (!elements_
.top().node
->getMembers().insert(
349 NodeMap::value_type(top
.name
, top
.node
)).second
)
351 throw css::uno::RuntimeException(
352 "duplicate " + top
.name
+ " in " + reader
.getUrl());
358 case STATE_COMPONENT_SCHEMA
:
359 // To support old, broken extensions with .xcs files that contain
360 // empty <component-schema> elements:
361 state_
= STATE_COMPONENT_DONE
;
363 case STATE_TEMPLATES
:
364 state_
= STATE_TEMPLATES_DONE
;
366 case STATE_TEMPLATES_DONE
:
367 throw css::uno::RuntimeException(
368 "no component element in " + reader
.getUrl());
369 case STATE_COMPONENT_DONE
:
372 assert(false); // this cannot happen
377 void XcsParser::characters(xmlreader::Span
const & text
) {
380 description_
.append(text
.convertFromUtf8());
383 valueParser_
.characters(text
);
386 void XcsParser::handleComponentSchema(xmlreader::XmlReader
& reader
) {
387 //TODO: oor:version, xml:lang attributes
388 OStringBuffer
buf(256);
390 bool hasPackage
= false;
391 bool hasName
= false;
394 xmlreader::Span attrLn
;
395 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
398 if (attrNsId
== ParseManager::NAMESPACE_OOR
&& attrLn
== "package")
401 throw css::uno::RuntimeException(
402 "multiple component-schema package attributes in " +
406 xmlreader::Span
s(reader
.getAttributeValue(false));
407 buf
.insert(0, s
.begin
, s
.length
);
408 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
412 throw css::uno::RuntimeException(
413 "multiple component-schema name attributes in " +
417 xmlreader::Span
s(reader
.getAttributeValue(false));
418 buf
.append(s
.begin
, s
.length
);
422 throw css::uno::RuntimeException(
423 "no component-schema package attribute in " + reader
.getUrl());
426 throw css::uno::RuntimeException(
427 "no component-schema name attribute in " + reader
.getUrl());
429 componentName_
= xmlreader::Span(buf
.getStr(), buf
.getLength()).
433 void XcsParser::handleNodeRef(xmlreader::XmlReader
& reader
) {
434 bool hasName
= false;
436 OUString
component(componentName_
);
437 bool hasNodeType
= false;
441 xmlreader::Span attrLn
;
442 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
445 if (attrNsId
== ParseManager::NAMESPACE_OOR
&& attrLn
== "name") {
447 name
= reader
.getAttributeValue(false).convertFromUtf8();
448 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
449 attrLn
== "component")
451 component
= reader
.getAttributeValue(false).convertFromUtf8();
452 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
453 attrLn
== "node-type")
456 nodeType
= reader
.getAttributeValue(false).convertFromUtf8();
460 throw css::uno::RuntimeException(
461 "no node-ref name attribute in " + reader
.getUrl());
463 rtl::Reference
< Node
> tmpl(
465 valueParser_
.getLayer(),
466 xmldata::parseTemplateReference(
467 component
, hasNodeType
, nodeType
, nullptr)));
469 //TODO: this can erroneously happen as long as import/uses attributes
470 // are not correctly processed
471 throw css::uno::RuntimeException(
472 "unknown node-ref " + name
+ " in " + reader
.getUrl());
474 rtl::Reference
< Node
> node(tmpl
->clone(false));
475 node
->setLayer(valueParser_
.getLayer());
476 elements_
.push(Element(node
, name
));
479 void XcsParser::handleProp(xmlreader::XmlReader
& reader
) {
480 bool hasName
= false;
482 valueParser_
.type_
= TYPE_ERROR
;
483 bool localized
= false;
484 bool nillable
= true;
487 xmlreader::Span attrLn
;
488 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
491 if (attrNsId
== ParseManager::NAMESPACE_OOR
&& attrLn
== "name") {
493 name
= reader
.getAttributeValue(false).convertFromUtf8();
494 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
497 valueParser_
.type_
= xmldata::parseType(
498 reader
, reader
.getAttributeValue(true));
499 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
500 attrLn
== "localized")
502 localized
= xmldata::parseBoolean(reader
.getAttributeValue(true));
503 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
504 attrLn
== "nillable")
506 nillable
= xmldata::parseBoolean(reader
.getAttributeValue(true));
510 throw css::uno::RuntimeException(
511 "no prop name attribute in " + reader
.getUrl());
513 if (valueParser_
.type_
== TYPE_ERROR
) {
514 throw css::uno::RuntimeException(
515 "no prop type attribute in " + reader
.getUrl());
520 ? rtl::Reference
< Node
>(
521 new LocalizedPropertyNode(
522 valueParser_
.getLayer(), valueParser_
.type_
, nillable
))
523 : rtl::Reference
< Node
>(
525 valueParser_
.getLayer(), valueParser_
.type_
, nillable
,
526 css::uno::Any(), false))),
530 void XcsParser::handlePropValue(
531 xmlreader::XmlReader
& reader
, rtl::Reference
< Node
> const & property
)
533 xmlreader::Span attrSeparator
;
536 xmlreader::Span attrLn
;
537 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
540 if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
541 attrLn
== "separator")
543 attrSeparator
= reader
.getAttributeValue(false);
544 if (attrSeparator
.length
== 0) {
545 throw css::uno::RuntimeException(
546 "bad oor:separator attribute in " + reader
.getUrl());
550 valueParser_
.separator_
= OString(
551 attrSeparator
.begin
, attrSeparator
.length
);
552 valueParser_
.start(property
);
555 void XcsParser::handleGroup(xmlreader::XmlReader
& reader
, bool isTemplate
) {
556 bool hasName
= false;
558 bool extensible
= false;
561 xmlreader::Span attrLn
;
562 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
565 if (attrNsId
== ParseManager::NAMESPACE_OOR
&& attrLn
== "name") {
567 name
= reader
.getAttributeValue(false).convertFromUtf8();
568 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
569 attrLn
== "extensible")
571 extensible
= xmldata::parseBoolean(reader
.getAttributeValue(true));
575 throw css::uno::RuntimeException(
576 "no group name attribute in " + reader
.getUrl());
579 name
= Data::fullTemplateName(componentName_
, name
);
584 valueParser_
.getLayer(), extensible
,
585 isTemplate
? name
: OUString()),
589 void XcsParser::handleSet(xmlreader::XmlReader
& reader
, bool isTemplate
) {
590 bool hasName
= false;
592 OUString
component(componentName_
);
593 bool hasNodeType
= false;
597 xmlreader::Span attrLn
;
598 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
601 if (attrNsId
== ParseManager::NAMESPACE_OOR
&& attrLn
== "name") {
603 name
= reader
.getAttributeValue(false).convertFromUtf8();
604 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
605 attrLn
== "component")
607 component
= reader
.getAttributeValue(false).convertFromUtf8();
608 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
609 attrLn
== "node-type")
612 nodeType
= reader
.getAttributeValue(false).convertFromUtf8();
616 throw css::uno::RuntimeException(
617 "no set name attribute in " + reader
.getUrl());
620 name
= Data::fullTemplateName(componentName_
, name
);
625 valueParser_
.getLayer(),
626 xmldata::parseTemplateReference(
627 component
, hasNodeType
, nodeType
, nullptr),
628 isTemplate
? name
: OUString()),
632 void XcsParser::handleSetItem(xmlreader::XmlReader
& reader
, SetNode
* set
) {
633 OUString
component(componentName_
);
634 bool hasNodeType
= false;
638 xmlreader::Span attrLn
;
639 if (!reader
.nextAttribute(&attrNsId
, &attrLn
)) {
642 if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
643 attrLn
== "component")
645 component
= reader
.getAttributeValue(false).convertFromUtf8();
646 } else if (attrNsId
== ParseManager::NAMESPACE_OOR
&&
647 attrLn
== "node-type")
650 nodeType
= reader
.getAttributeValue(false).convertFromUtf8();
653 set
->getAdditionalTemplateNames().push_back(
654 xmldata::parseTemplateReference(component
, hasNodeType
, nodeType
, nullptr));
655 elements_
.push(Element(rtl::Reference
< Node
>(), ""));
660 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */