Files
create/src/Base/Parameter.cpp
Markus Reitböck f0eca551b3 Base: use CMake to generate precompiled headers on all platforms
"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
2025-09-14 09:47:01 +02:00

2220 lines
68 KiB
C++

/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License (LGPL) *
* as published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* for detail see the LICENCE text file. *
* *
* 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with FreeCAD; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
* *
***************************************************************************/
#include <algorithm>
#include <iostream>
#include <memory>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <xercesc/framework/LocalFileInputSource.hpp>
#include <xercesc/framework/MemBufFormatTarget.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax/EntityResolver.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <sstream>
#include <string>
#include <utility>
#include <QFileInfo>
#include <QLockFile>
#include <QDir>
#include <FCConfig.h>
#ifdef FC_OS_LINUX
#include <unistd.h>
#endif
#include <boost/algorithm/string.hpp>
#include <fmt/printf.h>
#include "Parameter.h"
#include "Parameter.inl"
#include "Console.h"
#include "Exception.h"
#include "Tools.h"
FC_LOG_LEVEL_INIT("Parameter", true, true)
#ifndef XERCES_CPP_NAMESPACE_BEGIN
#define XERCES_CPP_NAMESPACE_QUALIFIER
using namespace XERCES_CPP_NAMESPACE;
#else
XERCES_CPP_NAMESPACE_USE
#endif
using namespace Base;
#include "XMLTools.h"
//**************************************************************************
//**************************************************************************
// private classes declaration:
// - DOMTreeErrorReporter
// - StrX
// - DOMPrintFilter
// - DOMPrintErrorHandler
// - XStr
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class DOMTreeErrorReporter: public ErrorHandler
{
public:
// -----------------------------------------------------------------------
// Implementation of the error handler interface
// -----------------------------------------------------------------------
void warning(const SAXParseException& toCatch) override;
void error(const SAXParseException& toCatch) override;
void fatalError(const SAXParseException& toCatch) override;
void resetErrors() override;
// -----------------------------------------------------------------------
// Getter methods
// -----------------------------------------------------------------------
bool getSawErrors() const;
// -----------------------------------------------------------------------
// Private data members
//
// fSawErrors
// This is set if we get any errors, and is queryable via a getter
// method. Its used by the main code to suppress output if there are
// errors.
// -----------------------------------------------------------------------
bool fSawErrors {false};
};
class DOMPrintFilter: public DOMLSSerializerFilter
{
public:
/** @name Constructors */
explicit DOMPrintFilter(ShowType whatToShow = DOMNodeFilter::SHOW_ALL);
//@{
/** @name Destructors */
~DOMPrintFilter() override = default;
//@{
/** @ interface from DOMWriterFilter */
FilterAction acceptNode(const XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* node) const override;
//@{
ShowType getWhatToShow() const override
{
return fWhatToShow;
}
// unimplemented copy ctor and assignment operator
DOMPrintFilter(const DOMPrintFilter&) = delete;
DOMPrintFilter(DOMPrintFilter&&) = delete;
DOMPrintFilter& operator=(const DOMPrintFilter&) = delete;
DOMPrintFilter& operator=(DOMPrintFilter&&) = delete;
ShowType fWhatToShow;
};
class DOMPrintErrorHandler: public DOMErrorHandler
{
public:
DOMPrintErrorHandler() = default;
~DOMPrintErrorHandler() override = default;
/** @name The error handler interface */
bool handleError(const DOMError& domError) override;
void resetErrors()
{}
/* Unimplemented constructors and operators */
DOMPrintErrorHandler(const DOMPrintErrorHandler&) = delete;
DOMPrintErrorHandler(DOMPrintErrorHandler&&) = delete;
void operator=(const DOMPrintErrorHandler&) = delete;
void operator=(DOMPrintErrorHandler&&) = delete;
};
inline bool DOMTreeErrorReporter::getSawErrors() const
{
return fSawErrors;
}
//**************************************************************************
//**************************************************************************
// ParameterManager
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//**************************************************************************
// Construction/Destruction
/** Default construction
*/
ParameterGrp::ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* GroupNode,
const char* sName,
ParameterGrp* Parent)
: _pGroupNode(GroupNode)
, _Parent(Parent)
{
if (sName) {
_cName = sName;
}
if (_Parent) {
_Manager = _Parent->_Manager;
}
}
/** Destruction
* complete destruction of the object
*/
ParameterGrp::~ParameterGrp()
{
for (auto& v : _GroupMap) {
v.second->_Parent = nullptr;
v.second->_Manager = nullptr;
}
if (_Detached && _pGroupNode) {
_pGroupNode->release();
}
}
//**************************************************************************
// Access methods
void ParameterGrp::copyTo(const Base::Reference<ParameterGrp>& Grp)
{
if (Grp == this) {
return;
}
// delete previous content
Grp->Clear(true);
// copy all
insertTo(Grp);
}
void ParameterGrp::insertTo(const Base::Reference<ParameterGrp>& Grp)
{
if (Grp == this) {
return;
}
// copy group
std::vector<Base::Reference<ParameterGrp>> Grps = GetGroups();
std::vector<Base::Reference<ParameterGrp>>::iterator It1;
for (It1 = Grps.begin(); It1 != Grps.end(); ++It1) {
(*It1)->insertTo(Grp->GetGroup((*It1)->GetGroupName()));
}
// copy strings
std::vector<std::pair<std::string, std::string>> StringMap = GetASCIIMap();
std::vector<std::pair<std::string, std::string>>::iterator It2;
for (It2 = StringMap.begin(); It2 != StringMap.end(); ++It2) {
Grp->SetASCII(It2->first.c_str(), It2->second.c_str());
}
// copy bool
std::vector<std::pair<std::string, bool>> BoolMap = GetBoolMap();
std::vector<std::pair<std::string, bool>>::iterator It3;
for (It3 = BoolMap.begin(); It3 != BoolMap.end(); ++It3) {
Grp->SetBool(It3->first.c_str(), It3->second);
}
// copy int
std::vector<std::pair<std::string, long>> IntMap = GetIntMap();
std::vector<std::pair<std::string, long>>::iterator It4;
for (It4 = IntMap.begin(); It4 != IntMap.end(); ++It4) {
Grp->SetInt(It4->first.c_str(), It4->second);
}
// copy float
std::vector<std::pair<std::string, double>> FloatMap = GetFloatMap();
std::vector<std::pair<std::string, double>>::iterator It5;
for (It5 = FloatMap.begin(); It5 != FloatMap.end(); ++It5) {
Grp->SetFloat(It5->first.c_str(), It5->second);
}
// copy uint
std::vector<std::pair<std::string, unsigned long>> UIntMap = GetUnsignedMap();
std::vector<std::pair<std::string, unsigned long>>::iterator It6;
for (It6 = UIntMap.begin(); It6 != UIntMap.end(); ++It6) {
Grp->SetUnsigned(It6->first.c_str(), It6->second);
}
}
void ParameterGrp::exportTo(const char* FileName)
{
auto Mngr = ParameterManager::Create();
Mngr->CreateDocument();
// copy all into the new document
insertTo(Base::Reference<ParameterGrp>(Mngr));
Mngr->SaveDocument(FileName);
}
void ParameterGrp::importFrom(const char* FileName)
{
auto Mngr = ParameterManager::Create();
if (Mngr->LoadDocument(FileName) != 1) {
throw FileException("ParameterGrp::import() cannot load document", FileName);
}
Mngr->copyTo(Base::Reference<ParameterGrp>(this));
}
void ParameterGrp::insert(const char* FileName)
{
auto Mngr = ParameterManager::Create();
if (Mngr->LoadDocument(FileName) != 1) {
throw FileException("ParameterGrp::import() cannot load document", FileName);
}
Mngr->insertTo(Base::Reference<ParameterGrp>(this));
}
void ParameterGrp::revert(const char* FileName)
{
auto Mngr = ParameterManager::Create();
if (Mngr->LoadDocument(FileName) != 1) {
throw FileException("ParameterGrp::revert() cannot load document", FileName);
}
revert(Base::Reference<ParameterGrp>(Mngr));
}
void ParameterGrp::revert(const Base::Reference<ParameterGrp>& Grp)
{
if (Grp == this) {
return;
}
for (auto& grp : Grp->GetGroups()) {
if (HasGroup(grp->GetGroupName())) {
GetGroup(grp->GetGroupName())->revert(grp);
}
}
for (const auto& v : Grp->GetASCIIMap()) {
if (GetASCII(v.first.c_str(), v.second.c_str()) == v.second) {
RemoveASCII(v.first.c_str());
}
}
for (const auto& v : Grp->GetBoolMap()) {
if (GetBool(v.first.c_str(), v.second) == v.second) {
RemoveBool(v.first.c_str());
}
}
for (const auto& v : Grp->GetIntMap()) {
if (GetInt(v.first.c_str(), v.second) == v.second) {
RemoveInt(v.first.c_str());
}
}
for (const auto& v : Grp->GetUnsignedMap()) {
if (GetUnsigned(v.first.c_str(), v.second) == v.second) {
RemoveUnsigned(v.first.c_str());
}
}
for (const auto& v : Grp->GetFloatMap()) {
if (GetFloat(v.first.c_str(), v.second) == v.second) {
RemoveFloat(v.first.c_str());
}
}
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement*
ParameterGrp::CreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* Start,
const char* Type,
const char* Name)
{
if (XMLString::compareString(Start->getNodeName(), XStrLiteral("FCParamGroup").unicodeForm())
!= 0
&& XMLString::compareString(Start->getNodeName(), XStrLiteral("FCParameters").unicodeForm())
!= 0) {
Base::Console().warning("CreateElement: %s cannot have the element %s of type %s\n",
StrX(Start->getNodeName()).c_str(),
Name,
Type);
return nullptr;
}
if (_Detached && _Parent) {
// re-attach the group
_Parent->_GetGroup(_cName.c_str());
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* pDocument = Start->getOwnerDocument();
auto pcElem = pDocument->createElement(XStr(Type).unicodeForm());
pcElem->setAttribute(XStrLiteral("Name").unicodeForm(), XStr(Name).unicodeForm());
Start->appendChild(pcElem);
return pcElem;
}
Base::Reference<ParameterGrp> ParameterGrp::GetGroup(const char* Name)
{
if (!Name) {
throw Base::ValueError("Empty group name");
}
Base::Reference<ParameterGrp> hGrp = this;
std::vector<std::string> tokens;
boost::split(tokens, Name, boost::is_any_of("/"));
for (auto& token : tokens) {
boost::trim(token);
if (token.empty()) {
continue;
}
hGrp = hGrp->_GetGroup(token.c_str());
if (!hGrp) {
// The group is clearing. Return some dummy group to avoid caller
// crashing for backward compatibility.
hGrp = new ParameterGrp();
hGrp->_cName = Name;
break;
}
}
if (hGrp == this) {
throw Base::ValueError("Empty group name");
}
return hGrp;
}
Base::Reference<ParameterGrp> ParameterGrp::_GetGroup(const char* Name)
{
Base::Reference<ParameterGrp> rParamGrp;
if (!_pGroupNode) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Adding group " << Name << " in an orphan group " << _cName);
}
return rParamGrp;
}
if (_Clearing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Adding group " << Name << " while clearing " << GetPath());
}
return rParamGrp;
}
DOMElement* pcTemp {};
// search if Group node already there
pcTemp = FindElement(_pGroupNode, "FCParamGroup", Name);
// already created?
if (!(rParamGrp = _GroupMap[Name]).isValid()) {
if (!pcTemp) {
pcTemp = CreateElement(_pGroupNode, "FCParamGroup", Name);
}
// create and register handle
rParamGrp = Base::Reference<ParameterGrp>(new ParameterGrp(pcTemp, Name, this));
_GroupMap[Name] = rParamGrp;
}
else if (!pcTemp) {
_pGroupNode->appendChild(rParamGrp->_pGroupNode);
rParamGrp->_Detached = false;
if (this->_Detached && this->_Parent) {
// Re-attach the group. Note that this may fail if the parent is
// clearing. That's why we check this->_Detached below.
this->_Parent->_GetGroup(_cName.c_str());
}
}
if (!pcTemp && !this->_Detached) {
_Notify(ParamType::FCGroup, Name, Name);
}
return rParamGrp;
}
std::string ParameterGrp::GetPath() const
{
std::string path;
if (_Parent && _Parent != _Manager) {
path = _Parent->GetPath();
}
if (!path.empty() && !_cName.empty()) {
path += "/";
}
path += _cName;
return path;
}
std::vector<Base::Reference<ParameterGrp>> ParameterGrp::GetGroups()
{
Base::Reference<ParameterGrp> rParamGrp;
std::vector<Base::Reference<ParameterGrp>> vrParamGrp;
if (!_pGroupNode) {
return vrParamGrp;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCParamGroup");
while (pcTemp) {
Name = StrX(pcTemp->getAttributes()
->getNamedItem(XStrLiteral("Name").unicodeForm())
->getNodeValue())
.c_str();
// already created?
if (!(rParamGrp = _GroupMap[Name]).isValid()) {
rParamGrp = Base::Reference<ParameterGrp>(new ParameterGrp(pcTemp, Name.c_str(), this));
_GroupMap[Name] = rParamGrp;
}
vrParamGrp.push_back(rParamGrp);
// go to next
pcTemp = FindNextElement(pcTemp, "FCParamGroup");
}
return vrParamGrp;
}
/// test if this group is empty
bool ParameterGrp::IsEmpty() const
{
return !(_pGroupNode && _pGroupNode->getFirstChild());
}
/// test if a special sub group is in this group
bool ParameterGrp::HasGroup(const char* Name) const
{
if (_GroupMap.find(Name) != _GroupMap.end()) {
return true;
}
if (_pGroupNode && FindElement(_pGroupNode, "FCParamGroup", Name) != nullptr) {
return true;
}
return false;
}
const char* ParameterGrp::TypeName(ParamType Type)
{
switch (Type) {
case ParamType::FCBool:
return "FCBool";
case ParamType::FCInt:
return "FCInt";
case ParamType::FCUInt:
return "FCUInt";
case ParamType::FCText:
return "FCText";
case ParamType::FCFloat:
return "FCFloat";
case ParamType::FCGroup:
return "FCParamGroup";
default:
return nullptr;
}
}
ParameterGrp::ParamType ParameterGrp::TypeValue(const char* Name)
{
if (Name) {
if (boost::equals(Name, "FCBool")) {
return ParamType::FCBool;
}
if (boost::equals(Name, "FCInt")) {
return ParamType::FCInt;
}
if (boost::equals(Name, "FCUInt")) {
return ParamType::FCUInt;
}
if (boost::equals(Name, "FCText")) {
return ParamType::FCText;
}
if (boost::equals(Name, "FCFloat")) {
return ParamType::FCFloat;
}
if (boost::equals(Name, "FCParamGroup")) {
return ParamType::FCGroup;
}
}
return ParamType::FCInvalid;
}
void ParameterGrp::SetAttribute(ParamType Type, const char* Name, const char* Value)
{
switch (Type) {
case ParamType::FCBool:
case ParamType::FCInt:
case ParamType::FCUInt:
case ParamType::FCFloat:
return _SetAttribute(Type, Name, Value);
case ParamType::FCText:
return SetASCII(Name, Value);
case ParamType::FCGroup:
RenameGrp(Name, Value);
break;
default:
break;
}
}
void ParameterGrp::RemoveAttribute(ParamType Type, const char* Name)
{
switch (Type) {
case ParamType::FCBool:
return RemoveBool(Name);
case ParamType::FCInt:
return RemoveInt(Name);
case ParamType::FCUInt:
return RemoveUnsigned(Name);
case ParamType::FCText:
return RemoveASCII(Name);
case ParamType::FCFloat:
return RemoveFloat(Name);
case ParamType::FCGroup:
return RemoveGrp(Name);
default:
break;
}
}
const char* ParameterGrp::GetAttribute(ParamType Type,
const char* Name,
std::string& Value,
const char* Default) const
{
if (!_pGroupNode) {
return Default;
}
const char* T = TypeName(Type);
if (!T) {
return Default;
}
DOMElement* pcElem = FindElement(_pGroupNode, T, Name);
if (!pcElem) {
return Default;
}
if (Type == ParamType::FCText) {
Value = GetASCII(Name, Default);
}
else if (Type != ParamType::FCGroup) {
Value = StrX(pcElem->getAttribute(XStrLiteral("Value").unicodeForm())).c_str();
}
return Value.c_str();
}
std::vector<std::pair<std::string, std::string>>
ParameterGrp::GetAttributeMap(ParamType Type, const char* sFilter) const
{
std::vector<std::pair<std::string, std::string>> res;
if (!_pGroupNode) {
return res;
}
const char* T = TypeName(Type);
if (!T) {
return res;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, T);
while (pcTemp) {
Name = StrX(static_cast<DOMElement*>(pcTemp)
->getAttributes()
->getNamedItem(XStrLiteral("Name").unicodeForm())
->getNodeValue())
.c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
if (Type == ParamType::FCGroup) {
res.emplace_back(Name, std::string());
}
else {
res.emplace_back(Name,
StrX(static_cast<DOMElement*>(pcTemp)->getAttribute(
XStrLiteral("Value").unicodeForm()))
.c_str());
}
}
pcTemp = FindNextElement(pcTemp, T);
}
return res;
}
void ParameterGrp::_Notify(ParamType Type, const char* Name, const char* Value)
{
if (_Manager) {
_Manager->signalParamChanged(this, Type, Name, Value);
}
}
void ParameterGrp::_SetAttribute(ParamType T, const char* Name, const char* Value)
{
const char* Type = TypeName(T);
if (!Type) {
return;
}
if (!_pGroupNode) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Setting attribute " << Type << ":" << Name << " in an orphan group "
<< _cName);
}
return;
}
if (_Clearing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Adding attribute " << Type << ":" << Name << " while clearing " << GetPath());
}
return;
}
// find or create the Element
DOMElement* pcElem = FindOrCreateElement(_pGroupNode, Type, Name);
if (pcElem) {
XStr attr("Value");
// set the value only if different
if (strcmp(StrX(pcElem->getAttribute(attr.unicodeForm())).c_str(), Value) != 0) {
pcElem->setAttribute(attr.unicodeForm(), XStr(Value).unicodeForm());
// trigger observer
_Notify(T, Name, Value);
}
// For backward compatibility, old observer gets notified regardless of
// value changes or not.
Notify(Name);
}
}
bool ParameterGrp::GetBool(const char* Name, bool bPreset) const
{
if (!_pGroupNode) {
return bPreset;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCBool", Name);
// if not return preset
if (!pcElem) {
return bPreset;
}
// if yes check the value and return
return (strcmp(StrX(pcElem->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(), "1")
== 0);
}
void ParameterGrp::SetBool(const char* Name, bool bValue)
{
_SetAttribute(ParamType::FCBool, Name, bValue ? "1" : "0");
}
std::vector<bool> ParameterGrp::GetBools(const char* sFilter) const
{
std::vector<bool> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCBool");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
if (strcmp(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(), "1")
!= 0) {
vrValues.push_back(false);
}
else {
vrValues.push_back(true);
}
}
pcTemp = FindNextElement(pcTemp, "FCBool");
}
return vrValues;
}
std::vector<std::pair<std::string, bool>> ParameterGrp::GetBoolMap(const char* sFilter) const
{
std::vector<std::pair<std::string, bool>> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCBool");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
if (strcmp(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(), "1")
!= 0) {
vrValues.emplace_back(Name, false);
}
else {
vrValues.emplace_back(Name, true);
}
}
pcTemp = FindNextElement(pcTemp, "FCBool");
}
return vrValues;
}
long ParameterGrp::GetInt(const char* Name, long lPreset) const
{
if (!_pGroupNode) {
return lPreset;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCInt", Name);
// if not return preset
if (!pcElem) {
return lPreset;
}
// if yes check the value and return
return atol(StrX(pcElem->getAttribute(XStrLiteral("Value").unicodeForm())).c_str());
}
void ParameterGrp::SetInt(const char* Name, long lValue)
{
std::string buf = fmt::sprintf("%li", lValue);
_SetAttribute(ParamType::FCInt, Name, buf.c_str());
}
std::vector<long> ParameterGrp::GetInts(const char* sFilter) const
{
std::vector<long> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCInt");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.push_back(
atol(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str()));
}
pcTemp = FindNextElement(pcTemp, "FCInt");
}
return vrValues;
}
std::vector<std::pair<std::string, long>> ParameterGrp::GetIntMap(const char* sFilter) const
{
std::vector<std::pair<std::string, long>> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCInt");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.emplace_back(
Name,
(atol(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str())));
}
pcTemp = FindNextElement(pcTemp, "FCInt");
}
return vrValues;
}
unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset) const
{
if (!_pGroupNode) {
return lPreset;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCUInt", Name);
// if not return preset
if (!pcElem) {
return lPreset;
}
// if yes check the value and return
const int base = 10;
return strtoul(StrX(pcElem->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(),
nullptr,
base);
}
void ParameterGrp::SetUnsigned(const char* Name, unsigned long lValue)
{
std::string buf = fmt::sprintf("%lu", lValue);
_SetAttribute(ParamType::FCUInt, Name, buf.c_str());
}
std::vector<unsigned long> ParameterGrp::GetUnsigneds(const char* sFilter) const
{
std::vector<unsigned long> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
const int base = 10;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.push_back(
strtoul(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(),
nullptr,
base));
}
pcTemp = FindNextElement(pcTemp, "FCUInt");
}
return vrValues;
}
std::vector<std::pair<std::string, unsigned long>>
ParameterGrp::GetUnsignedMap(const char* sFilter) const
{
std::vector<std::pair<std::string, unsigned long>> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
const int base = 10;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.emplace_back(
Name,
(strtoul(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str(),
nullptr,
base)));
}
pcTemp = FindNextElement(pcTemp, "FCUInt");
}
return vrValues;
}
double ParameterGrp::GetFloat(const char* Name, double dPreset) const
{
if (!_pGroupNode) {
return dPreset;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCFloat", Name);
// if not return preset
if (!pcElem) {
return dPreset;
}
// if yes check the value and return
return atof(StrX(pcElem->getAttribute(XStrLiteral("Value").unicodeForm())).c_str());
}
void ParameterGrp::SetFloat(const char* Name, double dValue)
{
// use %.12f instead of %f to handle values < 1.0e-6
std::string buf = fmt::sprintf("%.12f", dValue);
_SetAttribute(ParamType::FCFloat, Name, buf.c_str());
}
std::vector<double> ParameterGrp::GetFloats(const char* sFilter) const
{
std::vector<double> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCFloat");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.push_back(
atof(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str()));
}
pcTemp = FindNextElement(pcTemp, "FCFloat");
}
return vrValues;
}
std::vector<std::pair<std::string, double>> ParameterGrp::GetFloatMap(const char* sFilter) const
{
std::vector<std::pair<std::string, double>> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCFloat");
while (pcTemp) {
Name = StrX(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
vrValues.emplace_back(
Name,
(atof(StrX(pcTemp->getAttribute(XStrLiteral("Value").unicodeForm())).c_str())));
}
pcTemp = FindNextElement(pcTemp, "FCFloat");
}
return vrValues;
}
void ParameterGrp::SetASCII(const char* Name, const char* sValue)
{
if (!_pGroupNode) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Setting attribute " << "FCText:" << Name << " in an orphan group " << _cName);
}
return;
}
if (_Clearing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Adding attribute " << "FCText:" << Name << " while clearing " << GetPath());
}
return;
}
bool isNew = false;
DOMElement* pcElem = FindElement(_pGroupNode, "FCText", Name);
if (!pcElem) {
pcElem = CreateElement(_pGroupNode, "FCText", Name);
isNew = true;
}
if (pcElem) {
// and set the value
DOMNode* pcElem2 = pcElem->getFirstChild();
if (!pcElem2) {
XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* pDocument = _pGroupNode->getOwnerDocument();
DOMText* pText = pDocument->createTextNode(XUTF8Str(sValue).unicodeForm());
pcElem->appendChild(pText);
if (isNew || sValue[0] != 0) {
_Notify(ParamType::FCText, Name, sValue);
}
}
else if (strcmp(StrXUTF8(pcElem2->getNodeValue()).c_str(), sValue) != 0) {
pcElem2->setNodeValue(XUTF8Str(sValue).unicodeForm());
_Notify(ParamType::FCText, Name, sValue);
}
// trigger observer
Notify(Name);
}
}
std::string ParameterGrp::GetASCII(const char* Name, const char* pPreset) const
{
if (!_pGroupNode) {
return pPreset ? pPreset : "";
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCText", Name);
// if not return preset
if (!pcElem) {
if (!pPreset) {
return {};
}
return {pPreset};
}
// if yes check the value and return
DOMNode* pcElem2 = pcElem->getFirstChild();
if (pcElem2) {
return {StrXUTF8(pcElem2->getNodeValue()).c_str()};
}
return {};
}
std::vector<std::string> ParameterGrp::GetASCIIs(const char* sFilter) const
{
std::vector<std::string> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCText");
while (pcTemp) {
Name = StrXUTF8(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
// retrieve the text element
DOMNode* pcElem2 = pcTemp->getFirstChild();
if (pcElem2) {
vrValues.emplace_back(StrXUTF8(pcElem2->getNodeValue()).c_str());
}
else {
vrValues.emplace_back(""); // For a string, an empty value is possible and allowed
}
}
pcTemp = FindNextElement(pcTemp, "FCText");
}
return vrValues;
}
std::vector<std::pair<std::string, std::string>>
ParameterGrp::GetASCIIMap(const char* sFilter) const
{
std::vector<std::pair<std::string, std::string>> vrValues;
if (!_pGroupNode) {
return vrValues;
}
std::string Name;
DOMElement* pcTemp = FindElement(_pGroupNode, "FCText");
while (pcTemp) {
Name = StrXUTF8(pcTemp->getAttribute(XStrLiteral("Name").unicodeForm())).c_str();
// check on filter condition
if (!sFilter || Name.find(sFilter) != std::string::npos) {
// retrieve the text element
DOMNode* pcElem2 = pcTemp->getFirstChild();
if (pcElem2) {
vrValues.emplace_back(Name, std::string(StrXUTF8(pcElem2->getNodeValue()).c_str()));
}
else {
vrValues.emplace_back(
Name,
std::string()); // For a string, an empty value is possible and allowed
}
}
pcTemp = FindNextElement(pcTemp, "FCText");
}
return vrValues;
}
//**************************************************************************
// Access methods
void ParameterGrp::RemoveASCII(const char* Name)
{
if (!_pGroupNode) {
return;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCText", Name);
// if not return
if (!pcElem) {
return;
}
DOMNode* node = _pGroupNode->removeChild(pcElem);
node->release();
// trigger observer
_Notify(ParamType::FCText, Name, nullptr);
Notify(Name);
}
void ParameterGrp::RemoveBool(const char* Name)
{
if (!_pGroupNode) {
return;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCBool", Name);
// if not return
if (!pcElem) {
return;
}
DOMNode* node = _pGroupNode->removeChild(pcElem);
node->release();
// trigger observer
_Notify(ParamType::FCBool, Name, nullptr);
Notify(Name);
}
void ParameterGrp::RemoveFloat(const char* Name)
{
if (!_pGroupNode) {
return;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCFloat", Name);
// if not return
if (!pcElem) {
return;
}
DOMNode* node = _pGroupNode->removeChild(pcElem);
node->release();
// trigger observer
_Notify(ParamType::FCFloat, Name, nullptr);
Notify(Name);
}
void ParameterGrp::RemoveInt(const char* Name)
{
if (!_pGroupNode) {
return;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCInt", Name);
// if not return
if (!pcElem) {
return;
}
DOMNode* node = _pGroupNode->removeChild(pcElem);
node->release();
// trigger observer
_Notify(ParamType::FCInt, Name, nullptr);
Notify(Name);
}
void ParameterGrp::RemoveUnsigned(const char* Name)
{
if (!_pGroupNode) {
return;
}
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCUInt", Name);
// if not return
if (!pcElem) {
return;
}
DOMNode* node = _pGroupNode->removeChild(pcElem);
node->release();
// trigger observer
_Notify(ParamType::FCUInt, Name, nullptr);
Notify(Name);
}
Base::Color ParameterGrp::GetColor(const char* Name, Base::Color lPreset) const
{
auto packed = GetUnsigned(Name, lPreset.getPackedValue());
return Color(static_cast<uint32_t>(packed));
}
void ParameterGrp::SetColor(const char* Name, Base::Color lValue)
{
SetUnsigned(Name, lValue.getPackedValue());
}
std::vector<Base::Color> ParameterGrp::GetColors(const char* sFilter) const
{
auto packed = GetUnsigneds(sFilter);
std::vector<Base::Color> result;
std::transform(packed.begin(),
packed.end(),
std::back_inserter(result),
[](const unsigned long lValue) {
return Color(static_cast<uint32_t>(lValue));
});
return result;
}
std::vector<std::pair<std::string, Base::Color>>
ParameterGrp::GetColorMap(const char* sFilter) const
{
auto packed = GetUnsignedMap(sFilter);
std::vector<std::pair<std::string, Base::Color>> result;
std::transform(packed.begin(),
packed.end(),
std::back_inserter(result),
[](const std::pair<std::string, unsigned long>& lValue) {
return std::make_pair(lValue.first,
Color(static_cast<uint32_t>(lValue.second)));
});
return result;
}
void ParameterGrp::RemoveColor(const char* Name)
{
RemoveUnsigned(Name);
}
void ParameterGrp::RemoveGrp(const char* Name)
{
if (!_pGroupNode) {
return;
}
auto it = _GroupMap.find(Name);
if (it == _GroupMap.end()) {
return;
}
// If this or any of its children is referenced by an observer we do not
// delete the handle, just in case the group is later added again, or else
// those existing observer won't get any notification. BUT, we DO delete
// the underlying xml elements, so that we don't save the empty group
// later.
it->second->Clear(false);
if (!it->second->_Detached) {
it->second->_Detached = true;
_pGroupNode->removeChild(it->second->_pGroupNode);
}
if (it->second->ShouldRemove()) {
it->second->_Parent = nullptr;
it->second->_Manager = nullptr;
_GroupMap.erase(it);
}
// trigger observer
Notify(Name);
}
bool ParameterGrp::RenameGrp(const char* OldName, const char* NewName)
{
if (!_pGroupNode) {
return false;
}
auto it = _GroupMap.find(OldName);
if (it == _GroupMap.end()) {
return false;
}
auto jt = _GroupMap.find(NewName);
if (jt != _GroupMap.end()) {
return false;
}
// rename group handle
_GroupMap[NewName] = _GroupMap[OldName];
_GroupMap.erase(OldName);
_GroupMap[NewName]->_cName = NewName;
// check if Element in group
DOMElement* pcElem = FindElement(_pGroupNode, "FCParamGroup", OldName);
if (pcElem) {
pcElem->setAttribute(XStrLiteral("Name").unicodeForm(), XStr(NewName).unicodeForm());
}
_Notify(ParamType::FCGroup, NewName, OldName);
return true;
}
void ParameterGrp::Clear(bool notify)
{
if (!_pGroupNode) {
return;
}
Base::StateLocker guard(_Clearing);
// early trigger notification of group removal when all its children
// hierarchies are intact.
_Notify(ParamType::FCGroup, nullptr, nullptr);
// checking on references
for (auto it = _GroupMap.begin(); it != _GroupMap.end();) {
// If a group handle is referenced by some observer, then do not remove
// it but clear it, so that any existing observer can still get
// notification if the group is later on add back. We do remove the
// underlying xml element from its parent so that we won't save this
// empty group.
it->second->Clear(notify);
if (!it->second->_Detached) {
it->second->_Detached = true;
_pGroupNode->removeChild(it->second->_pGroupNode);
}
if (!it->second->ShouldRemove()) {
++it;
}
else {
it->second->_Parent = nullptr;
it->second->_Manager = nullptr;
it = _GroupMap.erase(it);
}
}
// Remove the rest of non-group nodes;
std::vector<std::pair<ParamType, std::string>> params;
for (DOMNode *child = _pGroupNode->getFirstChild(), *next = child; child != nullptr;
child = next) {
next = next->getNextSibling();
ParamType type = TypeValue(StrX(child->getNodeName()).c_str());
if (type != ParamType::FCInvalid && type != ParamType::FCGroup) {
params.emplace_back(type,
StrX(child->getAttributes()
->getNamedItem(XStrLiteral("Name").unicodeForm())
->getNodeValue())
.c_str());
}
DOMNode* node = _pGroupNode->removeChild(child);
node->release();
}
for (auto& v : params) {
_Notify(v.first, v.second.c_str(), nullptr);
if (notify) {
Notify(v.second.c_str());
}
}
// trigger observer
Notify("");
}
//**************************************************************************
// Access methods
bool ParameterGrp::ShouldRemove() const
{
if (this->getRefCount() > 1) {
return false;
}
return std::all_of(_GroupMap.cbegin(), _GroupMap.cend(), [](const auto& it) {
return it.second->ShouldRemove();
});
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement*
ParameterGrp::FindElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* Start,
const char* Type,
const char* Name) const
{
if (XMLString::compareString(Start->getNodeName(), XStrLiteral("FCParamGroup").unicodeForm())
!= 0
&& XMLString::compareString(Start->getNodeName(), XStrLiteral("FCParameters").unicodeForm())
!= 0) {
Base::Console().warning("FindElement: %s cannot have the element %s of type %s\n",
StrX(Start->getNodeName()).c_str(),
Name,
Type);
return nullptr;
}
const XStr xType(Type);
const XStr xName(Name);
for (DOMNode* clChild = Start->getFirstChild(); clChild != nullptr;
clChild = clChild->getNextSibling()) {
if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) {
// the right node Type
if (!XMLString::compareString(xType.unicodeForm(), clChild->getNodeName())) {
auto attrs = clChild->getAttributes();
if (attrs->getLength() > 0) {
if (Name) {
DOMNode* attr = attrs->getNamedItem(XStrLiteral("Name").unicodeForm());
if (attr
&& !XMLString::compareString(xName.unicodeForm(),
attr->getNodeValue())) {
return dynamic_cast<DOMElement*>(clChild);
}
}
else {
return dynamic_cast<DOMElement*>(clChild);
}
}
}
}
}
return nullptr;
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement*
ParameterGrp::FindNextElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* Prev, const char* Type) const
{
DOMNode* clChild = Prev;
if (!clChild) {
return nullptr;
}
const XStr xType(Type);
while ((clChild = clChild->getNextSibling()) != nullptr) {
if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) {
// the right node Type
if (!XMLString::compareString(xType.unicodeForm(), clChild->getNodeName())) {
return dynamic_cast<DOMElement*>(clChild);
}
}
}
return nullptr;
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement*
ParameterGrp::FindOrCreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* Start,
const char* Type,
const char* Name)
{
// first try to find it
DOMElement* pcElem = FindElement(Start, Type, Name);
if (pcElem) {
return pcElem;
}
return CreateElement(Start, Type, Name);
}
XERCES_CPP_NAMESPACE_QUALIFIER DOMNode*
ParameterGrp::FindAttribute(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* Node, const char* Name) const
{
DOMNamedNodeMap* attr = Node->getAttributes();
if (attr) {
return attr->getNamedItem(XStr(Name).unicodeForm());
}
return nullptr;
}
std::vector<std::pair<ParameterGrp::ParamType, std::string>>
ParameterGrp::GetParameterNames(const char* sFilter) const
{
std::vector<std::pair<ParameterGrp::ParamType, std::string>> res;
if (!_pGroupNode) {
return res;
}
std::string Name;
for (DOMNode* clChild = _pGroupNode->getFirstChild(); clChild != nullptr;
clChild = clChild->getNextSibling()) {
if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) {
StrX type(clChild->getNodeName());
ParamType Type = TypeValue(type.c_str());
if (Type != ParamType::FCInvalid && Type != ParamType::FCGroup) {
if (clChild->getAttributes()->getLength() > 0) {
StrX name(clChild->getAttributes()
->getNamedItem(XStrLiteral("Name").unicodeForm())
->getNodeValue());
if (!sFilter || strstr(name.c_str(), sFilter)) {
res.emplace_back(Type, name.c_str());
}
}
}
}
}
return res;
}
void ParameterGrp::NotifyAll()
{
// get all ints and notify
std::vector<std::pair<std::string, long>> IntMap = GetIntMap();
for (const auto& it : IntMap) {
Notify(it.first.c_str());
}
// get all booleans and notify
std::vector<std::pair<std::string, bool>> BoolMap = GetBoolMap();
for (const auto& it : BoolMap) {
Notify(it.first.c_str());
}
// get all Floats and notify
std::vector<std::pair<std::string, double>> FloatMap = GetFloatMap();
for (const auto& it : FloatMap) {
Notify(it.first.c_str());
}
// get all strings and notify
std::vector<std::pair<std::string, std::string>> StringMap = GetASCIIMap();
for (const auto& it : StringMap) {
Notify(it.first.c_str());
}
// get all uints and notify
std::vector<std::pair<std::string, unsigned long>> UIntMap = GetUnsignedMap();
for (const auto& it : UIntMap) {
Notify(it.first.c_str());
}
}
void ParameterGrp::_Reset()
{
_pGroupNode = nullptr;
for (auto& v : _GroupMap) {
v.second->_Reset();
}
}
//**************************************************************************
//**************************************************************************
// ParameterSerializer
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ParameterSerializer::ParameterSerializer(std::string fn)
: filename(std::move(fn))
{}
ParameterSerializer::~ParameterSerializer() = default;
void ParameterSerializer::SaveDocument(const ParameterManager& mgr)
{
mgr.SaveDocument(filename.c_str());
}
int ParameterSerializer::LoadDocument(ParameterManager& mgr)
{
return mgr.LoadDocument(filename.c_str());
}
bool ParameterSerializer::LoadOrCreateDocument(ParameterManager& mgr)
{
return mgr.LoadOrCreateDocument(filename.c_str());
}
//**************************************************************************
//**************************************************************************
// ParameterManager
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Auto; // NOLINT
//**************************************************************************
// Construction/Destruction
/** Default construction
*/
ParameterManager::ParameterManager()
{
_Manager = this;
// initialize the XML system
Init();
// ---------------------------------------------------------------------------
// Local data
//
// gXmlFile
// The path to the file to parser. Set via command line.
//
// gDoNamespaces
// Indicates whether namespace processing should be done.
//
// gDoSchema
// Indicates whether schema processing should be done.
//
// gSchemaFullChecking
// Indicates whether full schema constraint checking should be done.
//
// gDoCreate
// Indicates whether entity reference nodes needs to be created or not.
// Defaults to false.
//
// gOutputEncoding
// The encoding we are to output in. If not set on the command line,
// then it is defaults to the encoding of the input XML file.
//
// gMyEOLSequence
// The end of line sequence we are to output.
//
// gSplitCdataSections
// Indicates whether split-cdata-sections is to be enabled or not.
//
// gDiscardDefaultContent
// Indicates whether default content is discarded or not.
//
// gUseFilter
// Indicates if user wants to plug in the DOMPrintFilter.
//
// gValScheme
// Indicates what validation scheme to use. It defaults to 'auto', but
// can be set via the -v= command.
//
// ---------------------------------------------------------------------------
// NOLINTBEGIN
gIgnoreSave = false;
gDoNamespaces = false;
gDoSchema = false;
gSchemaFullChecking = false;
gDoCreate = true;
gOutputEncoding = nullptr;
gMyEOLSequence = nullptr;
gSplitCdataSections = true;
gDiscardDefaultContent = true;
gUseFilter = true;
gFormatPrettyPrint = true;
// NOLINTEND
}
/** Destruction
* complete destruction of the object
*/
ParameterManager::~ParameterManager()
{
_Reset();
delete _pDocument;
delete paramSerializer;
}
Base::Reference<ParameterManager> ParameterManager::Create()
{
return {new ParameterManager()};
}
void ParameterManager::Init()
{
static bool Init = false;
if (!Init) {
try {
XMLPlatformUtils::Initialize();
}
catch (const XMLException& toCatch) {
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN)
std::ostringstream err;
#else
std::stringstream err;
#endif
char* pMsg = XMLString::transcode(toCatch.getMessage());
err << "Error during Xerces-c Initialization.\n"
<< " Exception message:" << pMsg;
XMLString::release(&pMsg);
throw XMLBaseException(err.str().c_str());
}
Init = true;
}
}
void ParameterManager::Terminate()
{
XMLTools::terminate();
XMLPlatformUtils::Terminate();
}
//**************************************************************************
// Serializer handling
void ParameterManager::SetSerializer(ParameterSerializer* ps)
{
if (paramSerializer != ps) {
delete paramSerializer;
}
paramSerializer = ps;
}
bool ParameterManager::HasSerializer() const
{
return (paramSerializer != nullptr);
}
const std::string& ParameterManager::GetSerializeFileName() const
{
static const std::string _dummy;
return paramSerializer ? paramSerializer->GetFileName() : _dummy;
}
int ParameterManager::LoadDocument()
{
if (paramSerializer) {
return paramSerializer->LoadDocument(*this);
}
return -1;
}
bool ParameterManager::LoadOrCreateDocument()
{
if (paramSerializer) {
return paramSerializer->LoadOrCreateDocument(*this);
}
return false;
}
void ParameterManager::SaveDocument() const
{
if (paramSerializer) {
paramSerializer->SaveDocument(*this);
}
}
void ParameterManager::SetIgnoreSave(bool value)
{
gIgnoreSave = value;
}
bool ParameterManager::IgnoreSave() const
{
return gIgnoreSave;
}
namespace
{
QString getLockFile(const Base::FileInfo& file)
{
QFileInfo fi(QDir::tempPath(), QString::fromStdString(file.fileName() + ".lock"));
return fi.absoluteFilePath();
}
int getTimeout()
{
const int timeout = 5000;
return timeout;
}
} // namespace
//**************************************************************************
// Document handling
bool ParameterManager::LoadOrCreateDocument(const char* sFileName)
{
Base::FileInfo file(sFileName);
if (file.exists()) {
LoadDocument(sFileName);
return false;
}
CreateDocument();
return true;
}
int ParameterManager::LoadDocument(const char* sFileName)
{
try {
Base::FileInfo file(sFileName);
QLockFile lock(getLockFile(file));
if (!lock.tryLock(getTimeout())) {
// Continue with empty config
CreateDocument();
SetIgnoreSave(true);
std::cerr << "Failed to access file for reading: " << sFileName << std::endl;
return 1;
}
#if defined(FC_OS_WIN32)
std::wstring name = file.toStdWString();
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
LocalFileInputSource inputSource(reinterpret_cast<const XMLCh*>(name.c_str()));
#else
LocalFileInputSource inputSource(XStr(file.filePath().c_str()).unicodeForm());
#endif
return LoadDocument(inputSource);
}
catch (const Base::Exception& e) {
std::cerr << e.what() << std::endl;
throw;
}
catch (...) {
std::cerr << "An error occurred during parsing\n " << std::endl;
throw;
}
}
class NoOpEntityResolver: public XERCES_CPP_NAMESPACE_QUALIFIER EntityResolver
{
public:
InputSource* resolveEntity(const XMLCh* const publicId, const XMLCh* const systemId) override
{
(void)publicId;
(void)systemId;
return nullptr; // Block all external entity resolution
}
};
int ParameterManager::LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSource& inputSource)
{
//
// Create our parser, then attach an error handler to the parser.
// The parser will call back to methods of the ErrorHandler if it
// discovers errors during the course of parsing the XML document.
//
auto parser = std::make_unique<XercesDOMParser>();
auto entityBlocker = std::make_unique<NoOpEntityResolver>();
parser->setValidationScheme(gValScheme);
parser->setDoNamespaces(gDoNamespaces);
parser->setDoSchema(gDoSchema);
parser->setValidationSchemaFullChecking(gSchemaFullChecking);
parser->setCreateEntityReferenceNodes(gDoCreate);
parser->setDisableDefaultEntityResolution(true);
parser->setEntityResolver(entityBlocker.get());
auto errReporter = std::make_unique<DOMTreeErrorReporter>();
parser->setErrorHandler(errReporter.get());
//
// Parse the XML file, catching any XML exceptions that might propagate
// out of it.
//
bool errorsOccurred = false;
try {
parser->parse(inputSource);
}
catch (const XMLException& e) {
std::cerr << "An error occurred during parsing\n Message: " << StrX(e.getMessage())
<< "\n";
errorsOccurred = true;
}
catch (const DOMException& e) {
std::cerr << "A DOM error occurred during parsing\n DOMException code: " << e.code
<< "\n";
errorsOccurred = true;
}
catch (...) {
std::cerr << "An error occurred during parsing\n " << "\n";
errorsOccurred = true;
}
if (errorsOccurred) {
return 0;
}
_pDocument = parser->adoptDocument();
if (!_pDocument) {
throw XMLBaseException("Malformed Parameter document: Invalid document");
}
DOMElement* rootElem = _pDocument->getDocumentElement();
if (!rootElem) {
throw XMLBaseException("Malformed Parameter document: Root group not found");
}
_pGroupNode = FindElement(rootElem, "FCParamGroup", "Root");
if (!_pGroupNode) {
throw XMLBaseException("Malformed Parameter document: Root group not found");
}
return 1;
}
void ParameterManager::SaveDocument(const char* sFileName) const
{
try {
Base::FileInfo file(sFileName);
QLockFile lock(getLockFile(file));
if (!lock.tryLock(getTimeout())) {
std::cerr << "Failed to access file for writing: " << sFileName << std::endl;
return;
}
//
// Plug in a format target to receive the resultant
// XML stream from the serializer.
//
// LocalFileFormatTarget prints the resultant XML stream
// to a file once it receives any thing from the serializer.
//
XMLFormatTarget* myFormTarget {};
#if defined(FC_OS_WIN32)
std::wstring name = file.toStdWString();
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
myFormTarget = new LocalFileFormatTarget(reinterpret_cast<const XMLCh*>(name.c_str()));
#else
myFormTarget = new LocalFileFormatTarget(file.filePath().c_str());
#endif
SaveDocument(myFormTarget);
delete myFormTarget;
}
catch (XMLException& e) {
std::cerr << "An error occurred during creation of output transcoder. Msg is:" << std::endl
<< StrX(e.getMessage()) << std::endl;
}
}
void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const
{
try {
std::unique_ptr<DOMPrintFilter> myFilter;
std::unique_ptr<DOMErrorHandler> myErrorHandler;
// NOLINTBEGIN
// get a serializer, an instance of DOMWriter
XMLCh tempStr[100];
XMLString::transcode("LS", tempStr, 99);
DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
DOMLSSerializer* theSerializer =
static_cast<DOMImplementationLS*>(impl)->createLSSerializer();
// NOLINTEND
// set user specified end of line sequence and output encoding
theSerializer->setNewLine(gMyEOLSequence);
//
// do the serialization through DOMWriter::writeNode();
//
if (_pDocument) {
DOMLSOutput* theOutput = static_cast<DOMImplementationLS*>(impl)->createLSOutput();
theOutput->setEncoding(gOutputEncoding);
if (gUseFilter) {
myFilter = std::make_unique<DOMPrintFilter>(
DOMNodeFilter::SHOW_ELEMENT | DOMNodeFilter::SHOW_ATTRIBUTE
| DOMNodeFilter::SHOW_DOCUMENT_TYPE | DOMNodeFilter::SHOW_TEXT);
theSerializer->setFilter(myFilter.get());
}
// plug in user's own error handler
myErrorHandler = std::make_unique<DOMPrintErrorHandler>();
DOMConfiguration* config = theSerializer->getDomConfig();
// NOLINTBEGIN
config->setParameter(XMLUni::fgDOMErrorHandler, myErrorHandler.get());
// set feature if the serializer supports the feature/mode
if (config->canSetParameter(XMLUni::fgDOMWRTSplitCdataSections, gSplitCdataSections)) {
config->setParameter(XMLUni::fgDOMWRTSplitCdataSections, gSplitCdataSections);
}
if (config->canSetParameter(XMLUni::fgDOMWRTDiscardDefaultContent,
gDiscardDefaultContent)) {
config->setParameter(XMLUni::fgDOMWRTDiscardDefaultContent, gDiscardDefaultContent);
}
if (config->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint)) {
config->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint);
}
// NOLINTEND
theOutput->setByteStream(pFormatTarget);
theSerializer->write(_pDocument, theOutput);
theOutput->release();
}
theSerializer->release();
}
catch (XMLException& e) {
std::cerr << "An error occurred during creation of output transcoder. Msg is:" << std::endl
<< StrX(e.getMessage()) << std::endl;
}
}
void ParameterManager::CreateDocument()
{
// creating a document from screatch
DOMImplementation* impl =
DOMImplementationRegistry::getDOMImplementation(XStrLiteral("Core").unicodeForm());
delete _pDocument;
_pDocument =
impl->createDocument(nullptr, // root element namespace URI.
XStrLiteral("FCParameters").unicodeForm(), // root element name
nullptr); // document type object (DTD).
// creating the node for the root group
DOMElement* rootElem = _pDocument->getDocumentElement();
_pGroupNode = _pDocument->createElement(XStrLiteral("FCParamGroup").unicodeForm());
_pGroupNode->setAttribute(XStrLiteral("Name").unicodeForm(), XStrLiteral("Root").unicodeForm());
rootElem->appendChild(_pGroupNode);
}
void ParameterManager::CheckDocument() const
{
if (!_pDocument) {
return;
}
try {
//
// Plug in a format target to receive the resultant
// XML stream from the serializer.
//
// LocalFileFormatTarget prints the resultant XML stream
// to a file once it receives any thing from the serializer.
//
MemBufFormatTarget myFormTarget;
SaveDocument(&myFormTarget);
// Either use the file saved on disk or write the current XML into a buffer in memory
// const char* xmlFile = "...";
MemBufInputSource xmlFile(myFormTarget.getRawBuffer(), myFormTarget.getLen(), "(memory)");
// Either load the XSD file from disk or use the built-in string
// const char* xsdFile = "...";
std::string xsdStr(xmlSchemeString); // NOLINT
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
MemBufInputSource xsdFile(reinterpret_cast<const XMLByte*>(xsdStr.c_str()),
xsdStr.size(),
"Parameter.xsd");
// See
// http://apache-xml-project.6118.n7.nabble.com/validating-xml-with-xsd-schema-td17515.html
//
XercesDOMParser parser;
Grammar* grammar = parser.loadGrammar(xsdFile, Grammar::SchemaGrammarType, true);
if (!grammar) {
Base::Console().error("Grammar file cannot be loaded.\n");
return;
}
parser.setExternalNoNamespaceSchemaLocation("Parameter.xsd");
// parser.setExitOnFirstFatalError(true);
// parser.setValidationConstraintFatal(true);
parser.cacheGrammarFromParse(true);
parser.setValidationScheme(XercesDOMParser::Val_Auto);
parser.setDoNamespaces(true);
parser.setDoSchema(true);
parser.setDisableDefaultEntityResolution(true);
DOMTreeErrorReporter errHandler;
parser.setErrorHandler(&errHandler);
parser.parse(xmlFile);
if (parser.getErrorCount() > 0) {
Base::Console().error("Unexpected XML structure detected: %zu errors\n",
parser.getErrorCount());
}
}
catch (XMLException& e) {
std::cerr << "An error occurred while checking document. Msg is:" << std::endl
<< StrX(e.getMessage()) << std::endl;
}
}
//**************************************************************************
//**************************************************************************
// DOMTreeErrorReporter
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void DOMTreeErrorReporter::warning(const SAXParseException& /*exc*/)
{
//
// Ignore all warnings.
//
}
void DOMTreeErrorReporter::error(const SAXParseException& toCatch)
{
fSawErrors = true;
std::cerr << "Error at file \"" << StrX(toCatch.getSystemId()) << "\", line "
<< toCatch.getLineNumber() << ", column " << toCatch.getColumnNumber()
<< "\n Message: " << StrX(toCatch.getMessage()) << std::endl;
}
void DOMTreeErrorReporter::fatalError(const SAXParseException& toCatch)
{
fSawErrors = true;
std::cerr << "Fatal Error at file \"" << StrX(toCatch.getSystemId()) << "\", line "
<< toCatch.getLineNumber() << ", column " << toCatch.getColumnNumber()
<< "\n Message: " << StrX(toCatch.getMessage()) << std::endl;
}
void DOMTreeErrorReporter::resetErrors()
{
// No-op in this case
}
//**************************************************************************
//**************************************************************************
// DOMPrintFilter
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DOMPrintFilter::DOMPrintFilter(ShowType whatToShow)
: fWhatToShow(whatToShow)
{}
DOMPrintFilter::FilterAction DOMPrintFilter::acceptNode(const DOMNode* node) const
{
if (XMLString::compareString(node->getNodeName(), XStrLiteral("FCParameters").unicodeForm())
== 0) {
// This node is supposed to have a single FCParamGroup and two text nodes.
// Over time it can happen that the text nodes collect extra newlines.
const DOMNodeList* children = node->getChildNodes();
for (XMLSize_t i = 0; i < children->getLength(); i++) {
DOMNode* child = children->item(i);
if (child->getNodeType() == DOMNode::TEXT_NODE) {
child->setNodeValue(XStrLiteral("\n").unicodeForm());
}
}
}
// clang-format off
switch (node->getNodeType()) {
case DOMNode::TEXT_NODE: {
// Filter out text element if it is under a group node. Note text xml
// element is plain text in between tags, and we do not store any text
// there.
auto parent = node->getParentNode();
if (parent && XMLString::compareString(parent->getNodeName(),
XStrLiteral("FCParamGroup").unicodeForm()) == 0) {
return DOMNodeFilter::FILTER_REJECT;
}
return DOMNodeFilter::FILTER_ACCEPT;
}
case DOMNode::DOCUMENT_TYPE_NODE:
case DOMNode::DOCUMENT_NODE: {
return DOMNodeFilter::FILTER_REJECT; // no effect
}
default: {
return DOMNodeFilter::FILTER_ACCEPT;
}
}
// clang-format on
}
//**************************************************************************
//**************************************************************************
// DOMPrintErrorHandler
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bool DOMPrintErrorHandler::handleError(const DOMError& domError)
{
// Display whatever error message passed from the serializer
char* msg = XMLString::transcode(domError.getMessage());
std::cout << msg << std::endl;
XMLString::release(&msg);
// Instructs the serializer to continue serialization if possible.
return true;
}