Merge pull request #27064 from ScholliYT/addObjectNameTo3mfExport
Mesh: Add object name attribute to 3MF export
This commit is contained in:
@@ -117,6 +117,44 @@ std::basic_string<XMLCh> XMLTools::toXMLString(const char* const fromTranscode)
|
||||
return str;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Escape special XML characters in a string.
|
||||
*
|
||||
* Replaces XML special characters (&, <, >, ", ') with their entity equivalents
|
||||
* (&, <, >, ", ').
|
||||
*
|
||||
* \param input The string to escape
|
||||
* \return The escaped string safe for use in XML content or attributes
|
||||
*/
|
||||
std::string XMLTools::escapeXml(const std::string& input)
|
||||
{
|
||||
std::string output;
|
||||
output.reserve(input.size());
|
||||
for (char ch : input) {
|
||||
switch (ch) {
|
||||
case '&':
|
||||
output.append("&");
|
||||
break;
|
||||
case '<':
|
||||
output.append("<");
|
||||
break;
|
||||
case '>':
|
||||
output.append(">");
|
||||
break;
|
||||
case '"':
|
||||
output.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
output.append("'");
|
||||
break;
|
||||
default:
|
||||
output.push_back(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void XMLTools::terminate()
|
||||
{
|
||||
transcoder.reset();
|
||||
|
||||
@@ -47,6 +47,7 @@ class BaseExport XMLTools
|
||||
public:
|
||||
static std::string toStdString(const XMLCh* const toTranscode);
|
||||
static std::basic_string<XMLCh> toXMLString(const char* const fromTranscode);
|
||||
static std::string escapeXml(const std::string& input);
|
||||
static void initialize();
|
||||
static void terminate();
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "Core/Evaluation.h"
|
||||
#include "Core/MeshKernel.h"
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/XMLTools.h>
|
||||
|
||||
#include "Writer3MF.h"
|
||||
|
||||
@@ -74,11 +75,11 @@ void Writer3MF::Finish(std::ostream& str)
|
||||
str << "</model>\n";
|
||||
}
|
||||
|
||||
bool Writer3MF::AddMesh(const MeshKernel& mesh, const Base::Matrix4D& mat)
|
||||
bool Writer3MF::AddMesh(const MeshKernel& mesh, const Base::Matrix4D& mat, const std::string& name)
|
||||
{
|
||||
int id = ++objectIndex;
|
||||
SaveBuildItem(id, mat);
|
||||
return SaveObject(zip, id, mesh);
|
||||
return SaveObject(zip, id, mesh, name);
|
||||
}
|
||||
|
||||
void Writer3MF::AddResource(const Resource3MF& res)
|
||||
@@ -111,7 +112,7 @@ bool Writer3MF::Save()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Writer3MF::SaveObject(std::ostream& str, int id, const MeshKernel& mesh) const
|
||||
bool Writer3MF::SaveObject(std::ostream& str, int id, const MeshKernel& mesh, const std::string& name) const
|
||||
{
|
||||
// NOLINTBEGIN(readability-magic-numbers, cppcoreguidelines-avoid-magic-numbers)
|
||||
const MeshPointArray& rPoints = mesh.GetPoints();
|
||||
@@ -121,7 +122,11 @@ bool Writer3MF::SaveObject(std::ostream& str, int id, const MeshKernel& mesh) co
|
||||
return false;
|
||||
}
|
||||
|
||||
str << Base::blanks(2) << "<object id=\"" << id << "\" type=\"" << GetType(mesh) << "\">\n";
|
||||
str << Base::blanks(2) << "<object id=\"" << id << "\" type=\"" << GetType(mesh) << "\"";
|
||||
if (!name.empty()) {
|
||||
str << " name=\"" << XMLTools::escapeXml(name) << "\"";
|
||||
}
|
||||
str << ">\n";
|
||||
str << Base::blanks(3) << "<mesh>\n";
|
||||
|
||||
// vertices
|
||||
|
||||
@@ -76,9 +76,10 @@ public:
|
||||
* \brief Add a mesh object resource to the 3MF file.
|
||||
* \param mesh The mesh object to be written
|
||||
* \param mat The placement of the mesh object
|
||||
* \param name The name of the mesh object which can later be displayed by downstream applications
|
||||
* \return true if the added mesh could be written successfully, false otherwise.
|
||||
*/
|
||||
bool AddMesh(const MeshKernel& mesh, const Base::Matrix4D& mat);
|
||||
bool AddMesh(const MeshKernel& mesh, const Base::Matrix4D& mat, const std::string& name = "");
|
||||
/*!
|
||||
* \brief AddResource
|
||||
* Add an additional resource to the 3MF file.
|
||||
@@ -97,7 +98,7 @@ private:
|
||||
std::string GetType(const MeshKernel& mesh) const;
|
||||
void SaveBuildItem(int id, const Base::Matrix4D& mat);
|
||||
static std::string DumpMatrix(const Base::Matrix4D& mat);
|
||||
bool SaveObject(std::ostream& str, int id, const MeshKernel& mesh) const;
|
||||
bool SaveObject(std::ostream& str, int id, const MeshKernel& mesh, const std::string& name) const;
|
||||
bool SaveRels(std::ostream& str) const;
|
||||
bool SaveContent(std::ostream& str) const;
|
||||
|
||||
|
||||
@@ -2193,7 +2193,7 @@ void MeshOutput::SaveXML(Base::Writer& writer) const
|
||||
bool MeshOutput::Save3MF(std::ostream& output) const
|
||||
{
|
||||
Writer3MF writer(output);
|
||||
writer.AddMesh(_rclMesh, _transform);
|
||||
writer.AddMesh(_rclMesh, _transform, objectName);
|
||||
return writer.Save();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <Base/Sequencer.h>
|
||||
#include <Base/Stream.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/XMLTools.h>
|
||||
#include "Core/Iterator.h"
|
||||
#include "Core/IO/Writer3MF.h"
|
||||
#include <zipios++/zipoutputstream.h>
|
||||
@@ -91,17 +92,6 @@ static std::vector<std::string> expandSubObjectNames(
|
||||
Exporter::Exporter() = default;
|
||||
|
||||
// static
|
||||
std::string Exporter::xmlEscape(const std::string& input)
|
||||
{
|
||||
std::string out(input);
|
||||
boost::replace_all(out, "&", "&");
|
||||
boost::replace_all(out, "\"", """);
|
||||
boost::replace_all(out, "'", "'");
|
||||
boost::replace_all(out, "<", "<");
|
||||
boost::replace_all(out, ">", ">");
|
||||
return out;
|
||||
}
|
||||
|
||||
int Exporter::addObject(App::DocumentObject* obj, float tol)
|
||||
{
|
||||
int count = 0;
|
||||
@@ -304,8 +294,7 @@ Exporter3MF::~Exporter3MF()
|
||||
|
||||
bool Exporter3MF::addMesh(const char* name, const MeshObject& mesh)
|
||||
{
|
||||
boost::ignore_unused(name);
|
||||
bool ok = d->writer3mf.AddMesh(mesh.getKernel(), mesh.getTransform());
|
||||
bool ok = d->writer3mf.AddMesh(mesh.getKernel(), mesh.getTransform(), name);
|
||||
if (ok) {
|
||||
for (const auto& it : d->ext) {
|
||||
d->writer3mf.AddResource(it->addMesh(mesh));
|
||||
@@ -421,7 +410,8 @@ bool ExporterAMF::addMesh(const char* name, const MeshObject& mesh)
|
||||
Base::SequencerLauncher seq("Saving...", 2 * numFacets + 1);
|
||||
|
||||
*outputStreamPtr << "\t<object id=\"" << nextObjectIndex << "\">\n";
|
||||
*outputStreamPtr << "\t\t<metadata type=\"name\">" << xmlEscape(name) << "</metadata>\n";
|
||||
*outputStreamPtr << "\t\t<metadata type=\"name\">" << XMLTools::escapeXml(name)
|
||||
<< "</metadata>\n";
|
||||
*outputStreamPtr << "\t\t<mesh>\n"
|
||||
<< "\t\t\t<vertices>\n";
|
||||
|
||||
|
||||
@@ -69,8 +69,6 @@ public:
|
||||
Exporter& operator=(Exporter&&) = delete;
|
||||
|
||||
protected:
|
||||
/// Does some simple escaping of characters for XML-type exports
|
||||
static std::string xmlEscape(const std::string& input);
|
||||
void throwIfNoPermission(const std::string&);
|
||||
|
||||
std::map<const App::DocumentObject*, std::vector<std::string>> subObjectNameCache;
|
||||
|
||||
@@ -29,6 +29,7 @@ add_executable(Base_tests_run
|
||||
Vector3D.cpp
|
||||
ViewProj.cpp
|
||||
Writer.cpp
|
||||
XMLTools.cpp
|
||||
)
|
||||
|
||||
setup_qt_test(InventorBuilder)
|
||||
|
||||
79
tests/src/Base/XMLTools.cpp
Normal file
79
tests/src/Base/XMLTools.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <Base/XMLTools.h>
|
||||
|
||||
class TestXMLTools: public testing::Test
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlEmptyString)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml(""), "");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlNoChangesForSafeText)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml("abcXYZ_123"), "abcXYZ_123");
|
||||
EXPECT_EQ(XMLTools::escapeXml("hello world"), "hello world");
|
||||
EXPECT_EQ(XMLTools::escapeXml("line1\nline2\tend"), "line1\nline2\tend");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlEscapesAmpersand)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml("&"), "&");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a&b"), "a&b");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlEscapesLessThanAndGreaterThan)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml("<"), "<");
|
||||
EXPECT_EQ(XMLTools::escapeXml(">"), ">");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a<b"), "a<b");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a>b"), "a>b");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a<b>c"), "a<b>c");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlEscapesQuotes)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml("\""), """);
|
||||
EXPECT_EQ(XMLTools::escapeXml("'"), "'");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a\"b"), "a"b");
|
||||
EXPECT_EQ(XMLTools::escapeXml("a'b"), "a'b");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlEscapesAllFiveInOneString)
|
||||
{
|
||||
// input: & < > " '
|
||||
EXPECT_EQ(XMLTools::escapeXml("&<>\"'"), "&<>"'");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlDoesNotDoubleEscapeNewlyInsertedEntities)
|
||||
{
|
||||
// This test specifically catches the classic bug where you replace
|
||||
// '<' with "<" and then later replace '&' with "&" and end up with "&lt;".
|
||||
EXPECT_EQ(XMLTools::escapeXml("<"), "<");
|
||||
EXPECT_EQ(XMLTools::escapeXml(">"), ">");
|
||||
EXPECT_EQ(XMLTools::escapeXml("\""), """);
|
||||
EXPECT_EQ(XMLTools::escapeXml("'"), "'");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlComplexMixedContent)
|
||||
{
|
||||
const std::string in = "Tom & Jerry <\"fun\"> 'n' games";
|
||||
const std::string out = "Tom & Jerry <"fun"> 'n' games";
|
||||
EXPECT_EQ(XMLTools::escapeXml(in), out);
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlMultipleAdjacentCharacters)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml("&&&&"), "&&&&");
|
||||
EXPECT_EQ(XMLTools::escapeXml("<<<<"), "<<<<");
|
||||
EXPECT_EQ(XMLTools::escapeXml("\"\"''"), """''");
|
||||
}
|
||||
|
||||
TEST_F(TestXMLTools, EscapeXmlPreservesWhitespaceAndControlCommonInText)
|
||||
{
|
||||
EXPECT_EQ(XMLTools::escapeXml(" a \r\n b \t c "), " a \r\n b \t c ");
|
||||
}
|
||||
Reference in New Issue
Block a user