From 679d4c239711f4e038d05815b81770271bbd2ed2 Mon Sep 17 00:00:00 2001 From: tetektoza Date: Sat, 27 Dec 2025 18:45:32 +0100 Subject: [PATCH 1/5] Gui: Fix XHTML doc structure - Add missing element in <head> (required by XHTML Strict) - Add <body> wrapper around content (required by XHTML) - Add Content-Type meta tag for proper character encoding - Fix indentation of buttons to be inside body element --- src/Gui/SoFCDB.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index c12c3bde4a..ce403a6483 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -690,14 +690,17 @@ bool Gui::SoFCDB::writeToX3DOM(SoNode* node, std::string& buffer) "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"; out << "<html xmlns='http://www.w3.org/1999/xhtml'>\n" << " <head>\n" + << " <title>FreeCAD X3DOM Export\n" + << " \n" << " \n" << " \n" - << " \n"; + << " \n" + << " \n"; auto onclick = [&out](const char* text) { - out << " \n"; }; @@ -711,7 +714,8 @@ bool Gui::SoFCDB::writeToX3DOM(SoNode* node, std::string& buffer) out << x3d; - out << "\n"; + out << " \n" + << "\n"; buffer = out.str(); From fe7bceeb794bc227050fae46e37e6ceff606b131 Mon Sep 17 00:00:00 2001 From: tetektoza Date: Sat, 27 Dec 2025 18:51:01 +0100 Subject: [PATCH 2/5] Gui: Escape XML special characters in attributes for XHTML Add escapeXmlAttribute() helper function to properly escape special characters in XML attribute values: - Double quotes (") --> " - Ampersands (&) --> & - Less-than (<) --> < - Greater-than (>) --> > - Single quotes (') --> ' --- src/Gui/SoFCDB.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index ce403a6483..4144658330 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -497,6 +497,43 @@ bool Gui::SoFCDB::writeToX3D(SoNode* node, bool exportViewpoints, std::string& b return true; } +namespace +{ +std::string escapeXmlAttribute(const char* str) +{ + if (!str) { + return std::string(); + } + + std::string result; + result.reserve(strlen(str)); + + for (const char* p = str; *p; ++p) { + switch (*p) { + case '"': + result.append("""); + break; + case '&': + result.append("&"); + break; + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + case '\'': + result.append("'"); + break; + default: + result.push_back(*p); + break; + } + } + return result; +} +} // namespace + void Gui::SoFCDB::writeX3DFields( SoNode* node, std::map& nodeMap, @@ -522,7 +559,8 @@ void Gui::SoFCDB::writeX3DFields( } nodeMap[node] = str.str(); - out << " DEF=\"" << str.str() << "\""; + std::string escapedName = escapeXmlAttribute(str.str().c_str()); + out << " DEF=\"" << escapedName << "\""; } const SoFieldData* fielddata = node->getFieldData(); @@ -545,9 +583,12 @@ void Gui::SoFCDB::writeX3DFields( ba = ba.simplified(); } + // escape XML special characters in attribute value + std::string escapedValue = escapeXmlAttribute(ba.data()); + out << '\n' << Base::blanks(spaces + 2) << fielddata->getFieldName(i).getString() - << "=\"" << ba.data() << "\" "; + << "=\"" << escapedValue << "\" "; } else { numFieldNodes++; @@ -606,7 +647,8 @@ void Gui::SoFCDB::writeX3DChild( // remove the VRML prefix from the type name std::string sftype(node->getTypeId().getName().getString()); sftype = sftype.substr(4); - out << Base::blanks(spaces) << "<" << sftype << " USE=\"" << mapIt->second << "\" />\n"; + std::string escapedRef = escapeXmlAttribute(mapIt->second.c_str()); + out << Base::blanks(spaces) << "<" << sftype << " USE=\"" << escapedRef << "\" />\n"; } } From 2ae054e470e0b7d29425434ffdbb61257cc5c61f Mon Sep 17 00:00:00 2001 From: tetektoza Date: Sat, 27 Dec 2025 18:54:00 +0100 Subject: [PATCH 3/5] Gui: DOCTYPE and structure improvements Fix X3D standalone export (.x3d): - Use proper X3D 3.3 DOCTYPE instead of XHTML DOCTYPE - Remove problematic default namespace declaration - Use xsd:noNamespaceSchemaLocation for better parser compatibility - Remove invalid width/height attributes Fix XHTML/X3DOM export (.xhtml): - Wrap navigation buttons in
(required by XHTML Strict) - Keep X3D elements unprefixed for X3DOM compatibility - Add proper XHTML structure with XML declaration --- src/Gui/SoFCDB.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 4144658330..afc3df6f55 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -655,12 +655,11 @@ void Gui::SoFCDB::writeX3DChild( void Gui::SoFCDB::writeX3D(SoVRMLGroup* node, bool exportViewpoints, std::ostream& out) { out << "\n"; - out << "\n"; - out << "\n"; + out << "\n"; + "xsd:noNamespaceSchemaLocation=\"http://www.web3d.org/specifications/x3d-3.3.xsd\">\n"; out << " \n" " \n" " \n" @@ -727,22 +726,23 @@ bool Gui::SoFCDB::writeToX3DOM(SoNode* node, std::string& buffer) x3d = x3d.erase(0, pos + 1); std::stringstream out; - out << "\n" - << "\n"; + out << "\n"; - out << "\n" + out << "\n" << " \n" - << " FreeCAD X3DOM Export\n" << " \n" - << " \n" - << " \n" + << " \n" << " \n" - << " \n"; + << " \n" + << "
\n"; auto onclick = [&out](const char* text) { - out << " \n"; }; @@ -754,7 +754,7 @@ bool Gui::SoFCDB::writeToX3DOM(SoNode* node, std::string& buffer) onclick("Top"); onclick("Bottom"); - out << x3d; + out << "
\n" << x3d; out << " \n" << "\n"; From 38bb34ab85ee5cff9e16e395fcf489e6fa49d1fd Mon Sep 17 00:00:00 2001 From: tetektoza Date: Sat, 27 Dec 2025 19:38:34 +0100 Subject: [PATCH 4/5] Gui: Clean up XHTML meta tags per Nu validator feedback - Change http-equiv="Content-Type" to simpler charset="utf-8" - Remove unnecessary type="text/javascript" from script tag --- src/Gui/SoFCDB.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index afc3df6f55..3ffa9210b9 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -731,10 +731,9 @@ bool Gui::SoFCDB::writeToX3DOM(SoNode* node, std::string& buffer) "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"; out << "\n" << " \n" - << " \n" + << " \n" << " FreeCAD X3DOM Export\n" - << " \n" + << " \n" << " \n" << " \n" From 57dd04f214ea70a94ed1fb699e4821c9d864155a Mon Sep 17 00:00:00 2001 From: tetektoza Date: Tue, 30 Dec 2025 17:22:00 +0100 Subject: [PATCH 5/5] Gui: Use `encodeAttribute` when escaping XML attributes for XHTML --- src/Gui/SoFCDB.cpp | 45 ++++++--------------------------------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 3ffa9210b9..2076baeb0c 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -42,6 +42,7 @@ #include +#include #include #include #include @@ -497,42 +498,6 @@ bool Gui::SoFCDB::writeToX3D(SoNode* node, bool exportViewpoints, std::string& b return true; } -namespace -{ -std::string escapeXmlAttribute(const char* str) -{ - if (!str) { - return std::string(); - } - - std::string result; - result.reserve(strlen(str)); - - for (const char* p = str; *p; ++p) { - switch (*p) { - case '"': - result.append("""); - break; - case '&': - result.append("&"); - break; - case '<': - result.append("<"); - break; - case '>': - result.append(">"); - break; - case '\'': - result.append("'"); - break; - default: - result.push_back(*p); - break; - } - } - return result; -} -} // namespace void Gui::SoFCDB::writeX3DFields( SoNode* node, @@ -559,7 +524,7 @@ void Gui::SoFCDB::writeX3DFields( } nodeMap[node] = str.str(); - std::string escapedName = escapeXmlAttribute(str.str().c_str()); + std::string escapedName = Base::Persistence::encodeAttribute(str.str()); out << " DEF=\"" << escapedName << "\""; } @@ -584,7 +549,9 @@ void Gui::SoFCDB::writeX3DFields( } // escape XML special characters in attribute value - std::string escapedValue = escapeXmlAttribute(ba.data()); + std::string escapedValue = Base::Persistence::encodeAttribute( + std::string(ba.data()) + ); out << '\n' << Base::blanks(spaces + 2) << fielddata->getFieldName(i).getString() @@ -647,7 +614,7 @@ void Gui::SoFCDB::writeX3DChild( // remove the VRML prefix from the type name std::string sftype(node->getTypeId().getName().getString()); sftype = sftype.substr(4); - std::string escapedRef = escapeXmlAttribute(mapIt->second.c_str()); + std::string escapedRef = Base::Persistence::encodeAttribute(mapIt->second); out << Base::blanks(spaces) << "<" << sftype << " USE=\"" << escapedRef << "\" />\n"; } }