"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
1217 lines
36 KiB
C++
1217 lines
36 KiB
C++
/**************************************************************************
|
|
* *
|
|
* Copyright (c) 2021-2023 FreeCAD Project Association *
|
|
* *
|
|
* This file is part of FreeCAD. *
|
|
* *
|
|
* FreeCAD is free software: you can redistribute it and/or modify it *
|
|
* under the terms of the GNU Lesser General Public License as *
|
|
* published by the Free Software Foundation, either version 2.1 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* FreeCAD is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with FreeCAD. If not, see *
|
|
* <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <boost/core/ignore_unused.hpp>
|
|
#include <memory>
|
|
#include <sstream>
|
|
|
|
#include <xercesc/framework/LocalFileFormatTarget.hpp>
|
|
#include <xercesc/framework/LocalFileInputSource.hpp>
|
|
#include <xercesc/framework/MemBufInputSource.hpp>
|
|
#include <xercesc/sax/HandlerBase.hpp>
|
|
|
|
#include <FCConfig.h>
|
|
|
|
#include "App/Application.h"
|
|
#include "App/Expression.h"
|
|
#include "Base/XMLTools.h"
|
|
|
|
#include "Metadata.h"
|
|
|
|
|
|
/*
|
|
*** From GCC: ***
|
|
In the GNU C Library, "major" and "minor" are defined
|
|
by <sys/sysmacros.h>. For historical compatibility, it is
|
|
currently defined by <sys/types.h> as well, but we plan to
|
|
remove this soon. To use "major", include <sys/sysmacros.h>
|
|
directly. If you did not intend to use a system-defined macro
|
|
"major", you should undefine it after including <sys/types.h>.
|
|
*/
|
|
#ifdef major
|
|
#undef major
|
|
#endif
|
|
#ifdef minor
|
|
#undef minor
|
|
#endif
|
|
|
|
using namespace App;
|
|
namespace fs = std::filesystem;
|
|
#ifndef XERCES_CPP_NAMESPACE_BEGIN
|
|
#define XERCES_CPP_NAMESPACE_QUALIFIER
|
|
using namespace XERCES_CPP_NAMESPACE;
|
|
#else
|
|
XERCES_CPP_NAMESPACE_USE
|
|
#endif
|
|
|
|
namespace MetadataInternal
|
|
{
|
|
class XMLErrorHandler: public HandlerBase
|
|
{
|
|
void warning(const SAXParseException& toCatch) override
|
|
{
|
|
// Don't deal with warnings at all
|
|
boost::ignore_unused(toCatch);
|
|
}
|
|
|
|
void error(const SAXParseException& toCatch) override
|
|
{
|
|
std::stringstream message;
|
|
message << "Error at file \"" << StrX(toCatch.getSystemId()) << "\", line "
|
|
<< toCatch.getLineNumber() << ", column " << toCatch.getColumnNumber()
|
|
<< "\n Message: " << StrX(toCatch.getMessage()) << std::endl;
|
|
throw Base::XMLBaseException(message.str());
|
|
}
|
|
|
|
void fatalError(const SAXParseException& toCatch) override
|
|
{
|
|
std::stringstream message;
|
|
message << "Fatal error at file \"" << StrX(toCatch.getSystemId()) << "\", line "
|
|
<< toCatch.getLineNumber() << ", column " << toCatch.getColumnNumber()
|
|
<< "\n Message: " << StrX(toCatch.getMessage()) << std::endl;
|
|
throw Base::XMLBaseException(message.str());
|
|
}
|
|
};
|
|
} // namespace MetadataInternal
|
|
|
|
Metadata::Metadata(const fs::path& metadataFile)
|
|
: _dom(nullptr)
|
|
{
|
|
#if defined(FC_OS_WIN32)
|
|
auto source =
|
|
LocalFileInputSource(reinterpret_cast<const XMLCh*>(metadataFile.wstring().c_str()));
|
|
#else
|
|
auto source = LocalFileInputSource(XUTF8Str(metadataFile.string().c_str()).unicodeForm());
|
|
#endif
|
|
loadFromInputSource(source);
|
|
}
|
|
|
|
Metadata::Metadata()
|
|
: _dom(nullptr)
|
|
{}
|
|
|
|
Metadata::Metadata(const DOMNode* domNode, int format)
|
|
: _dom(nullptr)
|
|
{
|
|
auto element = dynamic_cast<const DOMElement*>(domNode);
|
|
if (element) {
|
|
switch (format) {
|
|
case 1:
|
|
parseVersion1(element);
|
|
break;
|
|
default:
|
|
throw Base::XMLBaseException(
|
|
"package.xml format version is not supported by this version of FreeCAD");
|
|
}
|
|
}
|
|
}
|
|
|
|
App::Metadata::Metadata(const std::string& rawData)
|
|
: _dom(nullptr)
|
|
{
|
|
MemBufInputSource buffer(reinterpret_cast<const XMLByte*>(rawData.c_str()),
|
|
rawData.size(),
|
|
"raw data (in memory)");
|
|
loadFromInputSource(buffer);
|
|
}
|
|
|
|
void Metadata::loadFromInputSource(const InputSource& source)
|
|
{
|
|
// Any exception thrown by the XML code propagates out and prevents object creation
|
|
XMLPlatformUtils::Initialize();
|
|
|
|
_parser = std::make_shared<XercesDOMParser>();
|
|
_parser->setValidationScheme(XercesDOMParser::Val_Never);
|
|
_parser->setDoNamespaces(true);
|
|
|
|
auto errHandler = std::make_unique<MetadataInternal::XMLErrorHandler>();
|
|
_parser->setErrorHandler(errHandler.get());
|
|
|
|
_parser->parse(source);
|
|
|
|
auto doc = _parser->getDocument();
|
|
_dom = doc->getDocumentElement();
|
|
|
|
auto rootTagName = StrXUTF8(_dom->getTagName()).str;
|
|
if (rootTagName != "package") {
|
|
throw Base::XMLBaseException(
|
|
"Malformed package.xml document: Root <package> group not found");
|
|
}
|
|
auto formatVersion = XMLString::parseInt(_dom->getAttribute(XUTF8StrLiteral("format").unicodeForm()));
|
|
switch (formatVersion) {
|
|
case 1:
|
|
parseVersion1(_dom);
|
|
break;
|
|
default:
|
|
throw Base::XMLBaseException(
|
|
"package.xml format version is not supported by this version of FreeCAD");
|
|
}
|
|
}
|
|
|
|
Metadata::~Metadata() = default;
|
|
|
|
std::string Metadata::name() const
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
std::string Metadata::type() const
|
|
{
|
|
return _type;
|
|
}
|
|
|
|
Meta::Version Metadata::version() const
|
|
{
|
|
return _version;
|
|
}
|
|
|
|
std::string App::Metadata::date() const
|
|
{
|
|
return _date;
|
|
}
|
|
|
|
std::string Metadata::description() const
|
|
{
|
|
return _description;
|
|
}
|
|
|
|
std::vector<Meta::Contact> Metadata::maintainer() const
|
|
{
|
|
return _maintainer;
|
|
}
|
|
|
|
std::vector<Meta::License> Metadata::license() const
|
|
{
|
|
return _license;
|
|
}
|
|
|
|
std::vector<Meta::Url> Metadata::url() const
|
|
{
|
|
return _url;
|
|
}
|
|
|
|
std::vector<Meta::Contact> Metadata::author() const
|
|
{
|
|
return _author;
|
|
}
|
|
|
|
std::vector<Meta::Dependency> Metadata::depend() const
|
|
{
|
|
return _depend;
|
|
}
|
|
|
|
std::vector<Meta::Dependency> Metadata::conflict() const
|
|
{
|
|
return _conflict;
|
|
}
|
|
|
|
std::vector<Meta::Dependency> Metadata::replace() const
|
|
{
|
|
return _replace;
|
|
}
|
|
|
|
std::vector<std::string> Metadata::tag() const
|
|
{
|
|
return _tag;
|
|
}
|
|
|
|
fs::path Metadata::icon() const
|
|
{
|
|
return _icon;
|
|
}
|
|
|
|
std::string Metadata::classname() const
|
|
{
|
|
return _classname;
|
|
}
|
|
|
|
std::filesystem::path Metadata::subdirectory() const
|
|
{
|
|
return _subdirectory;
|
|
}
|
|
|
|
std::vector<fs::path> Metadata::file() const
|
|
{
|
|
return _file;
|
|
}
|
|
|
|
Meta::Version Metadata::freecadmin() const
|
|
{
|
|
return _freecadmin;
|
|
}
|
|
|
|
Meta::Version Metadata::freecadmax() const
|
|
{
|
|
return _freecadmax;
|
|
}
|
|
|
|
Meta::Version Metadata::pythonmin() const
|
|
{
|
|
return _pythonmin;
|
|
}
|
|
|
|
std::multimap<std::string, Metadata> Metadata::content() const
|
|
{
|
|
return _content;
|
|
}
|
|
|
|
std::vector<Meta::GenericMetadata> Metadata::operator[](const std::string& tag) const
|
|
{
|
|
std::vector<Meta::GenericMetadata> returnValue;
|
|
auto range = _genericMetadata.equal_range(tag);
|
|
for (auto item = range.first; item != range.second; ++item) {
|
|
returnValue.push_back(item->second);
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
XERCES_CPP_NAMESPACE::DOMElement* Metadata::dom() const
|
|
{
|
|
return _dom;
|
|
}
|
|
|
|
void Metadata::setName(const std::string& name)
|
|
{
|
|
std::string invalidCharacters = "/\\?%*:|\"<>"; // Should cover all OSes
|
|
if (_name.find_first_of(invalidCharacters) != std::string::npos) {
|
|
throw Base::RuntimeError("Name cannot contain any of: " + invalidCharacters);
|
|
}
|
|
_name = name;
|
|
}
|
|
|
|
void Metadata::setType(const std::string& type)
|
|
{
|
|
_type = type;
|
|
}
|
|
|
|
void Metadata::setVersion(const Meta::Version& version)
|
|
{
|
|
_version = version;
|
|
}
|
|
|
|
void App::Metadata::setDate(const std::string& date)
|
|
{
|
|
_date = date;
|
|
}
|
|
|
|
void Metadata::setDescription(const std::string& description)
|
|
{
|
|
_description = description;
|
|
}
|
|
|
|
void Metadata::addMaintainer(const Meta::Contact& maintainer)
|
|
{
|
|
_maintainer.push_back(maintainer);
|
|
}
|
|
|
|
void Metadata::addLicense(const Meta::License& license)
|
|
{
|
|
_license.push_back(license);
|
|
}
|
|
|
|
void Metadata::addUrl(const Meta::Url& url)
|
|
{
|
|
_url.push_back(url);
|
|
}
|
|
|
|
void Metadata::addAuthor(const Meta::Contact& author)
|
|
{
|
|
_author.push_back(author);
|
|
}
|
|
|
|
void Metadata::addDepend(const Meta::Dependency& dep)
|
|
{
|
|
_depend.push_back(dep);
|
|
}
|
|
|
|
void Metadata::addConflict(const Meta::Dependency& dep)
|
|
{
|
|
_conflict.push_back(dep);
|
|
}
|
|
|
|
void Metadata::addReplace(const Meta::Dependency& dep)
|
|
{
|
|
_replace.push_back(dep);
|
|
}
|
|
|
|
void Metadata::addTag(const std::string& tag)
|
|
{
|
|
_tag.push_back(tag);
|
|
}
|
|
|
|
void Metadata::setIcon(const fs::path& path)
|
|
{
|
|
_icon = path;
|
|
}
|
|
|
|
void Metadata::setClassname(const std::string& name)
|
|
{
|
|
_classname = name;
|
|
}
|
|
|
|
void Metadata::setSubdirectory(const std::filesystem::path& path)
|
|
{
|
|
_subdirectory = path;
|
|
}
|
|
|
|
void Metadata::addFile(const fs::path& path)
|
|
{
|
|
_file.push_back(path);
|
|
}
|
|
|
|
void Metadata::addContentItem(const std::string& tag, const Metadata& item)
|
|
{
|
|
_content.insert(std::make_pair(tag, item));
|
|
}
|
|
|
|
void Metadata::setFreeCADMin(const Meta::Version& version)
|
|
{
|
|
_freecadmin = version;
|
|
}
|
|
|
|
void Metadata::setPythonMin(const Meta::Version& version)
|
|
{
|
|
_pythonmin = version;
|
|
}
|
|
|
|
void Metadata::setFreeCADMax(const Meta::Version& version)
|
|
{
|
|
_freecadmax = version;
|
|
}
|
|
|
|
void Metadata::addGenericMetadata(const std::string& tag,
|
|
const Meta::GenericMetadata& genericMetadata)
|
|
{
|
|
_genericMetadata.insert(std::make_pair(tag, genericMetadata));
|
|
}
|
|
|
|
void Metadata::removeContentItem(const std::string& tag, const std::string& itemName)
|
|
{
|
|
auto tagRange = _content.equal_range(tag);
|
|
auto foundItem =
|
|
std::find_if(tagRange.first, tagRange.second, [&itemName](const auto& check) -> bool {
|
|
return itemName == check.second.name();
|
|
});
|
|
if (foundItem != tagRange.second) {
|
|
_content.erase(foundItem);
|
|
}
|
|
}
|
|
|
|
void Metadata::removeMaintainer(const Meta::Contact& maintainer)
|
|
{
|
|
auto new_end = std::remove(_maintainer.begin(), _maintainer.end(), maintainer);
|
|
_maintainer.erase(new_end, _maintainer.end());
|
|
}
|
|
|
|
void Metadata::removeLicense(const Meta::License& license)
|
|
{
|
|
auto new_end = std::remove(_license.begin(), _license.end(), license);
|
|
_license.erase(new_end, _license.end());
|
|
}
|
|
|
|
void Metadata::removeUrl(const Meta::Url& url)
|
|
{
|
|
auto new_end = std::remove(_url.begin(), _url.end(), url);
|
|
_url.erase(new_end, _url.end());
|
|
}
|
|
|
|
void Metadata::removeAuthor(const Meta::Contact& author)
|
|
{
|
|
auto new_end = std::remove(_author.begin(), _author.end(), author);
|
|
_author.erase(new_end, _author.end());
|
|
}
|
|
|
|
void Metadata::removeDepend(const Meta::Dependency& dep)
|
|
{
|
|
bool found = false;
|
|
for (const auto& check : _depend) {
|
|
if (dep == check) {
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
throw Base::RuntimeError("No match found for dependency to remove");
|
|
}
|
|
auto new_end = std::remove(_depend.begin(), _depend.end(), dep);
|
|
_depend.erase(new_end, _depend.end());
|
|
}
|
|
|
|
void Metadata::removeConflict(const Meta::Dependency& dep)
|
|
{
|
|
auto new_end = std::remove(_conflict.begin(), _conflict.end(), dep);
|
|
_conflict.erase(new_end, _conflict.end());
|
|
}
|
|
|
|
void Metadata::removeReplace(const Meta::Dependency& dep)
|
|
{
|
|
auto new_end = std::remove(_replace.begin(), _replace.end(), dep);
|
|
_replace.erase(new_end, _replace.end());
|
|
}
|
|
|
|
void Metadata::removeTag(const std::string& tag)
|
|
{
|
|
auto new_end = std::remove(_tag.begin(), _tag.end(), tag);
|
|
_tag.erase(new_end, _tag.end());
|
|
}
|
|
|
|
void Metadata::removeFile(const std::filesystem::path& path)
|
|
{
|
|
auto new_end = std::remove(_file.begin(), _file.end(), path);
|
|
_file.erase(new_end, _file.end());
|
|
}
|
|
|
|
|
|
void Metadata::clearContent()
|
|
{
|
|
_content.clear();
|
|
}
|
|
|
|
void Metadata::clearMaintainer()
|
|
{
|
|
_maintainer.clear();
|
|
}
|
|
|
|
void Metadata::clearLicense()
|
|
{
|
|
_license.clear();
|
|
}
|
|
|
|
void Metadata::clearUrl()
|
|
{
|
|
_url.clear();
|
|
}
|
|
|
|
void Metadata::clearAuthor()
|
|
{
|
|
_author.clear();
|
|
}
|
|
|
|
void Metadata::clearDepend()
|
|
{
|
|
_depend.clear();
|
|
}
|
|
|
|
void Metadata::clearConflict()
|
|
{
|
|
_conflict.clear();
|
|
}
|
|
|
|
void Metadata::clearReplace()
|
|
{
|
|
_replace.clear();
|
|
}
|
|
|
|
void Metadata::clearTag()
|
|
{
|
|
_tag.clear();
|
|
}
|
|
|
|
void Metadata::clearFile()
|
|
{
|
|
_file.clear();
|
|
}
|
|
|
|
|
|
DOMElement* appendSimpleXMLNode(DOMElement* baseNode,
|
|
const std::string& nodeName,
|
|
const std::string& nodeContents)
|
|
{
|
|
// For convenience (and brevity of final output) don't create nodes that don't have contents
|
|
if (nodeContents.empty()) {
|
|
return nullptr;
|
|
}
|
|
auto doc = baseNode->getOwnerDocument();
|
|
DOMElement* namedElement = doc->createElement(XUTF8Str(nodeName.c_str()).unicodeForm());
|
|
baseNode->appendChild(namedElement);
|
|
DOMText* namedNode = doc->createTextNode(XUTF8Str(nodeContents.c_str()).unicodeForm());
|
|
namedElement->appendChild(namedNode);
|
|
return namedElement;
|
|
}
|
|
|
|
void addAttribute(DOMElement* node, const std::string& key, const std::string& value)
|
|
{
|
|
if (value.empty()) {
|
|
return;
|
|
}
|
|
node->setAttribute(XUTF8Str(key.c_str()).unicodeForm(), XUTF8Str(value.c_str()).unicodeForm());
|
|
}
|
|
|
|
void addAttribute(DOMElement* node, const std::string& key, bool value)
|
|
{
|
|
if (value) {
|
|
node->setAttribute(XUTF8Str(key.c_str()).unicodeForm(), XUTF8StrLiteral("True").unicodeForm());
|
|
}
|
|
else {
|
|
node->setAttribute(XUTF8Str(key.c_str()).unicodeForm(), XUTF8StrLiteral("False").unicodeForm());
|
|
}
|
|
}
|
|
|
|
void addAttribute(DOMElement* node, const std::string& key, Meta::DependencyType value)
|
|
{
|
|
// Someday we should be able to change this to use reflection, but it's not yet
|
|
// available (using C++17)
|
|
std::string stringified("automatic");
|
|
switch (value) {
|
|
case Meta::DependencyType::automatic:
|
|
stringified = "automatic";
|
|
break;
|
|
case Meta::DependencyType::internal:
|
|
stringified = "internal";
|
|
break;
|
|
case Meta::DependencyType::addon:
|
|
stringified = "addon";
|
|
break;
|
|
case Meta::DependencyType::python:
|
|
stringified = "python";
|
|
break;
|
|
}
|
|
node->setAttribute(XUTF8Str(key.c_str()).unicodeForm(),
|
|
XUTF8Str(stringified.c_str()).unicodeForm());
|
|
}
|
|
|
|
void addDependencyNode(DOMElement* root, const std::string& name, const Meta::Dependency& depend)
|
|
{
|
|
auto element = appendSimpleXMLNode(root, name, depend.package);
|
|
if (element) {
|
|
addAttribute(element, "version_lt", depend.version_lt);
|
|
addAttribute(element, "version_lte", depend.version_lte);
|
|
addAttribute(element, "version_eq", depend.version_eq);
|
|
addAttribute(element, "version_gte", depend.version_gte);
|
|
addAttribute(element, "version_gt", depend.version_gt);
|
|
addAttribute(element, "condition", depend.condition);
|
|
addAttribute(element, "optional", depend.optional);
|
|
addAttribute(element, "type", depend.dependencyType);
|
|
}
|
|
}
|
|
|
|
void Metadata::write(const fs::path& file) const
|
|
{
|
|
DOMImplementation* impl =
|
|
DOMImplementationRegistry::getDOMImplementation(XUTF8StrLiteral("Core LS").unicodeForm());
|
|
|
|
DOMDocument* doc = impl->createDocument(nullptr, XUTF8StrLiteral("package").unicodeForm(), nullptr);
|
|
DOMElement* root = doc->getDocumentElement();
|
|
root->setAttribute(XUTF8StrLiteral("format").unicodeForm(), XUTF8StrLiteral("1").unicodeForm());
|
|
root->setAttribute(XUTF8StrLiteral("xmlns").unicodeForm(),
|
|
XUTF8StrLiteral("https://wiki.freecad.org/Package_Metadata").unicodeForm());
|
|
|
|
appendToElement(root);
|
|
|
|
DOMLSSerializer* theSerializer = ((DOMImplementationLS*)impl)->createLSSerializer();
|
|
DOMConfiguration* config = theSerializer->getDomConfig();
|
|
if (config->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true)) {
|
|
config->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true);
|
|
}
|
|
|
|
// set feature if the serializer supports the feature/mode
|
|
if (config->canSetParameter(XMLUni::fgDOMWRTSplitCdataSections, true)) {
|
|
config->setParameter(XMLUni::fgDOMWRTSplitCdataSections, true);
|
|
}
|
|
|
|
if (config->canSetParameter(XMLUni::fgDOMWRTDiscardDefaultContent, true)) {
|
|
config->setParameter(XMLUni::fgDOMWRTDiscardDefaultContent, true);
|
|
}
|
|
|
|
try {
|
|
XMLFormatTarget* myFormTarget = new LocalFileFormatTarget(file.string().c_str());
|
|
DOMLSOutput* theOutput = ((DOMImplementationLS*)impl)->createLSOutput();
|
|
|
|
theOutput->setByteStream(myFormTarget);
|
|
theSerializer->write(doc, theOutput);
|
|
|
|
theOutput->release();
|
|
theSerializer->release();
|
|
delete myFormTarget;
|
|
}
|
|
catch (const XMLException& toCatch) {
|
|
char* message = XMLString::transcode(toCatch.getMessage());
|
|
std::string what = message;
|
|
XMLString::release(&message);
|
|
throw Base::XMLBaseException(what);
|
|
}
|
|
catch (const DOMException& toCatch) {
|
|
char* message = XMLString::transcode(toCatch.getMessage());
|
|
std::string what = message;
|
|
XMLString::release(&message);
|
|
throw Base::XMLBaseException(what);
|
|
}
|
|
|
|
doc->release();
|
|
}
|
|
|
|
bool Metadata::satisfies(const Meta::Dependency& dep)
|
|
{
|
|
if (dep.package != _name) {
|
|
return false;
|
|
}
|
|
// The "condition" attribute allows an expression to enable or disable this dependency check: it
|
|
// must contain a valid FreeCAD Expression. If it evaluates to false, this dependency is
|
|
// bypassed (e.g. this function returns false).
|
|
if (!dep.condition.empty()) {
|
|
auto injectedString = dep.condition;
|
|
std::map<std::string, std::string> replacements;
|
|
std::map<std::string, std::string>& config = App::Application::Config();
|
|
replacements.insert(std::make_pair("$BuildVersionMajor", config["BuildVersionMajor"]));
|
|
replacements.insert(std::make_pair("$BuildVersionMinor", config["BuildVersionMinor"]));
|
|
replacements.insert(std::make_pair("$BuildVersionMinor", config["BuildVersionPoint"]));
|
|
replacements.insert(std::make_pair("$BuildRevision", config["BuildRevision"]));
|
|
for (const auto& replacement : replacements) {
|
|
auto pos = injectedString.find(replacement.first);
|
|
while (pos != std::string::npos) {
|
|
injectedString.replace(pos, replacement.first.length(), replacement.second);
|
|
pos = injectedString.find(replacement.first);
|
|
}
|
|
}
|
|
auto parsedExpression = App::Expression::parse(nullptr, dep.condition);
|
|
auto result = parsedExpression->eval();
|
|
if (!boost::any_cast<bool>(result->getValueAsAny())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!dep.version_eq.empty()) {
|
|
return _version == Meta::Version(dep.version_eq);
|
|
}
|
|
// Any of the others might be specified in pairs, so only return the "false" case
|
|
|
|
if (!dep.version_lt.empty()) {
|
|
if (!(_version < Meta::Version(dep.version_lt))) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!dep.version_lte.empty()) {
|
|
if (!(_version <= Meta::Version(dep.version_lt))) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!dep.version_gt.empty()) {
|
|
if (!(_version > Meta::Version(dep.version_lt))) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!dep.version_gte.empty()) {
|
|
if (!(_version >= Meta::Version(dep.version_lt))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Metadata::supportsCurrentFreeCAD() const
|
|
{
|
|
static auto fcVersion = Meta::Version();
|
|
if (fcVersion == Meta::Version()) {
|
|
std::map<std::string, std::string>& config = App::Application::Config();
|
|
std::stringstream ss;
|
|
ss << config["BuildVersionMajor"] << "." << config["BuildVersionMinor"] << "."
|
|
<< config["BuildVersionPoint"] << "."
|
|
<< (config["BuildRevision"].empty() ? "0" : config["BuildRevision"]);
|
|
fcVersion = Meta::Version(ss.str());
|
|
}
|
|
|
|
if (_freecadmin != Meta::Version() && _freecadmin > fcVersion) {
|
|
return false;
|
|
}
|
|
if (_freecadmax != Meta::Version() && _freecadmax < fcVersion) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Metadata::appendToElement(DOMElement* root) const
|
|
{
|
|
appendSimpleXMLNode(root, "name", _name);
|
|
appendSimpleXMLNode(root, "type", _type);
|
|
appendSimpleXMLNode(root, "description", _description);
|
|
if (_version != Meta::Version()) {
|
|
// Only append version if it's not 0.0.0
|
|
appendSimpleXMLNode(root, "version", _version.str());
|
|
}
|
|
|
|
if (!_date.empty()) {
|
|
appendSimpleXMLNode(root, "date", _date);
|
|
}
|
|
|
|
for (const auto& maintainer : _maintainer) {
|
|
auto element = appendSimpleXMLNode(root, "maintainer", maintainer.name);
|
|
if (element) {
|
|
addAttribute(element, "email", maintainer.email);
|
|
}
|
|
}
|
|
|
|
for (const auto& license : _license) {
|
|
auto element = appendSimpleXMLNode(root, "license", license.name);
|
|
if (element) {
|
|
addAttribute(element, "file", license.file.string());
|
|
}
|
|
}
|
|
|
|
if (_freecadmin != Meta::Version()) {
|
|
appendSimpleXMLNode(root, "freecadmin", _freecadmin.str());
|
|
}
|
|
|
|
if (_freecadmax != Meta::Version()) {
|
|
appendSimpleXMLNode(root, "freecadmax", _freecadmax.str());
|
|
}
|
|
|
|
if (_pythonmin != Meta::Version()) {
|
|
appendSimpleXMLNode(root, "pythonmin", _pythonmin.str());
|
|
}
|
|
|
|
for (const auto& url : _url) {
|
|
auto element = appendSimpleXMLNode(root, "url", url.location);
|
|
if (element) {
|
|
std::string typeAsString("website");
|
|
switch (url.type) {
|
|
case Meta::UrlType::website:
|
|
typeAsString = "website";
|
|
break;
|
|
case Meta::UrlType::repository:
|
|
typeAsString = "repository";
|
|
break;
|
|
case Meta::UrlType::bugtracker:
|
|
typeAsString = "bugtracker";
|
|
break;
|
|
case Meta::UrlType::readme:
|
|
typeAsString = "readme";
|
|
break;
|
|
case Meta::UrlType::documentation:
|
|
typeAsString = "documentation";
|
|
break;
|
|
case Meta::UrlType::discussion:
|
|
typeAsString = "discussion";
|
|
break;
|
|
}
|
|
addAttribute(element, "type", typeAsString);
|
|
if (url.type == Meta::UrlType::repository) {
|
|
addAttribute(element, "branch", url.branch);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& author : _author) {
|
|
auto element = appendSimpleXMLNode(root, "author", author.name);
|
|
if (element) {
|
|
addAttribute(element, "email", author.email);
|
|
}
|
|
}
|
|
|
|
for (const auto& depend : _depend) {
|
|
addDependencyNode(root, "depend", depend);
|
|
}
|
|
|
|
for (const auto& conflict : _conflict) {
|
|
addDependencyNode(root, "conflict", conflict);
|
|
}
|
|
|
|
for (const auto& replace : _replace) {
|
|
addDependencyNode(root, "replace", replace);
|
|
}
|
|
|
|
for (const auto& tag : _tag) {
|
|
appendSimpleXMLNode(root, "tag", tag);
|
|
}
|
|
|
|
appendSimpleXMLNode(root, "icon", _icon.string());
|
|
|
|
appendSimpleXMLNode(root, "classname", _classname);
|
|
|
|
appendSimpleXMLNode(root, "subdirectory", _subdirectory.string());
|
|
|
|
for (const auto& file : _file) {
|
|
appendSimpleXMLNode(root, "file", file.string());
|
|
}
|
|
|
|
for (const auto& md : _genericMetadata) {
|
|
auto element = appendSimpleXMLNode(root, md.first, md.second.contents);
|
|
for (const auto& attr : md.second.attributes) {
|
|
addAttribute(element, attr.first, attr.second);
|
|
}
|
|
}
|
|
|
|
if (!_content.empty()) {
|
|
auto doc = root->getOwnerDocument();
|
|
DOMElement* contentRootElement = doc->createElement(XUTF8StrLiteral("content").unicodeForm());
|
|
root->appendChild(contentRootElement);
|
|
for (const auto& content : _content) {
|
|
DOMElement* contentElement =
|
|
doc->createElement(XUTF8Str(content.first.c_str()).unicodeForm());
|
|
contentRootElement->appendChild(contentElement);
|
|
content.second.appendToElement(contentElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Metadata::parseVersion1(const DOMNode* startNode)
|
|
{
|
|
auto children = startNode->getChildNodes();
|
|
|
|
for (XMLSize_t i = 0; i < children->getLength(); ++i) {
|
|
auto child = children->item(i);
|
|
auto element = dynamic_cast<const DOMElement*>(child);
|
|
if (!element) {
|
|
continue;
|
|
}
|
|
|
|
auto tag = element->getNodeName();
|
|
auto tagString = StrXUTF8(tag).str;
|
|
|
|
if (tagString == "name") {
|
|
_name = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "type") {
|
|
_type = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "version") {
|
|
_version = Meta::Version(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "date") {
|
|
_date = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "description") {
|
|
_description = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "maintainer") {
|
|
_maintainer.emplace_back(element);
|
|
}
|
|
else if (tagString == "license") {
|
|
_license.emplace_back(element);
|
|
}
|
|
else if (tagString == "freecadmin") {
|
|
_freecadmin = Meta::Version(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "freecadmax") {
|
|
_freecadmax = Meta::Version(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "pythonmin") {
|
|
_pythonmin = Meta::Version(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "url") {
|
|
_url.emplace_back(element);
|
|
}
|
|
else if (tagString == "author") {
|
|
_author.emplace_back(element);
|
|
}
|
|
else if (tagString == "depend") {
|
|
_depend.emplace_back(element);
|
|
}
|
|
else if (tagString == "conflict") {
|
|
_conflict.emplace_back(element);
|
|
}
|
|
else if (tagString == "replace") {
|
|
_replace.emplace_back(element);
|
|
}
|
|
else if (tagString == "tag") {
|
|
_tag.emplace_back(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "file") {
|
|
_file.emplace_back(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "classname") {
|
|
_classname = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "subdirectory") {
|
|
_subdirectory = StrXUTF8(element->getTextContent()).str;
|
|
}
|
|
else if (tagString == "icon") {
|
|
_icon = fs::path(StrXUTF8(element->getTextContent()).str);
|
|
}
|
|
else if (tagString == "content") {
|
|
parseContentNodeVersion1(element); // Recursive call
|
|
}
|
|
else {
|
|
// If none of this node's nodeChildren have nodeChildren of their own, it is a simple
|
|
// element and we can handle it as a GenericMetadata object
|
|
auto nodeChildren = element->getChildNodes();
|
|
bool hasGrandchildren = false;
|
|
for (XMLSize_t j = 0; j < nodeChildren->getLength() && !hasGrandchildren; ++j) {
|
|
if (nodeChildren->item(j)->getChildNodes()->getLength() > 0) {
|
|
hasGrandchildren = true;
|
|
}
|
|
}
|
|
if (!hasGrandchildren) {
|
|
_genericMetadata.insert(std::make_pair(tagString, Meta::GenericMetadata(element)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Metadata::parseContentNodeVersion1(const DOMElement* contentNode)
|
|
{
|
|
auto children = contentNode->getChildNodes();
|
|
for (XMLSize_t i = 0; i < children->getLength(); ++i) {
|
|
auto child = dynamic_cast<const DOMElement*>(children->item(i));
|
|
if (child) {
|
|
auto tag = StrXUTF8(child->getTagName()).str;
|
|
_content.insert(std::make_pair(tag, Metadata(child, 1)));
|
|
}
|
|
}
|
|
}
|
|
|
|
Meta::Contact::Contact(std::string name, std::string email)
|
|
: name(std::move(name))
|
|
, email(std::move(email))
|
|
{
|
|
// This has to be provided manually since we have another constructor
|
|
}
|
|
|
|
Meta::Contact::Contact(const XERCES_CPP_NAMESPACE::DOMElement* elem)
|
|
{
|
|
if (!elem) {
|
|
return;
|
|
}
|
|
auto emailAttribute = elem->getAttribute(XUTF8StrLiteral("email").unicodeForm());
|
|
name = StrXUTF8(elem->getTextContent()).str;
|
|
email = StrXUTF8(emailAttribute).str;
|
|
}
|
|
|
|
bool App::Meta::Contact::operator==(const Contact& rhs) const
|
|
{
|
|
return name == rhs.name && email == rhs.email;
|
|
}
|
|
|
|
Meta::License::License(std::string name, fs::path file)
|
|
: name(std::move(name))
|
|
, file(std::move(file))
|
|
{
|
|
// This has to be provided manually since we have another constructor
|
|
}
|
|
|
|
Meta::License::License(const XERCES_CPP_NAMESPACE::DOMElement* elem)
|
|
{
|
|
if (!elem) {
|
|
return;
|
|
}
|
|
auto fileAttribute = elem->getAttribute(XUTF8StrLiteral("file").unicodeForm());
|
|
if (XMLString::stringLen(fileAttribute) > 0) {
|
|
file = fs::path(StrXUTF8(fileAttribute).str);
|
|
}
|
|
name = StrXUTF8(elem->getTextContent()).str;
|
|
}
|
|
|
|
bool App::Meta::License::operator==(const License& rhs) const
|
|
{
|
|
return name == rhs.name && file == rhs.file;
|
|
}
|
|
|
|
App::Meta::Url::Url()
|
|
: location("")
|
|
, type(App::Meta::UrlType::website)
|
|
{}
|
|
|
|
Meta::Url::Url(std::string location, UrlType type)
|
|
: location(std::move(location))
|
|
, type(type)
|
|
{
|
|
// This has to be provided manually since we have another constructor
|
|
}
|
|
|
|
Meta::Url::Url(const XERCES_CPP_NAMESPACE::DOMElement* elem)
|
|
{
|
|
if (!elem) {
|
|
return;
|
|
}
|
|
auto typeAttribute = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("type").unicodeForm())).str;
|
|
if (typeAttribute.empty() || typeAttribute == "website") {
|
|
type = UrlType::website;
|
|
}
|
|
else if (typeAttribute == "bugtracker") {
|
|
type = UrlType::bugtracker;
|
|
}
|
|
else if (typeAttribute == "repository") {
|
|
type = UrlType::repository;
|
|
}
|
|
else if (typeAttribute == "readme") {
|
|
type = UrlType::readme;
|
|
}
|
|
else if (typeAttribute == "documentation") {
|
|
type = UrlType::documentation;
|
|
}
|
|
else if (typeAttribute == "discussion") {
|
|
type = UrlType::discussion;
|
|
}
|
|
else {
|
|
type = UrlType::website;
|
|
}
|
|
|
|
if (type == UrlType::repository) {
|
|
branch = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("branch").unicodeForm())).str;
|
|
}
|
|
location = StrXUTF8(elem->getTextContent()).str;
|
|
}
|
|
|
|
bool App::Meta::Url::operator==(const Url& rhs) const
|
|
{
|
|
if (type == UrlType::repository && branch != rhs.branch) {
|
|
return false;
|
|
}
|
|
return type == rhs.type && location == rhs.location;
|
|
}
|
|
|
|
App::Meta::Dependency::Dependency()
|
|
: optional(false)
|
|
, dependencyType(App::Meta::DependencyType::automatic)
|
|
{}
|
|
|
|
App::Meta::Dependency::Dependency(std::string pkg)
|
|
: package(std::move(pkg))
|
|
, optional(false)
|
|
, dependencyType(App::Meta::DependencyType::automatic)
|
|
{}
|
|
|
|
Meta::Dependency::Dependency(const XERCES_CPP_NAMESPACE::DOMElement* elem)
|
|
{
|
|
version_lt = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("version_lt").unicodeForm())).str;
|
|
version_lte = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("version_lte").unicodeForm())).str;
|
|
version_eq = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("version_eq").unicodeForm())).str;
|
|
version_gte = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("version_gte").unicodeForm())).str;
|
|
version_gt = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("version_gt").unicodeForm())).str;
|
|
condition = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("condition").unicodeForm())).str;
|
|
std::string opt_string = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("optional").unicodeForm())).str;
|
|
if (opt_string == "true"
|
|
|| opt_string == "True") { // Support Python capitalization in this one case...
|
|
optional = true;
|
|
}
|
|
else {
|
|
optional = false;
|
|
}
|
|
std::string type_string = StrXUTF8(elem->getAttribute(XUTF8StrLiteral("type").unicodeForm())).str;
|
|
if (type_string == "automatic" || type_string.empty()) {
|
|
dependencyType = Meta::DependencyType::automatic;
|
|
}
|
|
else if (type_string == "addon") {
|
|
dependencyType = Meta::DependencyType::addon;
|
|
}
|
|
else if (type_string == "internal") {
|
|
dependencyType = Meta::DependencyType::internal;
|
|
}
|
|
else if (type_string == "python") {
|
|
dependencyType = Meta::DependencyType::python;
|
|
}
|
|
else {
|
|
auto message = std::string("Invalid dependency type \"") + type_string + "\"";
|
|
throw Base::XMLBaseException(message);
|
|
}
|
|
|
|
package = StrXUTF8(elem->getTextContent()).str;
|
|
}
|
|
|
|
bool App::Meta::Dependency::operator==(const Dependency& rhs) const
|
|
{
|
|
return package == rhs.package && version_lt == rhs.version_lt && version_lte == rhs.version_lte
|
|
&& version_eq == rhs.version_eq && version_gte == rhs.version_gte
|
|
&& version_gt == rhs.version_gt && condition == rhs.condition && optional == rhs.optional
|
|
&& dependencyType == rhs.dependencyType;
|
|
}
|
|
|
|
Meta::Version::Version() = default;
|
|
|
|
Meta::Version::Version(int major, int minor, int patch, std::string suffix)
|
|
: major(major)
|
|
, minor(minor)
|
|
, patch(patch)
|
|
, suffix(std::move(suffix))
|
|
{}
|
|
|
|
Meta::Version::Version(const std::string& versionString)
|
|
{
|
|
std::istringstream stream(versionString);
|
|
char separator {'.'};
|
|
stream >> major;
|
|
if (stream) {
|
|
stream >> separator;
|
|
}
|
|
if (stream) {
|
|
stream >> minor;
|
|
}
|
|
if (stream) {
|
|
stream >> separator;
|
|
}
|
|
if (stream) {
|
|
stream >> patch;
|
|
}
|
|
if (stream) {
|
|
stream >> suffix;
|
|
}
|
|
}
|
|
|
|
std::string Meta::Version::str() const
|
|
{
|
|
if (*this == Meta::Version()) {
|
|
return "";
|
|
}
|
|
std::ostringstream stream;
|
|
stream << major << "." << minor << "." << patch << suffix;
|
|
return stream.str();
|
|
}
|
|
|
|
bool Meta::Version::operator<(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
< std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
bool Meta::Version::operator>(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
> std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
bool Meta::Version::operator<=(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
<= std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
bool Meta::Version::operator>=(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
>= std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
bool Meta::Version::operator==(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
== std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
bool Meta::Version::operator!=(const Version& rhs) const
|
|
{
|
|
return std::tie(major, minor, patch, suffix)
|
|
!= std::tie(rhs.major, rhs.minor, rhs.patch, rhs.suffix);
|
|
}
|
|
|
|
Meta::GenericMetadata::GenericMetadata(const XERCES_CPP_NAMESPACE::DOMElement* elem)
|
|
{
|
|
contents = StrXUTF8(elem->getTextContent()).str;
|
|
for (XMLSize_t i = 0; i < elem->getAttributes()->getLength(); ++i) {
|
|
auto attr = elem->getAttributes()->item(i);
|
|
attributes.insert(std::make_pair(StrXUTF8(attr->getNodeName()).str,
|
|
StrXUTF8(attr->getTextContent()).str));
|
|
}
|
|
}
|
|
|
|
App::Meta::GenericMetadata::GenericMetadata(std::string contents)
|
|
: contents(std::move(contents))
|
|
{}
|