diff options
Diffstat (limited to 'configmgr/source/xcsparser.cxx')
-rw-r--r-- | configmgr/source/xcsparser.cxx | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/configmgr/source/xcsparser.cxx b/configmgr/source/xcsparser.cxx new file mode 100644 index 000000000..947792c0a --- /dev/null +++ b/configmgr/source/xcsparser.cxx @@ -0,0 +1,596 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> +#include <set> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <rtl/ref.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <xmlreader/span.hxx> +#include <xmlreader/xmlreader.hxx> + +#include "data.hxx" +#include "localizedpropertynode.hxx" +#include "groupnode.hxx" +#include "node.hxx" +#include "nodemap.hxx" +#include "parsemanager.hxx" +#include "propertynode.hxx" +#include "setnode.hxx" +#include "xcsparser.hxx" +#include "xmldata.hxx" + +namespace configmgr { + +namespace { + +// Conservatively merge a template or component (and its recursive parts) into +// an existing instance: +void merge( + rtl::Reference< Node > const & original, + rtl::Reference< Node > const & update) +{ + assert( + original.is() && update.is() && original->kind() == update->kind() && + update->getFinalized() == Data::NO_LAYER); + if (update->getLayer() < original->getLayer() || + update->getLayer() > original->getFinalized()) + return; + + switch (original->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_LOCALIZED_VALUE: + break; //TODO: merge certain parts? + case Node::KIND_GROUP: + for (auto const& updateMember : update->getMembers()) + { + NodeMap & members = original->getMembers(); + NodeMap::iterator i1(members.find(updateMember.first)); + if (i1 == members.end()) { + if (updateMember.second->kind() == Node::KIND_PROPERTY && + static_cast< GroupNode * >( + original.get())->isExtensible()) + { + members.insert(updateMember); + } + } else if (updateMember.second->kind() == i1->second->kind()) { + merge(i1->second, updateMember.second); + } + } + break; + case Node::KIND_SET: + for (auto const& updateMember : update->getMembers()) + { + NodeMap & members = original->getMembers(); + NodeMap::iterator i1(members.find(updateMember.first)); + if (i1 == members.end()) { + if (static_cast< SetNode * >(original.get())-> + isValidTemplate(updateMember.second->getTemplateName())) + { + members.insert(updateMember); + } + } else if (updateMember.second->kind() == i1->second->kind() && + (updateMember.second->getTemplateName() == + i1->second->getTemplateName())) + { + merge(i1->second, updateMember.second); + } + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } +} + +} + +XcsParser::XcsParser(int layer, Data & data): + valueParser_(layer), data_(data), state_(STATE_START), ignoring_() +{} + +XcsParser::~XcsParser() {} + +xmlreader::XmlReader::Text XcsParser::getTextMode() { + return valueParser_.getTextMode(); +} + +bool XcsParser::startElement( + xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name, + std::set< OUString > const * /*existingDependencies*/) +{ + if (valueParser_.startElement(reader, nsId, name)) { + return true; + } + if (state_ == STATE_START) { + if (nsId == ParseManager::NAMESPACE_OOR && + name == "component-schema") + { + handleComponentSchema(reader); + state_ = STATE_COMPONENT_SCHEMA; + ignoring_ = 0; + return true; + } + } else { + //TODO: ignoring component-schema import, component-schema uses, and + // prop constraints; accepting all four at illegal places (and with + // illegal content): + if (ignoring_ > 0 || + (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + (name == "info" || name == "import" || + name == "uses" || name == "constraints"))) + { + assert(ignoring_ < LONG_MAX); + ++ignoring_; + return true; + } + switch (state_) { + case STATE_COMPONENT_SCHEMA: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "templates") + { + state_ = STATE_TEMPLATES; + return true; + } + [[fallthrough]]; + case STATE_TEMPLATES_DONE: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "component") + { + state_ = STATE_COMPONENT; + assert(elements_.empty()); + elements_.push( + Element( + new GroupNode(valueParser_.getLayer(), false, ""), + componentName_)); + return true; + } + break; + case STATE_TEMPLATES: + if (elements_.empty()) { + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "group") + { + handleGroup(reader, true); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "set") + { + handleSet(reader, true); + return true; + } + break; + } + [[fallthrough]]; + case STATE_COMPONENT: + assert(!elements_.empty()); + switch (elements_.top().node->kind()) { + case Node::KIND_PROPERTY: + case Node::KIND_LOCALIZED_PROPERTY: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "value") + { + handlePropValue(reader, elements_.top().node); + return true; + } + break; + case Node::KIND_GROUP: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "prop") + { + handleProp(reader); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "node-ref") + { + handleNodeRef(reader); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "group") + { + handleGroup(reader, false); + return true; + } + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "set") + { + handleSet(reader, false); + return true; + } + break; + case Node::KIND_SET: + if (nsId == xmlreader::XmlReader::NAMESPACE_NONE && + name == "item") + { + handleSetItem( + reader, + static_cast< SetNode * >(elements_.top().node.get())); + return true; + } + break; + default: // Node::KIND_LOCALIZED_VALUE + assert(false); // this cannot happen + break; + } + break; + case STATE_COMPONENT_DONE: + break; + default: // STATE_START + assert(false); // this cannot happen + break; + } + } + throw css::uno::RuntimeException( + "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl()); +} + +void XcsParser::endElement(xmlreader::XmlReader const & reader) { + if (valueParser_.endElement()) { + return; + } + if (ignoring_ > 0) { + --ignoring_; + } else if (!elements_.empty()) { + Element top(std::move(elements_.top())); + elements_.pop(); + if (top.node.is()) { + if (elements_.empty()) { + switch (state_) { + case STATE_TEMPLATES: + { + auto itPair = data_.templates.insert({top.name, top.node}); + if (!itPair.second) { + merge(itPair.first->second, top.node); + } + } + break; + case STATE_COMPONENT: + { + NodeMap & components = data_.getComponents(); + auto itPair = components.insert({top.name, top.node}); + if (!itPair.second) { + merge(itPair.first->second, top.node); + } + state_ = STATE_COMPONENT_DONE; + } + break; + default: + assert(false); + throw css::uno::RuntimeException( + "this cannot happen"); + } + } else { + if (!elements_.top().node->getMembers().insert( + NodeMap::value_type(top.name, top.node)).second) + { + throw css::uno::RuntimeException( + "duplicate " + top.name + " in " + reader.getUrl()); + } + } + } + } else { + switch (state_) { + case STATE_COMPONENT_SCHEMA: + // To support old, broken extensions with .xcs files that contain + // empty <component-schema> elements: + state_ = STATE_COMPONENT_DONE; + break; + case STATE_TEMPLATES: + state_ = STATE_TEMPLATES_DONE; + break; + case STATE_TEMPLATES_DONE: + throw css::uno::RuntimeException( + "no component element in " + reader.getUrl()); + case STATE_COMPONENT_DONE: + break; + default: + assert(false); // this cannot happen + } + } +} + +void XcsParser::characters(xmlreader::Span const & text) { + valueParser_.characters(text); +} + +void XcsParser::handleComponentSchema(xmlreader::XmlReader & reader) { + //TODO: oor:version, xml:lang attributes + OStringBuffer buf(256); + buf.append('.'); + bool hasPackage = false; + bool hasName = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package") + { + if (hasPackage) { + throw css::uno::RuntimeException( + "multiple component-schema package attributes in " + + reader.getUrl()); + } + hasPackage = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.insert(0, s.begin, s.length); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "name") + { + if (hasName) { + throw css::uno::RuntimeException( + "multiple component-schema name attributes in " + + reader.getUrl()); + } + hasName = true; + xmlreader::Span s(reader.getAttributeValue(false)); + buf.append(s.begin, s.length); + } + } + if (!hasPackage) { + throw css::uno::RuntimeException( + "no component-schema package attribute in " + reader.getUrl()); + } + if (!hasName) { + throw css::uno::RuntimeException( + "no component-schema name attribute in " + reader.getUrl()); + } + componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()). + convertFromUtf8(); +} + +void XcsParser::handleNodeRef(xmlreader::XmlReader & reader) { + bool hasName = false; + OUString name; + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no node-ref name attribute in " + reader.getUrl()); + } + rtl::Reference< Node > tmpl( + data_.getTemplate( + valueParser_.getLayer(), + xmldata::parseTemplateReference( + component, hasNodeType, nodeType, nullptr))); + if (!tmpl.is()) { + //TODO: this can erroneously happen as long as import/uses attributes + // are not correctly processed + throw css::uno::RuntimeException( + "unknown node-ref " + name + " in " + reader.getUrl()); + } + rtl::Reference< Node > node(tmpl->clone(false)); + node->setLayer(valueParser_.getLayer()); + elements_.push(Element(node, name)); +} + +void XcsParser::handleProp(xmlreader::XmlReader & reader) { + bool hasName = false; + OUString name; + valueParser_.type_ = TYPE_ERROR; + bool localized = false; + bool nillable = true; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "type") + { + valueParser_.type_ = xmldata::parseType( + reader, reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "localized") + { + localized = xmldata::parseBoolean(reader.getAttributeValue(true)); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "nillable") + { + nillable = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no prop name attribute in " + reader.getUrl()); + } + if (valueParser_.type_ == TYPE_ERROR) { + throw css::uno::RuntimeException( + "no prop type attribute in " + reader.getUrl()); + } + elements_.push( + Element( + (localized + ? rtl::Reference< Node >( + new LocalizedPropertyNode( + valueParser_.getLayer(), valueParser_.type_, nillable)) + : rtl::Reference< Node >( + new PropertyNode( + valueParser_.getLayer(), valueParser_.type_, nillable, + css::uno::Any(), false))), + name)); +} + +void XcsParser::handlePropValue( + xmlreader::XmlReader & reader, rtl::Reference< Node > const & property) +{ + xmlreader::Span attrSeparator; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "separator") + { + attrSeparator = reader.getAttributeValue(false); + if (attrSeparator.length == 0) { + throw css::uno::RuntimeException( + "bad oor:separator attribute in " + reader.getUrl()); + } + } + } + valueParser_.separator_ = OString( + attrSeparator.begin, attrSeparator.length); + valueParser_.start(property); +} + +void XcsParser::handleGroup(xmlreader::XmlReader & reader, bool isTemplate) { + bool hasName = false; + OUString name; + bool extensible = false; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "extensible") + { + extensible = xmldata::parseBoolean(reader.getAttributeValue(true)); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no group name attribute in " + reader.getUrl()); + } + if (isTemplate) { + name = Data::fullTemplateName(componentName_, name); + } + elements_.push( + Element( + new GroupNode( + valueParser_.getLayer(), extensible, + isTemplate ? name : OUString()), + name)); +} + +void XcsParser::handleSet(xmlreader::XmlReader & reader, bool isTemplate) { + bool hasName = false; + OUString name; + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") { + hasName = true; + name = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + if (!hasName) { + throw css::uno::RuntimeException( + "no set name attribute in " + reader.getUrl()); + } + if (isTemplate) { + name = Data::fullTemplateName(componentName_, name); + } + elements_.push( + Element( + new SetNode( + valueParser_.getLayer(), + xmldata::parseTemplateReference( + component, hasNodeType, nodeType, nullptr), + isTemplate ? name : OUString()), + name)); +} + +void XcsParser::handleSetItem(xmlreader::XmlReader & reader, SetNode * set) { + OUString component(componentName_); + bool hasNodeType = false; + OUString nodeType; + for (;;) { + int attrNsId; + xmlreader::Span attrLn; + if (!reader.nextAttribute(&attrNsId, &attrLn)) { + break; + } + if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "component") + { + component = reader.getAttributeValue(false).convertFromUtf8(); + } else if (attrNsId == ParseManager::NAMESPACE_OOR && + attrLn == "node-type") + { + hasNodeType = true; + nodeType = reader.getAttributeValue(false).convertFromUtf8(); + } + } + set->getAdditionalTemplateNames().push_back( + xmldata::parseTemplateReference(component, hasNodeType, nodeType, nullptr)); + elements_.push(Element(rtl::Reference< Node >(), "")); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |