Files
create/src/App/ProjectFile.cpp
Markus Reitböck 73c97bc90f App: 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:02 +02:00

742 lines
25 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <cassert>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XercesVersion.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMImplementation.hpp>
#include <xercesc/dom/DOMImplementationLS.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <xercesc/framework/LocalFileInputSource.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/util/XMLUni.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <sstream>
#include <zipios++/zipios-config.h>
#include <zipios++/zipfile.h>
#include <zipios++/zipinputstream.h>
#include <zipios++/zipoutputstream.h>
#include <zipios++/meta-iostreams.h>
#include "ProjectFile.h"
#include "DocumentObject.h"
#include <Base/FileInfo.h>
#include <Base/InputSource.h>
#include <Base/Reader.h>
#include <Base/Writer.h>
#include <Base/Stream.h>
#include <Base/XMLTools.h>
#ifndef XERCES_CPP_NAMESPACE_BEGIN
#define XERCES_CPP_NAMESPACE_QUALIFIER
using namespace XERCES_CPP_NAMESPACE;
#else
XERCES_CPP_NAMESPACE_USE
#endif
using namespace App;
namespace
{
class ZipTools
{
public:
static std::unique_ptr<zipios::ZipFile> open(const std::string& file)
{
std::unique_ptr<zipios::ZipFile> project;
try {
project = std::make_unique<zipios::ZipFile>(file);
if (!project->isValid()) {
project.reset();
}
}
catch (const std::exception&) {
}
return project;
}
};
class DocumentMetadata
{
public:
explicit DocumentMetadata(XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* xmlDocument)
: xmlDocument {xmlDocument}
{}
ProjectFile::Metadata getMetadata() const
{
return metadata;
}
void readXML()
{
readProgramVersion();
std::map<std::string, std::string> propMap = initMap();
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("Properties").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
auto elem = static_cast<DOMElement*>(node); // NOLINT
DOMNodeList* propList = elem->getElementsByTagName(XStrLiteral("Property").unicodeForm());
for (XMLSize_t j = 0; j < propList->getLength(); j++) {
DOMNode* propNode = propList->item(j);
readProperty(propNode, propMap);
}
}
break;
}
setMetadata(propMap);
}
private:
void readProgramVersion()
{
if (DOMNodeList* nodes =
xmlDocument->getElementsByTagName(XStrLiteral("Document").unicodeForm())) {
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNode* nameAttr =
node->getAttributes()->getNamedItem(XStrLiteral("ProgramVersion").unicodeForm());
if (nameAttr) {
std::string value = StrX(nameAttr->getNodeValue()).c_str();
metadata.programVersion = value;
break;
}
}
}
}
}
static std::map<std::string, std::string> initMap()
{
// clang-format off
std::map<std::string, std::string> propMap = {{"Comment", ""},
{"Company", ""},
{"CreatedBy", ""},
{"CreationDate", ""},
{"Label", ""},
{"LastModifiedBy", ""},
{"LastModifiedDate", ""},
{"License", ""},
{"LicenseURL", ""},
{"Uid", ""}};
return propMap;
// clang-format on
}
void setMetadata(const std::map<std::string, std::string>& propMap)
{
metadata.comment = propMap.at("Comment");
metadata.company = propMap.at("Company");
metadata.createdBy = propMap.at("CreatedBy");
metadata.creationDate = propMap.at("CreationDate");
metadata.label = propMap.at("Label");
metadata.lastModifiedBy = propMap.at("LastModifiedBy");
metadata.lastModifiedDate = propMap.at("LastModifiedDate");
metadata.license = propMap.at("License");
metadata.licenseURL = propMap.at("LicenseURL");
metadata.uuid = propMap.at("Uid");
}
static void readProperty(DOMNode* propNode, std::map<std::string, std::string>& propMap)
{
DOMNode* nameAttr = propNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (nameAttr) {
std::string name = StrX(nameAttr->getNodeValue()).c_str();
auto it = propMap.find(name);
if (it != propMap.end()) {
it->second = readValue(propNode);
}
}
}
static std::string readValue(DOMNode* node)
{
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
if (DOMElement* child =
static_cast<DOMElement*>(node)->getFirstElementChild()) { // NOLINT
if (DOMNode* nameAttr =
child->getAttributes()->getNamedItem(XStrLiteral("value").unicodeForm())) {
std::string value = StrX(nameAttr->getNodeValue()).c_str();
return value;
}
}
}
return {};
}
private:
XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* xmlDocument;
ProjectFile::Metadata metadata;
};
} // namespace
ProjectFile::ProjectFile()
: xmlDocument(nullptr)
{}
ProjectFile::ProjectFile(std::string zipArchive)
: stdFile(std::move(zipArchive))
, xmlDocument(nullptr)
{}
ProjectFile::~ProjectFile()
{
delete xmlDocument;
}
void ProjectFile::setProjectFile(const std::string& zipArchive)
{
stdFile = zipArchive;
delete xmlDocument;
xmlDocument = nullptr;
}
bool ProjectFile::loadDocument()
{
if (xmlDocument) {
return true; // already loaded
}
auto project = ZipTools::open(stdFile);
if (!project) {
return false;
}
std::unique_ptr<std::istream> str(project->getInputStream("Document.xml"));
if (str) {
std::unique_ptr<XercesDOMParser> parser(new XercesDOMParser);
parser->setValidationScheme(XercesDOMParser::Val_Auto);
parser->setDoNamespaces(false);
parser->setDoSchema(false);
parser->setValidationSchemaFullChecking(false);
parser->setCreateEntityReferenceNodes(false);
try {
Base::StdInputSource inputSource(*str, stdFile.c_str());
parser->parse(inputSource);
xmlDocument = parser->adoptDocument();
return true;
}
catch (const XMLException&) {
return false;
}
catch (const DOMException&) {
return false;
}
}
return false;
}
ProjectFile::Metadata ProjectFile::getMetadata() const
{
if (!xmlDocument) {
return {};
}
DocumentMetadata reader(xmlDocument);
reader.readXML();
return reader.getMetadata();
}
std::list<ProjectFile::Object> ProjectFile::getObjects() const
{
std::list<Object> names;
if (!xmlDocument) {
return names;
}
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("Objects").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNodeList* objectList = static_cast<DOMElement*>(node)->getElementsByTagName(
XStrLiteral("Object").unicodeForm()); // NOLINT
for (XMLSize_t j = 0; j < objectList->getLength(); j++) {
DOMNode* objectNode = objectList->item(j);
DOMNode* typeAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("type").unicodeForm());
DOMNode* nameAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (typeAttr && nameAttr) {
Object obj;
obj.name = StrX(nameAttr->getNodeValue()).c_str();
obj.type = Base::Type::fromName(StrX(typeAttr->getNodeValue()).c_str());
names.push_back(obj);
}
}
}
}
return names;
}
std::list<std::string> ProjectFile::getObjectsOfType(const Base::Type& typeId) const
{
std::list<std::string> names;
if (!xmlDocument) {
return names;
}
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("Objects").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNodeList* objectList = static_cast<DOMElement*>(node)->getElementsByTagName(
XStrLiteral("Object").unicodeForm()); // NOLINT
for (XMLSize_t j = 0; j < objectList->getLength(); j++) {
DOMNode* objectNode = objectList->item(j);
DOMNode* typeAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("type").unicodeForm());
DOMNode* nameAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (typeAttr && nameAttr) {
if (Base::Type::fromName(StrX(typeAttr->getNodeValue()).c_str()) == typeId) {
names.emplace_back(StrX(nameAttr->getNodeValue()).c_str());
}
}
}
}
}
return names;
}
bool ProjectFile::restoreObject(const std::string& name, App::PropertyContainer* obj, bool verbose)
{
Base::FileInfo fi(stdFile);
Base::ifstream file(fi, std::ios::in | std::ios::binary);
zipios::ZipInputStream zipstream(file);
Base::XMLReader reader(stdFile.c_str(), zipstream);
reader.setVerbose(verbose);
if (!reader.isValid()) {
return false;
}
// skip document properties
reader.readElement("Properties");
reader.readEndElement("Properties");
// skip objects
reader.readElement("Objects");
reader.readEndElement("Objects");
reader.readElement("ObjectData");
long Cnt = reader.getAttribute<long>("Count");
for (long i = 0; i < Cnt; i++) {
reader.readElement("Object");
std::string nameAttr = reader.getAttribute<const char*>("name");
if (nameAttr == name) {
// obj->StatusBits.set(4);
obj->Restore(reader);
// obj->StatusBits.reset(4);
}
reader.readEndElement("Object");
}
reader.readEndElement("ObjectData");
reader.readFiles(zipstream);
return true;
}
Base::Type ProjectFile::getTypeId(const std::string& name) const
{
// <Objects Count="1">
// <Object type="Mesh::MeshFeature" name="Mesh" />
// <Objects/>
if (!xmlDocument) {
return Base::Type::BadType;
}
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("Objects").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNodeList* objectList = static_cast<DOMElement*>(node)->getElementsByTagName(
XStrLiteral("Object").unicodeForm()); // NOLINT
for (XMLSize_t j = 0; j < objectList->getLength(); j++) {
DOMNode* objectNode = objectList->item(j);
DOMNode* typeAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("type").unicodeForm());
DOMNode* nameAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (typeAttr && nameAttr) {
if (strcmp(name.c_str(), StrX(nameAttr->getNodeValue()).c_str()) == 0) {
std::string typeId = StrX(typeAttr->getNodeValue()).c_str();
return Base::Type::fromName(typeId.c_str());
}
}
}
}
}
return Base::Type::BadType;
}
std::list<ProjectFile::PropertyFile> ProjectFile::getPropertyFiles(const std::string& name) const
{
// <ObjectData Count="1">
// <Object name="Mesh">
// <Properties Count="1">
// <Property name="Mesh" type="Mesh::PropertyMeshKernel">
// <Mesh file="MeshKernel.bms"/>
// <Property/>
// <Properties/>
// <Object/>
// <ObjectData/>
if (!xmlDocument) {
return {};
}
std::list<PropertyFile> files;
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("ObjectData").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNodeList* objectList = static_cast<DOMElement*>(node)->getElementsByTagName(
XStrLiteral("Object").unicodeForm()); // NOLINT
for (XMLSize_t j = 0; j < objectList->getLength(); j++) {
DOMNode* objectNode = objectList->item(j);
DOMNode* nameAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (nameAttr && strcmp(name.c_str(), StrX(nameAttr->getNodeValue()).c_str()) == 0) {
// now go recursively through the sub-tree (i.e. the properties) and collect
// every file attribute
findFiles(objectNode, files);
break;
}
}
}
}
return files;
}
void ProjectFile::findFiles(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* node,
std::list<ProjectFile::PropertyFile>& files) const
{
if (node->hasAttributes()) {
ProjectFile::PropertyFile prop;
DOMNode* fileAttr = node->getAttributes()->getNamedItem(XStrLiteral("file").unicodeForm());
if (fileAttr) {
DOMNode* parentNode = node->getParentNode();
if (parentNode) {
DOMNode* nameAttr =
parentNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (nameAttr) {
prop.name = StrX(nameAttr->getNodeValue()).c_str();
}
DOMNode* typeAttr =
parentNode->getAttributes()->getNamedItem(XStrLiteral("type").unicodeForm());
if (typeAttr) {
prop.type = Base::Type::fromName(StrX(typeAttr->getNodeValue()).c_str());
}
}
prop.file = StrX(fileAttr->getNodeValue()).c_str();
files.push_back(prop);
}
}
DOMNodeList* subNodes = node->getChildNodes();
for (XMLSize_t i = 0; i < subNodes->getLength(); i++) {
DOMNode* child = subNodes->item(i);
findFiles(child, files);
}
}
bool ProjectFile::containsFile(const std::string& name) const
{
zipios::ZipFile project(stdFile);
auto entry = project.getEntry(name);
return entry != nullptr;
}
std::list<std::string> ProjectFile::getInputFiles(const std::string& name) const
{
// <ObjectData Count="1">
// <Object name="Mesh">
// <Properties Count="1">
// <Property name="Mesh" type="Mesh::PropertyMeshKernel">
// <Mesh file="MeshKernel.bms"/>
// <Property/>
// <Properties/>
// <Object/>
// <ObjectData/>
if (!xmlDocument) {
return {};
}
std::list<std::string> files;
DOMNodeList* nodes = xmlDocument->getElementsByTagName(XStrLiteral("ObjectData").unicodeForm());
for (XMLSize_t i = 0; i < nodes->getLength(); i++) {
DOMNode* node = nodes->item(i);
if (node->getNodeType() == DOMNode::ELEMENT_NODE) {
DOMNodeList* objectList = static_cast<DOMElement*>(node)->getElementsByTagName(
XStrLiteral("Object").unicodeForm()); // NOLINT
for (XMLSize_t j = 0; j < objectList->getLength(); j++) {
DOMNode* objectNode = objectList->item(j);
DOMNode* nameAttr =
objectNode->getAttributes()->getNamedItem(XStrLiteral("name").unicodeForm());
if (nameAttr && strcmp(name.c_str(), StrX(nameAttr->getNodeValue()).c_str()) == 0) {
// now go recursively through the sub-tree (i.e. the properties) and collect
// every file attribute
findFiles(objectNode, files);
break;
}
}
}
}
return files;
}
void ProjectFile::findFiles(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* node,
std::list<std::string>& files) const
{
if (node->hasAttributes()) {
DOMNode* fileAttr = node->getAttributes()->getNamedItem(XStrLiteral("file").unicodeForm());
if (fileAttr) {
files.emplace_back(StrX(fileAttr->getNodeValue()).c_str());
}
}
DOMNodeList* subNodes = node->getChildNodes();
for (XMLSize_t i = 0; i < subNodes->getLength(); i++) {
DOMNode* child = subNodes->item(i);
findFiles(child, files);
}
}
std::string ProjectFile::extractInputFile(const std::string& name)
{
zipios::ZipFile project(stdFile);
std::unique_ptr<std::istream> str(project.getInputStream(name));
if (str) {
// write it to a tmp. file as writing to the string stream
// might take too long
Base::FileInfo fi(Base::FileInfo::getTempFileName());
Base::ofstream file(fi, std::ios::out | std::ios::binary);
std::streambuf* buf = file.rdbuf();
(*str) >> buf;
file.flush();
file.close();
return fi.filePath();
}
return {};
}
void ProjectFile::readInputFile(const std::string& name, std::ostream& str)
{
Base::FileInfo fi(extractInputFile(name));
if (fi.exists()) {
Base::ifstream file(fi, std::ios::in | std::ios::binary);
file >> str.rdbuf();
file.close();
fi.deleteFile();
}
}
// Read the given input file from the zip directly into the given stream (not using a temporary
// file)
void ProjectFile::readInputFileDirect(const std::string& name, std::ostream& str)
{
zipios::ZipFile project(stdFile);
std::unique_ptr<std::istream> istr(project.getInputStream(name));
if (istr) {
*istr >> str.rdbuf();
}
}
std::string ProjectFile::replaceInputFile(const std::string& name, std::istream& inp)
{
// create a new zip file with the name '<zipfile>.<uuid>'
std::string uuid = Base::Uuid::createUuid();
std::string fn = stdFile;
fn += ".";
fn += uuid;
Base::FileInfo tmp(fn);
Base::ofstream newZip(tmp, std::ios::out | std::ios::binary);
// standard compression
const int compressionLevel = 6;
zipios::ZipOutputStream outZip(newZip);
outZip.setComment("FreeCAD Document");
outZip.setLevel(compressionLevel);
// open the original zip file
zipios::ZipFile project(stdFile);
zipios::ConstEntries files = project.entries();
for (const auto& it : files) {
std::string file = it->getFileName();
outZip.putNextEntry(file);
if (file == name) {
inp >> outZip.rdbuf();
}
else {
std::unique_ptr<std::istream> str(project.getInputStream(file));
if (str) {
*str >> outZip.rdbuf();
}
}
}
project.close();
outZip.close();
newZip.close();
return fn;
}
std::string ProjectFile::replaceInputFiles(const std::map<std::string, std::istream*>& inp)
{
// create a new zip file with the name '<zipfile>.<uuid>'
std::string uuid = Base::Uuid::createUuid();
std::string fn = stdFile;
fn += ".";
fn += uuid;
Base::FileInfo tmp(fn);
Base::ofstream newZip(tmp, std::ios::out | std::ios::binary);
// standard compression
const int compressionLevel = 6;
zipios::ZipOutputStream outZip(newZip);
outZip.setComment("FreeCAD Document");
outZip.setLevel(compressionLevel);
// open the original zip file
zipios::ZipFile project(stdFile);
zipios::ConstEntries files = project.entries();
for (const auto& it : files) {
std::string file = it->getFileName();
outZip.putNextEntry(file);
auto jt = inp.find(file);
if (jt != inp.end()) {
*jt->second >> outZip.rdbuf();
}
else {
std::unique_ptr<std::istream> str(project.getInputStream(file));
if (str) {
*str >> outZip.rdbuf();
}
}
}
project.close();
outZip.close();
newZip.close();
return fn;
}
std::string ProjectFile::replacePropertyFiles(const std::map<std::string, App::Property*>& props)
{
// create a new zip file with the name '<zipfile>.<uuid>'
std::string uuid = Base::Uuid::createUuid();
std::string fn = stdFile;
fn += ".";
fn += uuid;
Base::FileInfo tmp(fn);
Base::ofstream newZip(tmp, std::ios::out | std::ios::binary);
// open extra scope
{
// standard compression
const int compressionLevel = 6;
Base::ZipWriter writer(newZip);
writer.setComment("FreeCAD Document");
writer.setLevel(compressionLevel);
// open the original zip file
zipios::ZipFile project(stdFile);
zipios::ConstEntries files = project.entries();
for (const auto& it : files) {
std::string file = it->getFileName();
writer.putNextEntry(file.c_str());
auto jt = props.find(file);
if (jt != props.end()) {
jt->second->SaveDocFile(writer);
}
else {
std::unique_ptr<std::istream> str(project.getInputStream(file));
if (str) {
*str >> writer.Stream().rdbuf();
}
}
}
project.close();
}
return fn;
}
bool ProjectFile::replaceProjectFile(const std::string& name, bool keepfile)
{
std::string uuid = Base::Uuid::createUuid();
std::string fn = stdFile;
fn += ".";
fn += uuid;
// Now rename the original file to something unique
Base::FileInfo orig(stdFile);
if (!orig.renameFile(fn.c_str())) {
return false;
}
orig.setFile(fn);
// rename the tmp.file to the original file name
Base::FileInfo other(name);
if (!other.renameFile(stdFile.c_str())) {
return false;
}
// and delete the renamed original file.
if (!keepfile) {
if (!orig.deleteFile()) {
return false;
}
}
return true;
}